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