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;
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;
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;
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.
29 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
31 public class Database<T extends Comparable<T>> extends AbstractSet<T> {
32 private static final LogHelper log = Application.getLogger();
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;
44 public Database(Loader<T> loader) {
51 public Iterator<T> iterator() {
52 return new DBIterator();
61 public boolean add(T element) {
64 index = Collections.binarySearch(list, element);
66 // List might contain the element
67 if (list.contains(element)) {
73 list.add(index, element);
74 fireAddEvent(element);
80 * Get the element with the specified index.
81 * @param index the index to retrieve.
82 * @return the element at the index.
84 public T get(int index) {
85 return list.get(index);
89 * Return the index of the given <code>Motor</code>, or -1 if not in the database.
92 * @return the index of the motor
94 public int indexOf(T m) {
95 return list.indexOf(m);
99 public void addDatabaseListener(DatabaseListener<T> listener) {
100 listeners.add(listener);
103 public void removeChangeListener(DatabaseListener<T> listener) {
104 listeners.remove(listener);
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);
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);
127 //////// Directory loading
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>)
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).
141 public void loadDirectory(File dir, final String pattern) throws IOException {
142 if (loader == null) {
143 throw new IllegalStateException("no file loader set");
146 File[] files = dir.listFiles(new FilenameFilter() {
148 public boolean accept(File directory, String name) {
149 return name.matches(pattern);
153 throw new IOException("not a directory: " + dir);
155 for (File file : files) {
157 this.addAll(loader.load(new FileInputStream(file), file.getName()));
158 } catch (IOException e) {
159 log.warn("Error loading file " + file + ": " + e.getMessage(), e);
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.
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).
175 public void loadJarDirectory(String dir, String pattern) throws IOException {
177 // Process directory and extension
178 if (!dir.endsWith("/")) {
182 // Find and open the jar file this class is contained in
183 File file = JarUtil.getCurrentJarFile();
184 JarFile jarFile = new JarFile(file);
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)) {
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);
210 public void load(File file) throws IOException {
211 if (loader == null) {
212 throw new IllegalStateException("no file loader set");
214 this.addAll(loader.load(new FileInputStream(file), file.getName()));
220 * Iterator class implementation that fires changes if remove() is called.
222 private class DBIterator implements Iterator<T> {
223 private Iterator<T> iterator = list.iterator();
224 private T current = null;
227 public boolean hasNext() {
228 return iterator.hasNext();
233 current = iterator.next();
238 public void remove() {
240 fireRemoveEvent(current);