updates for 0.9.3
[debian/openrocket] / src / net / sf / openrocket / database / Database.java
1 package net.sf.openrocket.database;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.FilenameFilter;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.util.AbstractSet;
9 import java.util.ArrayList;
10 import java.util.Collections;
11 import java.util.Enumeration;
12 import java.util.Iterator;
13 import java.util.List;
14 import java.util.jar.JarEntry;
15 import java.util.jar.JarFile;
16
17 import javax.swing.event.ChangeEvent;
18 import javax.swing.event.ChangeListener;
19 import javax.swing.event.EventListenerList;
20
21 import net.sf.openrocket.file.Loader;
22 import net.sf.openrocket.util.ChangeSource;
23 import net.sf.openrocket.util.JarUtil;
24
25
26
27 /**
28  * A database set.  This class functions as a <code>Set</code> that contains items
29  * of a specific type.  Additionally, the items can be accessed via an index number.
30  * The elements are always kept in their natural order.
31  * 
32  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
33  */
34
35 // TODO: HIGH: Database saving
36 public class Database<T extends Comparable<T>> extends AbstractSet<T> implements ChangeSource {
37
38         private final List<T> list = new ArrayList<T>();
39         private final EventListenerList listenerList = new EventListenerList();
40         private final Loader<T> loader;
41         private final DatabaseStore<T> store;
42         
43         
44         public Database() {
45                 loader = null;
46                 store = null;
47         }
48         
49         public Database(Loader<T> loader) {
50                 this.loader = loader;
51                 this.store = null;
52         }
53         
54         public Database(DatabaseStore<T> store) {
55                 this.loader = null;
56                 this.store = store;
57         }
58         
59         public Database(Loader<T> loader, DatabaseStore<T> store) {
60                 this.loader = loader;
61                 this.store = store;
62         }
63         
64                 
65         @Override
66         public Iterator<T> iterator() {
67                 return new DBIterator();
68         }
69
70         @Override
71         public int size() {
72                 return list.size();
73         }
74         
75         @Override
76         public boolean add(T element) {
77                 int index;
78                 
79                 index = Collections.binarySearch(list, element);
80                 if (index >= 0) {
81                         // List might contain the element
82                         if (list.contains(element)) {
83                                 return false;
84                         }
85                 } else {
86                         index = -(index+1);
87                 }
88                 list.add(index,element);
89                 if (store != null)
90                         store.elementAdded(element);
91                 fireChangeEvent();
92                 return true;
93         }
94         
95         
96         /**
97          * Get the element with the specified index.
98          * @param index the index to retrieve.
99          * @return              the element at the index.
100          */
101         public T get(int index) {
102                 return list.get(index);
103         }
104
105         /**
106          * Return the index of the given <code>Motor</code>, or -1 if not in the database.
107          * 
108          * @param m   the motor
109          * @return        the index of the motor
110          */
111         public int indexOf(T m) {
112                 return list.indexOf(m);
113         }
114         
115
116         @Override
117         public void addChangeListener(ChangeListener listener) {
118                 listenerList .add(ChangeListener.class, listener);
119         }
120
121
122         @Override
123         public void removeChangeListener(ChangeListener listener) {
124                 listenerList .remove(ChangeListener.class, listener);
125         }
126
127         
128         protected void fireChangeEvent() {
129                 Object[] listeners = listenerList.getListenerList();
130                 ChangeEvent e = null;
131                 for (int i = listeners.length-2; i>=0; i-=2) {
132                         if (listeners[i]==ChangeListener.class) {
133                                 // Lazily create the event:
134                                 if (e == null)
135                                         e = new ChangeEvent(this);
136                                 ((ChangeListener)listeners[i+1]).stateChanged(e);
137                         }
138                 }
139         }
140         
141
142         
143         ////////  Directory loading
144         
145         
146         
147         /**
148          * Load all files in a directory to the motor database.  Only files with file
149          * names matching the given pattern (as matched by <code>String.matches(String)</code>)
150          * are processed.
151          * 
152          * @param dir                   the directory to read.
153          * @param pattern               the pattern to match the file names to.
154          * @throws IOException  if an IO error occurs when reading the JAR archive
155          *                                              (errors reading individual files are printed to stderr).
156          */
157         public void loadDirectory(File dir, final String pattern) throws IOException {
158                 if (loader == null) {
159                         throw new IllegalStateException("no file loader set");
160                 }
161                 
162                 File[] files = dir.listFiles(new FilenameFilter() {
163                         @Override
164                         public boolean accept(File dir, String name) {
165                                 return name.matches(pattern);
166                         }
167                 });
168                 if (files == null) {
169                         throw new IOException("not a directory: "+dir);
170                 }
171                 for (File file: files) {
172                         try {
173                                 this.addAll(loader.load(new FileInputStream(file), file.getName()));
174                         } catch (IOException e) {
175                                 System.err.println("Error loading file "+file+": " + e.getMessage());
176                         }
177                 }
178         }
179         
180         
181         /**
182          * Read all files in a directory contained in the JAR file that this class belongs to.
183          * Only files whose names match the given pattern (as matched by
184          * <code>String.matches(String)</code>) will be read.
185          * 
186          * @param dir                   the directory within the JAR archive to read.
187          * @param pattern               the pattern to match the file names to.
188          * @throws IOException  if an IO error occurs when reading the JAR archive
189          *                                              (errors reading individual files are printed to stderr).
190          */
191         public void loadJarDirectory(String dir, String pattern) throws IOException {
192                 
193                 // Process directory and extension
194                 if (!dir.endsWith("/")) {
195                         dir += "/";
196                 }
197                 
198                 // Find and open the jar file this class is contained in
199                 File file = JarUtil.getCurrentJarFile();
200                 JarFile jarFile = new JarFile(file);
201                 
202                 try {
203
204                         // Loop through JAR entries searching for files to load
205                         Enumeration<JarEntry> entries = jarFile.entries();
206                         while (entries.hasMoreElements()) {
207                                 JarEntry entry = entries.nextElement();
208                                 String name = entry.getName();
209                                 if (name.startsWith(dir) && name.matches(pattern)) {
210                                         try {
211                                                 InputStream stream = jarFile.getInputStream(entry);
212                                                 this.addAll(loader.load(stream, name));
213                                         } catch (IOException e) {
214                                                 System.err.println("Error loading file " + file + ": "
215                                                                 + e.getMessage());
216                                         }
217                                 }
218                         }
219
220                 } finally {
221                         jarFile.close();
222                 }
223         }
224         
225         
226         
227         public void load(File file) throws IOException {
228                 if (loader == null) {
229                         throw new IllegalStateException("no file loader set");
230                 }
231                 this.addAll(loader.load(new FileInputStream(file), file.getName()));
232         }
233
234         
235         
236         /**
237          * Iterator class implementation that fires changes if remove() is called.
238          */
239         private class DBIterator implements Iterator<T> {
240                 private Iterator<T> iterator = list.iterator();
241                 private T current = null;
242                 
243                 @Override
244                 public boolean hasNext() {
245                         return iterator.hasNext();
246                 }
247
248                 @Override
249                 public T next() {
250                         current = iterator.next();
251                         return current;
252                 }
253
254                 @Override
255                 public void remove() {
256                         iterator.remove();
257                         if (store != null)
258                                 store.elementRemoved(current);
259                         fireChangeEvent();
260                 }
261         }
262 }