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