lose embedded source jars from upstream branch
[debian/openrocket] / core / src / net / sf / openrocket / gui / adaptors / DoubleModel.java
1 package net.sf.openrocket.gui.adaptors;
2
3 import java.awt.event.ActionEvent;
4 import java.beans.PropertyChangeEvent;
5 import java.beans.PropertyChangeListener;
6 import java.lang.reflect.InvocationTargetException;
7 import java.lang.reflect.Method;
8 import java.util.ArrayList;
9 import java.util.EventListener;
10 import java.util.EventObject;
11
12 import javax.swing.AbstractAction;
13 import javax.swing.Action;
14 import javax.swing.BoundedRangeModel;
15 import javax.swing.SpinnerModel;
16 import javax.swing.SpinnerNumberModel;
17 import javax.swing.event.ChangeEvent;
18 import javax.swing.event.ChangeListener;
19
20 import net.sf.openrocket.logging.LogHelper;
21 import net.sf.openrocket.startup.Application;
22 import net.sf.openrocket.unit.Unit;
23 import net.sf.openrocket.unit.UnitGroup;
24 import net.sf.openrocket.util.BugException;
25 import net.sf.openrocket.util.ChangeSource;
26 import net.sf.openrocket.util.Invalidatable;
27 import net.sf.openrocket.util.Invalidator;
28 import net.sf.openrocket.util.MathUtil;
29 import net.sf.openrocket.util.MemoryManagement;
30 import net.sf.openrocket.util.Reflection;
31 import net.sf.openrocket.util.StateChangeListener;
32
33
34 /**
35  * A model connector that can read and modify any value of any ChangeSource that
36  * has the appropriate get/set methods defined.  
37  * 
38  * The variable is defined in the constructor by providing the variable name as a string
39  * (e.g. "Radius" -> getRadius()/setRadius()).  Additional scaling may be applied, e.g. a 
40  * DoubleModel for the diameter can be defined by the variable "Radius" and a multiplier of 2.
41  * 
42  * Sub-models suitable for JSpinners and other components are available from the appropriate
43  * methods.
44  * 
45  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
46  */
47
48 public class DoubleModel implements StateChangeListener, ChangeSource, Invalidatable {
49         private static final LogHelper log = Application.getLogger();
50         
51
52         public static final DoubleModel ZERO = new DoubleModel(0);
53         
54         //////////// JSpinner Model ////////////
55         
56         /**
57          * Model suitable for JSpinner using JSpinner.NumberEditor.  It extends SpinnerNumberModel
58          * to be compatible with the NumberEditor, but only has the necessary methods defined.
59          */
60         private class ValueSpinnerModel extends SpinnerNumberModel implements Invalidatable {
61                 
62                 @Override
63                 public Object getValue() {
64                         return currentUnit.toUnit(DoubleModel.this.getValue());
65                 }
66                 
67                 @Override
68                 public void setValue(Object value) {
69                         if (firing > 0) {
70                                 // Ignore, if called when model is sending events
71                                 log.verbose("Ignoring call to SpinnerModel setValue for " + DoubleModel.this.toString() +
72                                                 " value=" + value + ", currently firing events");
73                                 return;
74                         }
75                         Number num = (Number) value;
76                         double newValue = num.doubleValue();
77                         double converted = currentUnit.fromUnit(newValue);
78                         
79                         log.user("SpinnerModel setValue called for " + DoubleModel.this.toString() + " newValue=" + newValue +
80                                         " converted=" + converted);
81                         DoubleModel.this.setValue(converted);
82                         
83                 }
84                 
85                 @Override
86                 public Object getNextValue() {
87                         double d = currentUnit.toUnit(DoubleModel.this.getValue());
88                         double max = currentUnit.toUnit(maxValue);
89                         if (MathUtil.equals(d, max))
90                                 return null;
91                         d = currentUnit.getNextValue(d);
92                         if (d > max)
93                                 d = max;
94                         return d;
95                 }
96                 
97                 @Override
98                 public Object getPreviousValue() {
99                         double d = currentUnit.toUnit(DoubleModel.this.getValue());
100                         double min = currentUnit.toUnit(minValue);
101                         if (MathUtil.equals(d, min))
102                                 return null;
103                         d = currentUnit.getPreviousValue(d);
104                         if (d < min)
105                                 d = min;
106                         return d;
107                 }
108                 
109                 
110                 @Override
111                 public Comparable<Double> getMinimum() {
112                         return currentUnit.toUnit(minValue);
113                 }
114                 
115                 @Override
116                 public Comparable<Double> getMaximum() {
117                         return currentUnit.toUnit(maxValue);
118                 }
119                 
120                 
121                 @Override
122                 public void addChangeListener(ChangeListener l) {
123                         DoubleModel.this.addChangeListener(l);
124                 }
125                 
126                 @Override
127                 public void removeChangeListener(ChangeListener l) {
128                         DoubleModel.this.removeChangeListener(l);
129                 }
130                 
131                 @Override
132                 public void invalidate() {
133                         DoubleModel.this.invalidate();
134                 }
135         }
136         
137         /**
138          * Returns a new SpinnerModel with the same base as the DoubleModel.
139          * The values given to the JSpinner are in the currently selected units.
140          * 
141          * @return  A compatibility layer for a SpinnerModel.
142          */
143         public SpinnerModel getSpinnerModel() {
144                 return new ValueSpinnerModel();
145         }
146         
147         
148
149
150
151         ////////////  JSlider model  ////////////
152         
153         private class ValueSliderModel implements BoundedRangeModel, StateChangeListener, Invalidatable {
154                 private static final int MAX = 1000;
155                 
156                 /*
157                  * Use linear scale  value = linear1 * x + linear0  when x < linearPosition
158                  * Use quadratic scale  value = quad2 * x^2 + quad1 * x + quad0  otherwise
159                  */
160
161                 // Linear in range x <= linearPosition
162                 private final double linearPosition;
163                 
164                 // May be changing DoubleModels when using linear model
165                 private final DoubleModel min, mid, max;
166                 
167                 // Linear multiplier and constant
168                 //private final double linear1;
169                 //private final double linear0;
170                 
171                 // Non-linear multiplier, exponent and constant
172                 private final double quad2, quad1, quad0;
173                 
174                 
175
176                 public ValueSliderModel(DoubleModel min, DoubleModel max) {
177                         linearPosition = 1.0;
178                         
179                         this.min = min;
180                         this.mid = max; // Never use exponential scale
181                         this.max = max;
182                         
183                         min.addChangeListener(this);
184                         max.addChangeListener(this);
185                         
186                         quad2 = quad1 = quad0 = 0; // Not used
187                 }
188                 
189                 
190
191                 /**
192                  * Generate a linear model from min to max.
193                  */
194                 public ValueSliderModel(double min, double max) {
195                         linearPosition = 1.0;
196                         
197                         this.min = new DoubleModel(min);
198                         this.mid = new DoubleModel(max); // Never use exponential scale
199                         this.max = new DoubleModel(max);
200                         
201                         quad2 = quad1 = quad0 = 0; // Not used
202                 }
203                 
204                 public ValueSliderModel(double min, double mid, double max) {
205                         this(min, 0.5, mid, max);
206                 }
207                 
208                 /*
209                  * v(x)  = mul * x^exp + add
210                  * 
211                  * v(pos)  = mul * pos^exp + add = mid
212                  * v(1)    = mul + add = max
213                  * v'(pos) = mul*exp * pos^(exp-1) = linearMul
214                  */
215                 public ValueSliderModel(double min, double pos, double mid, double max) {
216                         this.min = new DoubleModel(min);
217                         this.mid = new DoubleModel(mid);
218                         this.max = new DoubleModel(max);
219                         
220
221                         linearPosition = pos;
222                         //linear0 = min;
223                         //linear1 = (mid-min)/pos;
224                         
225                         if (!(min < mid && mid <= max && 0 < pos && pos < 1)) {
226                                 throw new IllegalArgumentException("Bad arguments for ValueSliderModel " +
227                                                 "min=" + min + " mid=" + mid + " max=" + max + " pos=" + pos);
228                         }
229                         
230                         /*
231                          * quad2..0 are calculated such that
232                          *   f(pos)  = mid      - continuity
233                          *   f(1)    = max      - end point
234                          *   f'(pos) = linear1  - continuity of derivative
235                          */
236
237                         double delta = (mid - min) / pos;
238                         quad2 = (max - mid - delta + delta * pos) / pow2(pos - 1);
239                         quad1 = (delta + 2 * (mid - max) * pos - delta * pos * pos) / pow2(pos - 1);
240                         quad0 = (mid - (2 * mid + delta) * pos + (max + delta) * pos * pos) / pow2(pos - 1);
241                         
242                 }
243                 
244                 private double pow2(double x) {
245                         return x * x;
246                 }
247                 
248                 @Override
249                 public int getValue() {
250                         double value = DoubleModel.this.getValue();
251                         if (value <= min.getValue())
252                                 return 0;
253                         if (value >= max.getValue())
254                                 return MAX;
255                         
256                         double x;
257                         if (value <= mid.getValue()) {
258                                 // Use linear scale
259                                 //linear0 = min;
260                                 //linear1 = (mid-min)/pos;
261                                 
262                                 x = (value - min.getValue()) * linearPosition / (mid.getValue() - min.getValue());
263                         } else {
264                                 // Use quadratic scale
265                                 // Further solution of the quadratic equation
266                                 //   a*x^2 + b*x + c-value == 0
267                                 x = (MathUtil.safeSqrt(quad1 * quad1 - 4 * quad2 * (quad0 - value)) - quad1) / (2 * quad2);
268                         }
269                         return (int) (x * MAX);
270                 }
271                 
272                 
273                 @Override
274                 public void setValue(int newValue) {
275                         if (firing > 0) {
276                                 // Ignore loops
277                                 log.verbose("Ignoring call to SliderModel setValue for " + DoubleModel.this.toString() +
278                                                 " value=" + newValue + ", currently firing events");
279                                 return;
280                         }
281                         
282                         double x = (double) newValue / MAX;
283                         double scaledValue;
284                         
285                         if (x <= linearPosition) {
286                                 // Use linear scale
287                                 //linear0 = min;
288                                 //linear1 = (mid-min)/pos;
289                                 
290                                 scaledValue = (mid.getValue() - min.getValue()) / linearPosition * x + min.getValue();
291                         } else {
292                                 // Use quadratic scale
293                                 scaledValue = quad2 * x * x + quad1 * x + quad0;
294                         }
295                         
296                         double converted = currentUnit.fromUnit(currentUnit.round(currentUnit.toUnit(scaledValue)));
297                         log.user("SliderModel setValue called for " + DoubleModel.this.toString() + " newValue=" + newValue +
298                                         " scaledValue=" + scaledValue + " converted=" + converted);
299                         DoubleModel.this.setValue(converted);
300                 }
301                 
302                 
303                 // Static get-methods
304                 private boolean isAdjusting;
305                 
306                 @Override
307                 public int getExtent() {
308                         return 0;
309                 }
310                 
311                 @Override
312                 public int getMaximum() {
313                         return MAX;
314                 }
315                 
316                 @Override
317                 public int getMinimum() {
318                         return 0;
319                 }
320                 
321                 @Override
322                 public boolean getValueIsAdjusting() {
323                         return isAdjusting;
324                 }
325                 
326                 // Ignore set-values
327                 @Override
328                 public void setExtent(int newExtent) {
329                 }
330                 
331                 @Override
332                 public void setMaximum(int newMaximum) {
333                 }
334                 
335                 @Override
336                 public void setMinimum(int newMinimum) {
337                 }
338                 
339                 @Override
340                 public void setValueIsAdjusting(boolean b) {
341                         isAdjusting = b;
342                 }
343                 
344                 @Override
345                 public void setRangeProperties(int value, int extent, int min, int max, boolean adjusting) {
346                         setValueIsAdjusting(adjusting);
347                         setValue(value);
348                 }
349                 
350                 // Pass change listeners to the underlying model
351                 @Override
352                 public void addChangeListener(ChangeListener l) {
353                         DoubleModel.this.addChangeListener(l);
354                 }
355                 
356                 @Override
357                 public void removeChangeListener(ChangeListener l) {
358                         DoubleModel.this.removeChangeListener(l);
359                 }
360                 
361                 @Override
362                 public void invalidate() {
363                         DoubleModel.this.invalidate();
364                 }
365                 
366                 @Override
367                 public void stateChanged(EventObject e) {
368                         // Min or max range has changed.
369                         // Fire if not already firing
370                         if (firing == 0)
371                                 fireStateChanged();
372                 }
373         }
374         
375         
376         public BoundedRangeModel getSliderModel(DoubleModel min, DoubleModel max) {
377                 return new ValueSliderModel(min, max);
378         }
379         
380         public BoundedRangeModel getSliderModel(double min, double max) {
381                 return new ValueSliderModel(min, max);
382         }
383         
384         public BoundedRangeModel getSliderModel(double min, double mid, double max) {
385                 return new ValueSliderModel(min, mid, max);
386         }
387         
388         public BoundedRangeModel getSliderModel(double min, double pos, double mid, double max) {
389                 return new ValueSliderModel(min, pos, mid, max);
390         }
391         
392         
393
394
395
396         ////////////  Action model  ////////////
397         
398         private class AutomaticActionModel extends AbstractAction implements StateChangeListener, Invalidatable {
399                 private boolean oldValue = false;
400                 
401                 public AutomaticActionModel() {
402                         oldValue = isAutomatic();
403                         addChangeListener(this);
404                 }
405                 
406                 
407                 @Override
408                 public boolean isEnabled() {
409                         return isAutomaticAvailable();
410                 }
411                 
412                 @Override
413                 public Object getValue(String key) {
414                         if (key.equals(Action.SELECTED_KEY)) {
415                                 oldValue = isAutomatic();
416                                 return oldValue;
417                         }
418                         return super.getValue(key);
419                 }
420                 
421                 @Override
422                 public void putValue(String key, Object value) {
423                         if (firing > 0) {
424                                 log.verbose("Ignoring call to ActionModel putValue for " + DoubleModel.this.toString() +
425                                                 " key=" + key + " value=" + value + ", currently firing events");
426                                 return;
427                         }
428                         if (key.equals(Action.SELECTED_KEY) && (value instanceof Boolean)) {
429                                 log.user("ActionModel putValue called for " + DoubleModel.this.toString() +
430                                                 " key=" + key + " value=" + value);
431                                 oldValue = (Boolean) value;
432                                 setAutomatic((Boolean) value);
433                         } else {
434                                 log.debug("Passing ActionModel putValue call to supermethod for " + DoubleModel.this.toString() +
435                                                 " key=" + key + " value=" + value);
436                                 super.putValue(key, value);
437                         }
438                 }
439                 
440                 // Implement a wrapper to the ChangeListeners
441                 ArrayList<PropertyChangeListener> propertyChangeListeners =
442                                 new ArrayList<PropertyChangeListener>();
443                 
444                 @Override
445                 public void addPropertyChangeListener(PropertyChangeListener listener) {
446                         propertyChangeListeners.add(listener);
447                         DoubleModel.this.addChangeListener(this);
448                 }
449                 
450                 @Override
451                 public void removePropertyChangeListener(PropertyChangeListener listener) {
452                         propertyChangeListeners.remove(listener);
453                         if (propertyChangeListeners.isEmpty())
454                                 DoubleModel.this.removeChangeListener(this);
455                 }
456                 
457                 // If the value has changed, generate an event to the listeners
458                 @Override
459                 public void stateChanged(EventObject e) {
460                         boolean newValue = isAutomatic();
461                         if (oldValue == newValue)
462                                 return;
463                         PropertyChangeEvent event = new PropertyChangeEvent(this, Action.SELECTED_KEY,
464                                         oldValue, newValue);
465                         oldValue = newValue;
466                         Object[] l = propertyChangeListeners.toArray();
467                         for (int i = 0; i < l.length; i++) {
468                                 ((PropertyChangeListener) l[i]).propertyChange(event);
469                         }
470                 }
471                 
472                 @Override
473                 public void actionPerformed(ActionEvent e) {
474                         // Setting performed in putValue
475                 }
476                 
477                 @Override
478                 public void invalidate() {
479                         DoubleModel.this.invalidate();
480                 }
481         }
482         
483         /**
484          * Returns a new Action corresponding to the changes of the automatic setting
485          * property of the value model.  This may be used directly with e.g. check buttons.
486          * 
487          * @return  A compatibility layer for an Action.
488          */
489         public Action getAutomaticAction() {
490                 return new AutomaticActionModel();
491         }
492         
493         
494
495
496
497         ////////////  Main model  /////////////
498         
499         /*
500          * The main model handles all values in SI units, i.e. no conversion is made within the model.
501          */
502
503         private final ChangeSource source;
504         private final String valueName;
505         private final double multiplier;
506         
507         private final Method getMethod;
508         private final Method setMethod;
509         
510         private final Method getAutoMethod;
511         private final Method setAutoMethod;
512         
513         private final ArrayList<EventListener> listeners = new ArrayList<EventListener>();
514         
515         private final UnitGroup units;
516         private Unit currentUnit;
517         
518         private final double minValue;
519         private final double maxValue;
520         
521         private String toString = null;
522         
523
524         private int firing = 0; //  >0 when model itself is sending events
525         
526
527         // Used to differentiate changes in valueName and other changes in the component:
528         private double lastValue = 0;
529         private boolean lastAutomatic = false;
530         
531         private Invalidator invalidator = new Invalidator(this);
532         
533         
534         /**
535          * Generate a DoubleModel that contains an internal double value.
536          * 
537          * @param value         the initial value.
538          */
539         public DoubleModel(double value) {
540                 this(value, UnitGroup.UNITS_NONE, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
541         }
542         
543         /**
544          * Generate a DoubleModel that contains an internal double value.
545          * 
546          * @param value         the initial value.
547          * @param unit          the unit for the value.
548          */
549         public DoubleModel(double value, UnitGroup unit) {
550                 this(value, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
551         }
552         
553         /**
554          * Generate a DoubleModel that contains an internal double value.
555          * 
556          * @param value         the initial value.
557          * @param unit          the unit for the value.
558          * @param min           minimum value.
559          */
560         public DoubleModel(double value, UnitGroup unit, double min) {
561                 this(value, unit, min, Double.POSITIVE_INFINITY);
562         }
563         
564         /**
565          * Generate a DoubleModel that contains an internal double value.
566          * 
567          * @param value         the initial value.
568          * @param unit          the unit for the value.
569          * @param min           minimum value.
570          * @param max           maximum value.
571          */
572         public DoubleModel(double value, UnitGroup unit, double min, double max) {
573                 this.lastValue = value;
574                 this.minValue = min;
575                 this.maxValue = max;
576                 
577                 source = null;
578                 valueName = "Constant value";
579                 multiplier = 1;
580                 
581                 getMethod = setMethod = null;
582                 getAutoMethod = setAutoMethod = null;
583                 units = unit;
584                 currentUnit = units.getDefaultUnit();
585         }
586         
587         
588         /**
589          * Generates a new DoubleModel that changes the values of the specified component.
590          * The double value is read and written using the methods "get"/"set" + valueName.
591          *  
592          * @param source Component whose parameter to use.
593          * @param valueName Name of methods used to get/set the parameter.
594          * @param multiplier Value shown by the model is the value from component.getXXX * multiplier
595          * @param min Minimum value allowed (in SI units)
596          * @param max Maximum value allowed (in SI units)
597          */
598         public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit,
599                         double min, double max) {
600                 this.source = source;
601                 this.valueName = valueName;
602                 this.multiplier = multiplier;
603                 
604                 this.units = unit;
605                 currentUnit = units.getDefaultUnit();
606                 
607                 this.minValue = min;
608                 this.maxValue = max;
609                 
610                 try {
611                         getMethod = source.getClass().getMethod("get" + valueName);
612                 } catch (NoSuchMethodException e) {
613                         throw new IllegalArgumentException("get method for value '" + valueName +
614                                         "' not present in class " + source.getClass().getCanonicalName());
615                 }
616                 
617                 Method s = null;
618                 try {
619                         s = source.getClass().getMethod("set" + valueName, double.class);
620                 } catch (NoSuchMethodException e1) {
621                 } // Ignore
622                 setMethod = s;
623                 
624                 // Automatic selection methods
625                 
626                 Method set = null, get = null;
627                 
628                 try {
629                         get = source.getClass().getMethod("is" + valueName + "Automatic");
630                         set = source.getClass().getMethod("set" + valueName + "Automatic", boolean.class);
631                 } catch (NoSuchMethodException e) {
632                 } // ignore
633                 
634                 if (set != null && get != null) {
635                         getAutoMethod = get;
636                         setAutoMethod = set;
637                 } else {
638                         getAutoMethod = null;
639                         setAutoMethod = null;
640                 }
641                 
642         }
643         
644         public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit,
645                         double min) {
646                 this(source, valueName, multiplier, unit, min, Double.POSITIVE_INFINITY);
647         }
648         
649         public DoubleModel(ChangeSource source, String valueName, double multiplier, UnitGroup unit) {
650                 this(source, valueName, multiplier, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
651         }
652         
653         public DoubleModel(ChangeSource source, String valueName, UnitGroup unit,
654                         double min, double max) {
655                 this(source, valueName, 1.0, unit, min, max);
656         }
657         
658         public DoubleModel(ChangeSource source, String valueName, UnitGroup unit, double min) {
659                 this(source, valueName, 1.0, unit, min, Double.POSITIVE_INFINITY);
660         }
661         
662         public DoubleModel(ChangeSource source, String valueName, UnitGroup unit) {
663                 this(source, valueName, 1.0, unit, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
664         }
665         
666         public DoubleModel(ChangeSource source, String valueName) {
667                 this(source, valueName, 1.0, UnitGroup.UNITS_NONE,
668                                 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
669         }
670         
671         public DoubleModel(ChangeSource source, String valueName, double min) {
672                 this(source, valueName, 1.0, UnitGroup.UNITS_NONE, min, Double.POSITIVE_INFINITY);
673         }
674         
675         public DoubleModel(ChangeSource source, String valueName, double min, double max) {
676                 this(source, valueName, 1.0, UnitGroup.UNITS_NONE, min, max);
677         }
678         
679         
680
681         /**
682          * Returns the value of the variable (in SI units).
683          */
684         public double getValue() {
685                 if (getMethod == null) // Constant value
686                         return lastValue;
687                 
688                 try {
689                         return (Double) getMethod.invoke(source) * multiplier;
690                 } catch (IllegalArgumentException e) {
691                         throw new BugException("Unable to invoke getMethod of " + this, e);
692                 } catch (IllegalAccessException e) {
693                         throw new BugException("Unable to invoke getMethod of " + this, e);
694                 } catch (InvocationTargetException e) {
695                         throw Reflection.handleWrappedException(e);
696                 }
697         }
698         
699         /**
700          * Sets the value of the variable.
701          * @param v New value for parameter in SI units.
702          */
703         public void setValue(double v) {
704                 checkState(true);
705                 
706                 log.debug("Setting value " + v + " for " + this);
707                 if (setMethod == null) {
708                         if (getMethod != null) {
709                                 throw new BugException("setMethod not available for variable '" + valueName +
710                                                 "' in class " + source.getClass().getCanonicalName());
711                         }
712                         lastValue = v;
713                         fireStateChanged();
714                         return;
715                 }
716                 
717                 try {
718                         setMethod.invoke(source, v / multiplier);
719                 } catch (IllegalArgumentException e) {
720                         throw new BugException("Unable to invoke setMethod of " + this, e);
721                 } catch (IllegalAccessException e) {
722                         throw new BugException("Unable to invoke setMethod of " + this, e);
723                 } catch (InvocationTargetException e) {
724                         throw Reflection.handleWrappedException(e);
725                 }
726         }
727         
728         
729         /**
730          * Returns whether setting the value automatically is available.
731          */
732         public boolean isAutomaticAvailable() {
733                 return (getAutoMethod != null) && (setAutoMethod != null);
734         }
735         
736         /**
737          * Returns whether the value is currently being set automatically.
738          * Returns false if automatic setting is not available at all.
739          */
740         public boolean isAutomatic() {
741                 if (getAutoMethod == null)
742                         return false;
743                 
744                 try {
745                         return (Boolean) getAutoMethod.invoke(source);
746                 } catch (IllegalArgumentException e) {
747                         throw new BugException("Method call failed", e);
748                 } catch (IllegalAccessException e) {
749                         throw new BugException("Method call failed", e);
750                 } catch (InvocationTargetException e) {
751                         throw Reflection.handleWrappedException(e);
752                 }
753         }
754         
755         /**
756          * Sets whether the value should be set automatically.  Simply fires a
757          * state change event if automatic setting is not available.
758          */
759         public void setAutomatic(boolean auto) {
760                 checkState(true);
761                 
762                 if (setAutoMethod == null) {
763                         log.debug("Setting automatic to " + auto + " for " + this + ", automatic not available");
764                         fireStateChanged(); // in case something is out-of-sync
765                         return;
766                 }
767                 
768                 log.debug("Setting automatic to " + auto + " for " + this);
769                 lastAutomatic = auto;
770                 try {
771                         setAutoMethod.invoke(source, auto);
772                 } catch (IllegalArgumentException e) {
773                         throw new BugException(e);
774                 } catch (IllegalAccessException e) {
775                         throw new BugException(e);
776                 } catch (InvocationTargetException e) {
777                         throw Reflection.handleWrappedException(e);
778                 }
779         }
780         
781         
782         /**
783          * Returns the current Unit.  At the beginning it is the default unit of the UnitGroup.
784          * @return The most recently set unit.
785          */
786         public Unit getCurrentUnit() {
787                 return currentUnit;
788         }
789         
790         /**
791          * Sets the current Unit.  The unit must be one of those included in the UnitGroup.
792          * @param u  The unit to set active.
793          */
794         public void setCurrentUnit(Unit u) {
795                 checkState(true);
796                 if (currentUnit == u)
797                         return;
798                 log.debug("Setting unit for " + this + " to '" + u + "'");
799                 currentUnit = u;
800                 fireStateChanged();
801         }
802         
803         
804         /**
805          * Returns the UnitGroup associated with the parameter value.
806          *
807          * @return The UnitGroup given to the constructor.
808          */
809         public UnitGroup getUnitGroup() {
810                 return units;
811         }
812         
813         
814
815         /**
816          * Add a listener to the model.  Adds the model as a listener to the value source if this
817          * is the first listener.
818          * @param l Listener to add.
819          */
820         @Override
821         public void addChangeListener(EventListener l) {
822                 checkState(true);
823                 
824                 if (listeners.isEmpty()) {
825                         if (source != null) {
826                                 source.addChangeListener(this);
827                                 lastValue = getValue();
828                                 lastAutomatic = isAutomatic();
829                         }
830                 }
831                 
832                 listeners.add(l);
833                 log.verbose(this + " adding listener (total " + listeners.size() + "): " + l);
834         }
835         
836         /**
837          * Remove a listener from the model.  Removes the model from being a listener to the Component
838          * if this was the last listener of the model.
839          * @param l Listener to remove.
840          */
841         @Override
842         public void removeChangeListener(EventListener l) {
843                 checkState(false);
844                 
845                 listeners.remove(l);
846                 if (listeners.isEmpty() && source != null) {
847                         source.removeChangeListener(this);
848                 }
849                 log.verbose(this + " removing listener (total " + listeners.size() + "): " + l);
850         }
851         
852         
853         /**
854          * Invalidates this model by removing all listeners and removing this from
855          * listening to the source.  After invalidation no listeners can be added to this
856          * model and the value cannot be set.
857          */
858         @Override
859         public void invalidate() {
860                 log.verbose("Invalidating " + this);
861                 invalidator.invalidate();
862                 
863                 if (!listeners.isEmpty()) {
864                         log.warn("Invalidating " + this + " while still having listeners " + listeners);
865                 }
866                 listeners.clear();
867                 if (source != null) {
868                         source.removeChangeListener(this);
869                 }
870                 MemoryManagement.collectable(this);
871         }
872         
873         
874         private void checkState(boolean error) {
875                 invalidator.check(error);
876         }
877         
878         
879         @Override
880         protected void finalize() throws Throwable {
881                 super.finalize();
882                 if (!listeners.isEmpty()) {
883                         log.warn(this + " being garbage-collected while having listeners " + listeners);
884                 }
885         };
886         
887         
888         /**
889          * Fire a ChangeEvent to all listeners.
890          */
891         protected void fireStateChanged() {
892                 checkState(true);
893                 
894                 EventObject event = new EventObject(this);
895                 ChangeEvent cevent = new ChangeEvent(this);
896                 firing++;
897                 // Copy the list before iterating to prevent concurrent modification exceptions.
898                 EventListener[] ls = listeners.toArray(new EventListener[0]);
899                 for (EventListener l : ls) {
900                         if ( l instanceof StateChangeListener ) {
901                                 ((StateChangeListener)l).stateChanged(event);
902                         } else if ( l instanceof ChangeListener ) {
903                                 ((ChangeListener)l).stateChanged(cevent);
904                         }
905                 }
906                 firing--;
907         }
908         
909         /**
910          * Called when the component changes.  Checks whether the modeled value has changed, and if
911          * it has, updates lastValue and generates ChangeEvents for all listeners of the model.
912          */
913         @Override
914         public void stateChanged(EventObject e) {
915                 checkState(true);
916                 
917                 double v = getValue();
918                 boolean b = isAutomatic();
919                 if (lastValue == v && lastAutomatic == b)
920                         return;
921                 lastValue = v;
922                 lastAutomatic = b;
923                 fireStateChanged();
924         }
925         
926         
927         /**
928          * Explain the DoubleModel as a String.
929          */
930         @Override
931         public String toString() {
932                 if (toString == null) {
933                         if (source == null) {
934                                 toString = "DoubleModel[constant=" + lastValue + "]";
935                         } else {
936                                 toString = "DoubleModel[" + source.getClass().getSimpleName() + ":" + valueName + "]";
937                         }
938                 }
939                 return toString;
940         }
941 }