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