Bug fixes and startup checks
[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         
42         
43         public Database() {
44                 loader = null;
45         }
46         
47         public Database(Loader<T> loader) {
48                 this.loader = loader;
49         }
50         
51                 
52         @Override
53         public Iterator<T> iterator() {
54                 return new DBIterator();
55         }
56
57         @Override
58         public int size() {
59                 return list.size();
60         }
61         
62         @Override
63         public boolean add(T element) {
64                 int index;
65                 
66                 index = Collections.binarySearch(list, element);
67                 if (index >= 0) {
68                         // List might contain the element
69                         if (list.contains(element)) {
70                                 return false;
71                         }
72                 } else {
73                         index = -(index+1);
74                 }
75                 list.add(index,element);
76                 fireChangeEvent();
77                 return true;
78         }
79         
80         
81         /**
82          * Get the element with the specified index.
83          * @param index the index to retrieve.
84          * @return              the element at the index.
85          */
86         public T get(int index) {
87                 return list.get(index);
88         }
89
90         /**
91          * Return the index of the given <code>Motor</code>, or -1 if not in the database.
92          * 
93          * @param m   the motor
94          * @return        the index of the motor
95          */
96         public int indexOf(T m) {
97                 return list.indexOf(m);
98         }
99         
100
101         @Override
102         public void addChangeListener(ChangeListener listener) {
103                 listenerList .add(ChangeListener.class, listener);
104         }
105
106
107         @Override
108         public void removeChangeListener(ChangeListener listener) {
109                 listenerList .remove(ChangeListener.class, listener);
110         }
111
112         
113         protected void fireChangeEvent() {
114                 Object[] listeners = listenerList.getListenerList();
115                 ChangeEvent e = null;
116                 for (int i = listeners.length-2; i>=0; i-=2) {
117                         if (listeners[i]==ChangeListener.class) {
118                                 // Lazily create the event:
119                                 if (e == null)
120                                         e = new ChangeEvent(this);
121                                 ((ChangeListener)listeners[i+1]).stateChanged(e);
122                         }
123                 }
124         }
125         
126
127         
128         ////////  Directory loading
129         
130         
131         
132         /**
133          * Load all files in a directory to the motor database.  Only files with file
134          * names matching the given pattern (as matched by <code>String.matches(String)</code>)
135          * are processed.
136          * 
137          * @param dir                   the directory to read.
138          * @param pattern               the pattern to match the file names to.
139          * @throws IOException  if an IO error occurs when reading the JAR archive
140          *                                              (errors reading individual files are printed to stderr).
141          */
142         public void loadDirectory(File dir, final String pattern) throws IOException {
143                 if (loader == null) {
144                         throw new IllegalStateException("no file loader set");
145                 }
146                 
147                 File[] files = dir.listFiles(new FilenameFilter() {
148                         @Override
149                         public boolean accept(File dir, String name) {
150                                 return name.matches(pattern);
151                         }
152                 });
153                 if (files == null) {
154                         throw new IOException("not a directory: "+dir);
155                 }
156                 for (File file: files) {
157                         try {
158                                 this.addAll(loader.load(new FileInputStream(file), file.getName()));
159                         } catch (IOException e) {
160                                 System.err.println("Error loading file "+file+": " + e.getMessage());
161                         }
162                 }
163         }
164         
165         
166         /**
167          * Read all files in a directory contained in the JAR file that this class belongs to.
168          * Only files whose names match the given pattern (as matched by
169          * <code>String.matches(String)</code>) will be read.
170          * 
171          * @param dir                   the directory within the JAR archive to read.
172          * @param pattern               the pattern to match the file names to.
173          * @throws IOException  if an IO error occurs when reading the JAR archive
174          *                                              (errors reading individual files are printed to stderr).
175          */
176         public void loadJarDirectory(String dir, String pattern) throws IOException {
177                 
178                 // Process directory and extension
179                 if (!dir.endsWith("/")) {
180                         dir += "/";
181                 }
182                 
183                 // Find and open the jar file this class is contained in
184                 File file = JarUtil.getCurrentJarFile();
185                 JarFile jarFile = new JarFile(file);
186                 
187                 try {
188
189                         // Loop through JAR entries searching for files to load
190                         Enumeration<JarEntry> entries = jarFile.entries();
191                         while (entries.hasMoreElements()) {
192                                 JarEntry entry = entries.nextElement();
193                                 String name = entry.getName();
194                                 if (name.startsWith(dir) && name.matches(pattern)) {
195                                         try {
196                                                 InputStream stream = jarFile.getInputStream(entry);
197                                                 this.addAll(loader.load(stream, name));
198                                         } catch (IOException e) {
199                                                 System.err.println("Error loading file " + file + ": "
200                                                                 + e.getMessage());
201                                         }
202                                 }
203                         }
204
205                 } finally {
206                         jarFile.close();
207                 }
208         }
209         
210         
211         
212         public void load(File file) throws IOException {
213                 if (loader == null) {
214                         throw new IllegalStateException("no file loader set");
215                 }
216                 this.addAll(loader.load(new FileInputStream(file), file.getName()));
217         }
218
219         
220         
221         /**
222          * Iterator class implementation that fires changes if remove() is called.
223          */
224         private class DBIterator implements Iterator<T> {
225                 private Iterator<T> iterator = list.iterator();
226                 
227                 @Override
228                 public boolean hasNext() {
229                         return iterator.hasNext();
230                 }
231
232                 @Override
233                 public T next() {
234                         return iterator.next();
235                 }
236
237                 @Override
238                 public void remove() {
239                         iterator.remove();
240                         fireChangeEvent();
241                 }
242         }
243 }