bug fix + more logging
[debian/openrocket] / src / net / sf / openrocket / gui / adaptors / IntegerModel.java
1 package net.sf.openrocket.gui.adaptors;
2
3 import java.lang.reflect.InvocationTargetException;
4 import java.lang.reflect.Method;
5 import java.util.ArrayList;
6
7 import javax.swing.SpinnerModel;
8 import javax.swing.SpinnerNumberModel;
9 import javax.swing.event.ChangeEvent;
10 import javax.swing.event.ChangeListener;
11
12 import net.sf.openrocket.logging.LogHelper;
13 import net.sf.openrocket.startup.Application;
14 import net.sf.openrocket.util.BugException;
15 import net.sf.openrocket.util.ChangeSource;
16 import net.sf.openrocket.util.Reflection;
17
18
19 public class IntegerModel implements ChangeListener {
20         private static final LogHelper log = Application.getLogger();
21         
22         
23         //////////// JSpinner Model ////////////
24         
25         private class IntegerSpinnerModel extends SpinnerNumberModel {
26                 @Override
27                 public Object getValue() {
28                         return IntegerModel.this.getValue();
29                 }
30                 
31                 @Override
32                 public void setValue(Object value) {
33                         if (firing > 0) {
34                                 // Ignore, if called when model is sending events
35                                 log.verbose("Ignoring call to SpinnerModel setValue for " + IntegerModel.this.toString() +
36                                                 " value=" + value + ", currently firing events");
37                                 return;
38                                 
39                         }
40                         Number num = (Number) value;
41                         int newValue = num.intValue();
42                         log.user("SpinnerModel setValue called for " + IntegerModel.this.toString() + " newValue=" + newValue);
43                         IntegerModel.this.setValue(newValue);
44                 }
45                 
46                 @Override
47                 public Object getNextValue() {
48                         int d = IntegerModel.this.getValue();
49                         if (d >= maxValue)
50                                 return null;
51                         return (d + 1);
52                 }
53                 
54                 @Override
55                 public Object getPreviousValue() {
56                         int d = IntegerModel.this.getValue();
57                         if (d <= minValue)
58                                 return null;
59                         return (d - 1);
60                 }
61                 
62                 @Override
63                 public void addChangeListener(ChangeListener l) {
64                         IntegerModel.this.addChangeListener(l);
65                 }
66                 
67                 @Override
68                 public void removeChangeListener(ChangeListener l) {
69                         IntegerModel.this.removeChangeListener(l);
70                 }
71         }
72         
73         /**
74          * Returns a new SpinnerModel with the same base as the DoubleModel.
75          * The values given to the JSpinner are in the currently selected units.
76          * 
77          * @return  A compatibility layer for a SpinnerModel.
78          */
79         public SpinnerModel getSpinnerModel() {
80                 return new IntegerSpinnerModel();
81         }
82         
83         
84
85
86         ////////////  Main model  /////////////
87         
88         /*
89          * The main model handles all values in SI units, i.e. no conversion is made within the model.
90          */
91
92         private final ChangeSource source;
93         private final String valueName;
94         
95         private final Method getMethod;
96         private final Method setMethod;
97         
98         private final ArrayList<ChangeListener> listeners = new ArrayList<ChangeListener>();
99         
100         private final int minValue;
101         private final int maxValue;
102         
103         private String toString = null;
104         
105
106         private int firing = 0; //  >0 when model itself is sending events
107         
108
109         // Used to differentiate changes in valueName and other changes in the source:
110         private int lastValue = 0;
111         
112         
113
114         /**
115          * Generates a new DoubleModel that changes the values of the specified source.
116          * The double value is read and written using the methods "get"/"set" + valueName.
117          *  
118          * @param source Component whose parameter to use.
119          * @param valueName Name of methods used to get/set the parameter.
120          * @param min Minimum value allowed (in SI units)
121          * @param max Maximum value allowed (in SI units)
122          */
123         public IntegerModel(ChangeSource source, String valueName, int min, int max) {
124                 this.source = source;
125                 this.valueName = valueName;
126                 
127                 this.minValue = min;
128                 this.maxValue = max;
129                 
130                 try {
131                         getMethod = source.getClass().getMethod("get" + valueName);
132                         setMethod = source.getClass().getMethod("set" + valueName, int.class);
133                 } catch (NoSuchMethodException e) {
134                         throw new IllegalArgumentException("get/set methods for value '" + valueName +
135                                         "' not present in class " + source.getClass().getCanonicalName());
136                 }
137         }
138         
139         public IntegerModel(ChangeSource source, String valueName, int min) {
140                 this(source, valueName, min, Integer.MAX_VALUE);
141         }
142         
143         public IntegerModel(ChangeSource source, String valueName) {
144                 this(source, valueName, Integer.MIN_VALUE, Integer.MAX_VALUE);
145         }
146         
147         
148
149
150         /**
151          * Returns the value of the variable.
152          */
153         public int getValue() {
154                 try {
155                         return (Integer) getMethod.invoke(source);
156                 } catch (IllegalArgumentException e) {
157                         throw new BugException(e);
158                 } catch (IllegalAccessException e) {
159                         throw new BugException(e);
160                 } catch (InvocationTargetException e) {
161                         throw Reflection.handleWrappedException(e);
162                 }
163         }
164         
165         /**
166          * Sets the value of the variable.
167          */
168         public void setValue(int v) {
169                 log.debug("Setting value " + v + " for " + this);
170                 try {
171                         setMethod.invoke(source, v);
172                 } catch (IllegalArgumentException e) {
173                         throw new BugException(e);
174                 } catch (IllegalAccessException e) {
175                         throw new BugException(e);
176                 } catch (InvocationTargetException e) {
177                         throw Reflection.handleWrappedException(e);
178                 }
179         }
180         
181         
182         /**
183          * Add a listener to the model.  Adds the model as a listener to the Component if this
184          * is the first listener.
185          * @param l Listener to add.
186          */
187         public void addChangeListener(ChangeListener l) {
188                 if (listeners.isEmpty()) {
189                         source.addChangeListener(this);
190                         lastValue = getValue();
191                 }
192                 
193                 listeners.add(l);
194                 log.verbose(this + " adding listener (total " + listeners.size() + "): " + l);
195         }
196         
197         /**
198          * Remove a listener from the model.  Removes the model from being a listener to the Component
199          * if this was the last listener of the model.
200          * @param l Listener to remove.
201          */
202         public void removeChangeListener(ChangeListener l) {
203                 listeners.remove(l);
204                 if (listeners.isEmpty()) {
205                         source.removeChangeListener(this);
206                 }
207                 log.verbose(this + " removing listener (total " + listeners.size() + "): " + l);
208         }
209         
210         
211         @Override
212         protected void finalize() throws Throwable {
213                 super.finalize();
214                 if (!listeners.isEmpty()) {
215                         log.warn(this + " being garbage-collected while having listeners " + listeners);
216                 }
217         };
218         
219         
220         public void fireStateChanged() {
221                 Object[] l = listeners.toArray();
222                 ChangeEvent event = new ChangeEvent(this);
223                 firing++;
224                 for (int i = 0; i < l.length; i++)
225                         ((ChangeListener) l[i]).stateChanged(event);
226                 firing--;
227         }
228         
229         /**
230          * Called when the source changes.  Checks whether the modeled value has changed, and if
231          * it has, updates lastValue and generates ChangeEvents for all listeners of the model.
232          */
233         public void stateChanged(ChangeEvent e) {
234                 int v = getValue();
235                 if (lastValue == v)
236                         return;
237                 lastValue = v;
238                 fireStateChanged();
239         }
240         
241         /**
242          * Explain the DoubleModel as a String.
243          */
244         @Override
245         public String toString() {
246                 if (toString == null) {
247                         toString = "IntegerModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
248                 }
249                 return toString;
250         }
251         
252 }