1 package net.sf.openrocket.database;
4 import java.io.FileInputStream;
5 import java.io.FilenameFilter;
6 import java.io.IOException;
7 import java.io.InputStream;
9 import java.net.URISyntaxException;
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;
21 import javax.swing.event.ChangeEvent;
22 import javax.swing.event.ChangeListener;
23 import javax.swing.event.EventListenerList;
25 import net.sf.openrocket.file.Loader;
26 import net.sf.openrocket.util.ChangeSource;
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.
35 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
38 // TODO: HIGH: Database saving
39 public class Database<T extends Comparable<T>> extends AbstractSet<T> implements ChangeSource {
41 private final List<T> list = new ArrayList<T>();
42 private final EventListenerList listenerList = new EventListenerList();
43 private final Loader<T> loader;
50 public Database(Loader<T> loader) {
56 public Iterator<T> iterator() {
57 return new DBIterator();
66 public boolean add(T element) {
69 index = Collections.binarySearch(list, element);
71 // List might contain the element
72 if (list.contains(element)) {
78 list.add(index,element);
85 * Get the element with the specified index.
86 * @param index the index to retrieve.
87 * @return the element at the index.
89 public T get(int index) {
90 return list.get(index);
94 * Return the index of the given <code>Motor</code>, or -1 if not in the database.
97 * @return the index of the motor
99 public int indexOf(T m) {
100 return list.indexOf(m);
105 public void addChangeListener(ChangeListener listener) {
106 listenerList .add(ChangeListener.class, listener);
111 public void removeChangeListener(ChangeListener listener) {
112 listenerList .remove(ChangeListener.class, listener);
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:
123 e = new ChangeEvent(this);
124 ((ChangeListener)listeners[i+1]).stateChanged(e);
131 //////// Directory loading
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>)
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).
145 public void loadDirectory(File dir, final String pattern) throws IOException {
146 if (loader == null) {
147 throw new IllegalStateException("no file loader set");
150 File[] files = dir.listFiles(new FilenameFilter() {
152 public boolean accept(File dir, String name) {
153 return name.matches(pattern);
157 throw new IOException("not a directory: "+dir);
159 for (File file: files) {
161 this.addAll(loader.load(new FileInputStream(file), file.getName()));
162 } catch (IOException e) {
163 System.err.println("Error loading file "+file+": " + e.getMessage());
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.
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).
179 public void loadJarDirectory(String dir, String pattern) throws IOException {
181 // Process directory and extension
182 if (!dir.endsWith("/")) {
186 // Find the jar file this class is contained in and open it
188 CodeSource codeSource = Database.class.getProtectionDomain().getCodeSource();
189 if (codeSource != null)
190 jarUrl = codeSource.getLocation();
192 if (jarUrl == null) {
193 throw new IOException("Could not find containing JAR file.");
195 File file = urlToFile(jarUrl);
196 JarFile jarFile = new JarFile(file);
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)) {
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 + ": "
222 static File urlToFile(URL url) {
226 } catch (URISyntaxException e) {
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);
234 return new File(uri);
239 public void load(File file) throws IOException {
240 if (loader == null) {
241 throw new IllegalStateException("no file loader set");
243 this.addAll(loader.load(new FileInputStream(file), file.getName()));
249 * Iterator class implementation that fires changes if remove() is called.
251 private class DBIterator implements Iterator<T> {
252 private Iterator<T> iterator = list.iterator();
255 public boolean hasNext() {
256 return iterator.hasNext();
261 return iterator.next();
265 public void remove() {