001/**
002 * UnitSet -- A container for self-consistent and often coherent unit sets.
003 *
004 * Copyright (C) 2015-2017, by Joseph A. Huwaldt. All rights reserved.
005 *
006 * This library is free software; you can redistribute it and/or modify it under the terms
007 * of the GNU Lesser General Public License as published by the Free Software Foundation;
008 * either version 2 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
012 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public License along with
015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
017 */
018package jahuwaldt.js.unit;
019
020import java.beans.PropertyChangeListener;
021import java.beans.PropertyChangeSupport;
022import java.util.HashMap;
023import javax.measure.converter.ConversionException;
024import javax.measure.converter.UnitConverter;
025import javax.measure.quantity.*;
026import javax.measure.unit.NonSI;
027import static javax.measure.unit.NonSI.*;
028import static javax.measure.unit.SI.*;
029import javax.measure.unit.Unit;
030import javolution.xml.XMLFormat;
031import javolution.xml.stream.XMLStreamException;
032
033/**
034 * A class that contains a set of units (generally made self-consistent if not coherent)
035 * and also provides lists (sets) of related units.
036 *
037 * <p> Modified by: Joseph A. Huwaldt </p>
038 *
039 * @author Joseph A. Huwaldt, Date: July 12, 2008
040 * @version March 19, 2017
041 */
042public final class UnitSet implements Cloneable {
043
044    /**
045     * The type of unit system being used.
046     */
047    public enum UnitSystem {
048
049        /**
050         * Constant used to indicate that the coherent SI units should be used.
051         */
052        SI_MKS,
053        /**
054         * Constant used to indicate that the coherent metric (cm-gram-sec) units should be used.
055         */
056        CGS,
057        /**
058         * Constant used to indicate that coherent US Customary units (ft-slug-sec) should be
059         * used.
060         */
061        US_FSS,
062        /**
063         * Constant used to indicate that coherent US Customary units (ft-lbm-sec) should be
064         * used.
065         */
066        US_FPS,
067        /**
068         * Constant used to indicate that a custom set of units is being used which
069         * may or may <em>not</em> be coherent.
070         */
071        CUSTOM
072    }
073
074    /**
075     * The different unit set types in this collection.
076     */
077    public enum SetType {
078
079        TIME, LENGTH, MASS, ANGLE, AREA, VOLUME, VELOCITY, ACCELERATION, FORCE, INERTIA, MASS_DENSITY,
080        ANGULAR_VELOCITY, TORQUE
081    }
082
083    /**
084     * The angular unit "degree".
085     */
086    public static final Unit DEG = DEGREE_ANGLE;
087
088    /**
089     * The angular unit "radian".
090     */
091    public static final Unit RAD = Angle.UNIT;
092
093    private static final Unit<Acceleration> FPS2 = FOOT.divide(SECOND.pow(2)).asType(Acceleration.class);
094    
095    //  A collection of unit sets (arrays of related and compatible units).
096    private static final HashMap<SetType, Unit[]> sets = new HashMap();
097
098    static {
099        Unit<Length> CM = CENTIMETER;
100        Unit<Length> MM = MILLIMETER;
101        Unit MM2 = MM.pow(2);
102        Unit CM2 = CM.pow(2);
103        Unit M2 = SQUARE_METRE;
104        Unit IN2 = INCH.pow(2);
105        Unit FT2 = FOOT.pow(2);
106        Unit MM3 = MM2.times(MM);
107        Unit CM3 = CM2.times(CM);
108        Unit M3 = CUBIC_METRE;
109        Unit IN3 = CUBIC_INCH;
110        Unit FT3 = FT2.times(FOOT);
111
112        {
113            Unit[] unitArr = {SECOND, MINUTE, HOUR, DAY};
114            sets.put(SetType.TIME, unitArr);
115        }
116        {
117            Unit[] unitArr = {MM, CM, METER, KILOMETER,
118                    INCH, FOOT, NAUTICAL_MILE, MILE};
119            sets.put(SetType.LENGTH, unitArr);
120        }
121        {
122            Unit[] unitArr = {GRAM, KILOGRAM, METRIC_TON, OUNCE, POUND, SLUG, TON_US};
123            sets.put(SetType.MASS, unitArr);
124        }
125        {
126            Unit[] unitArr = {RADIAN, DEGREE_ANGLE, REVOLUTION, SECOND_ANGLE, MINUTE_ANGLE };
127            sets.put(SetType.ANGLE, unitArr);
128        }
129        
130        {
131            Unit[] unitArr = {MM2, CM2, M2, HECTARE, KILOMETER.pow(2),
132                    IN2, FT2, MILE.pow(2)};
133            sets.put(SetType.AREA, unitArr);
134        }
135        {
136            Unit[] unitArr = {MM3, CM3, LITER, M3, KILOMETER.pow(3),
137                    IN3, FT3, MILE.pow(3)};
138            sets.put(SetType.VOLUME, unitArr);
139        }
140        
141        {
142            Unit[] unitArr = {MM.divide(SECOND), CM.divide(SECOND), METERS_PER_SECOND, KILOMETERS_PER_HOUR,
143                INCH.divide(SECOND), FEET_PER_SECOND, KNOT, MILES_PER_HOUR};
144            sets.put(SetType.VELOCITY, unitArr);
145        }
146        {
147            Unit[] unitArr = {MM.divide(SECOND.pow(2)), CM.divide(SECOND.pow(2)), METERS_PER_SQUARE_SECOND,
148                INCH.divide(SECOND.pow(2)), FPS2, NonSI.G};
149            sets.put(SetType.ACCELERATION, unitArr);
150        }
151        
152        {
153            Unit[] unitArr = {DYNE, NEWTON, KILOGRAM_FORCE, POUND_FORCE, POUNDAL};
154            sets.put(SetType.FORCE, unitArr);
155        }
156       
157        {
158            Unit[] unitArr = {KILOGRAM.times(CM2), KILOGRAM.times(M2),
159                    POUND.times(IN2), POUND.times(FT2), SLUG.times(FT2)};
160            sets.put(SetType.INERTIA, unitArr);
161        }
162        {
163            Unit[] unitArr = {KILOGRAM.divide(CM3), KILOGRAM.divide(M3),
164                    POUND.divide(IN3), POUND.divide(FT3), SLUG.divide(IN3), SLUG.divide(FT3)};
165            sets.put(SetType.MASS_DENSITY, unitArr);
166        }
167        {
168            Unit[] unitArr = {RADIAN.divide(SECOND), DEGREE_ANGLE.divide(SECOND), REVOLUTION.divide(SECOND)};
169            sets.put(SetType.ANGULAR_VELOCITY, unitArr);
170        }
171        {
172            Unit[] unitArr = {NEWTON.times(CM), Torque.UNIT, POUND_FORCE.times(INCH), POUND_FORCE.times(FOOT)};
173            sets.put(SetType.TORQUE, unitArr);
174        }
175    }
176
177    //  The type code for this unit set.
178    private UnitSystem type = UnitSystem.SI_MKS;
179    
180    //  Flag indicating if the unit set is coherent or not.
181    private boolean isCoherent = false;
182    
183    //  Flag indicating if the unit set is consistent or not.
184    private boolean isConsistent = false;
185
186    //  The various units in this unit set.
187    private Unit<Duration> time;
188    private Unit<Length> length;
189    private Unit<Mass> mass;
190    private Unit<Angle> angle;
191    
192    //  Derived units.
193    private Unit<Area> area;
194    private Unit<Volume> volume;
195    private Unit<Velocity> velocity;
196    private Unit<Acceleration> accel;
197    private Unit<Force> force;
198    private Unit<VolumetricDensity> massDensity;
199    private Unit<Inertia> inertia;
200    private Unit<AngularVelocity> angularVelocity;
201    private Unit<Torque> torque;
202    
203
204    /**
205     * Construct a coherent set of units based on the system identification provided.
206     *
207     * @param system The Unit System type.
208     */
209    public UnitSet(UnitSystem system) {
210        //  Define the fundamental units.
211        time = SECOND;
212        angle = DEG;
213
214        switch (system) {
215            case US_FSS:
216                length = FOOT;
217                mass = SLUG;
218                break;
219            
220            case US_FPS:
221                length = FOOT;
222                mass = POUND;
223                break;
224
225            case CGS:
226                length = CENTIMETER;
227                mass = GRAM;
228                break;
229                
230            case SI_MKS:
231            case CUSTOM:
232                length = METER;
233                mass = KILOGRAM;
234                break;
235        }
236        this.type = system;
237        this.isCoherent = true;
238
239        //  Create a consistent derived unit set.
240        makeConsistent();
241
242    }
243
244    /**
245     * Method that returns the Unit System for this unit set.
246     * 
247     * @return The UnitSystem for this unit set.
248     */
249    public UnitSystem getSystem() {
250        return type;
251    }
252
253    /**
254     * Returns an array of units of the specified type (array of related units).
255     *
256     * @param type The unit type to return an array of units for.
257     * @return The unit set array requested
258     */
259    public static Unit[] getSet(SetType type) {
260        return sets.get(type);
261    }
262
263    /**
264     * Return <code>true</code> if this unit set is coherent. Coherent means that the
265     * derived units in this set are a product of powers of base units with no other
266     * proportionality factor than one.
267     *
268     * @return true if this unit set is coherent.
269     * @see #isConsistent() 
270     */
271    public boolean isCoherent() {
272        return isCoherent;
273    }
274
275    /**
276     * Return <code>true</code> if this unit set is consistent. Consistent means that the
277     * derived units are made up of powers of the base units though there may be
278     * conversion factors involved.
279     *
280     * @return true if this unit set is consistent.
281     * @see #makeConsistent()
282     * @see #isCoherent()
283     */
284    public boolean isConsistent() {
285        return isConsistent;
286    }
287
288    /**
289     * Makes this unit set consistent by deriving a complete set of units for this
290     * application based on the fundamental or base units of time, length, mass and angle.
291     * Consistent means that the derived units in this set are all derived from the base
292     * units.
293     *
294     * @see #isConsistent()
295     * @see #isCoherent()
296     */
297    public void makeConsistent() {
298
299        area = length.pow(2).asType(Area.class);
300        volume = length.pow(3).asType(Volume.class);
301        velocity = length.divide(time).asType(Velocity.class);
302        accel = length.divide(time.pow(2)).asType(Acceleration.class);
303        
304        if (equivUnit(mass, KILOGRAM) && equivUnit(accel, METERS_PER_SQUARE_SECOND))
305            force = NEWTON;
306        else if (equivUnit(mass, KILOGRAM) && equivUnit(accel, G))
307            force = KILOGRAM_FORCE;
308        else if (equivUnit(mass, SLUG) && equivUnit(accel, FPS2))
309            force = POUND_FORCE;
310        else if (equivUnit(mass, POUND) && equivUnit(accel, FPS2))
311            force = POUNDAL;
312        else if (equivUnit(mass, POUND) && equivUnit(accel, G))
313            force = POUND_FORCE;
314        else
315            force = mass.times(accel).asType(Force.class);
316        
317        massDensity = mass.divide(volume).asType(VolumetricDensity.class);
318        inertia = mass.times(area).asType(Inertia.class);
319        
320        angularVelocity = angle.divide(time).asType(AngularVelocity.class);
321        torque = force.times(length).asType(Torque.class);
322        
323        //  The derived units are not consistent.
324        isConsistent = true;
325
326        //  Make sure the newly derived units are in the unit sets.
327        addUnitsToSets();
328
329        //  Does this represent one of the standard coherent unit systems?
330        if (time.equals(SECOND)) {
331            if (length.equals(METER) && mass.equals(KILOGRAM)) {
332                type = UnitSystem.SI_MKS;
333                isCoherent = true;
334            } else if (length.equals(CENTIMETER) && mass.equals(GRAM)) {
335                type = UnitSystem.CGS;
336                isCoherent = true;
337            } else if (length.equals(FOOT)) {
338                if (mass.equals(SLUG)) {
339                    type = UnitSystem.US_FSS;
340                    isCoherent = true;
341                } else if (mass.equals(POUND)) {
342                    type = UnitSystem.US_FPS;
343                    isCoherent = true;
344                }
345            }
346        }
347        
348    }
349    
350    /**
351     * Return true if two units are equivalent to one another.  Equivalent means that
352     * the conversion from unit1 to unit 2 is an identity (1.0).
353     * 
354     * @param unit1 The 1st unit to compare.
355     * @param unit2 The 2nd unit to compare.
356     * @return true if no conversion is required between unit1 and unit2.
357     */
358    private boolean equivUnit(Unit unit1, Unit unit2) {
359        try {
360            UnitConverter cvt = unit1.getConverterTo(unit2);
361            return cvt.equals(UnitConverter.IDENTITY);
362            
363        } catch (ConversionException ignore) {
364            return false;
365        }
366    }
367
368    /**
369     * Method that ensures that all the current units are in the unit sets.
370     */
371    private void addUnitsToSets() {
372        //  Time
373        Unit[] set = getSet(SetType.TIME);
374        if (!unitInSet(set, time))
375            addUnitToSet(set, time);
376
377        //  Mass
378        set = getSet(SetType.MASS);
379        if (!unitInSet(set, mass))
380            addUnitToSet(set, mass);
381
382        //  Length
383        set = getSet(SetType.LENGTH);
384        if (!unitInSet(set, length))
385            addUnitToSet(set, length);
386
387        //  Angle
388        set = getSet(SetType.ANGLE);
389        if (!unitInSet(set, angle))
390            addUnitToSet(set, angle);
391
392        
393        //  Area
394        set = getSet(SetType.AREA);
395        if (!unitInSet(set, area))
396            addUnitToSet(set, area);
397        
398        //  Volume
399        set = getSet(SetType.VOLUME);
400        if (!unitInSet(set, volume))
401            addUnitToSet(set, volume);
402        
403        //  Velocity
404        set = getSet(SetType.VELOCITY);
405        if (!unitInSet(set, velocity))
406            addUnitToSet(set, velocity);
407        
408        //  Acceleration
409        set = getSet(SetType.ACCELERATION);
410        if (!unitInSet(set, accel))
411            addUnitToSet(set, accel);
412        
413        //  Force
414        set = getSet(SetType.FORCE);
415        if (!unitInSet(set, force))
416            addUnitToSet(set, force);
417        
418        //  Mass density
419        set = getSet(SetType.MASS_DENSITY);
420        if (!unitInSet(set, massDensity))
421            addUnitToSet(set, massDensity);
422
423        //  Inertia
424        set = getSet(SetType.INERTIA);
425        if (!unitInSet(set, inertia))
426            addUnitToSet(set, inertia);
427
428        //  Angular velocity
429        set = getSet(SetType.ANGULAR_VELOCITY);
430        if (!unitInSet(set, angularVelocity))
431            addUnitToSet(set, angularVelocity);
432        
433        //  Torque
434        set = getSet(SetType.TORQUE);
435        if (!unitInSet(set, torque))
436            addUnitToSet(set, torque);
437        
438    }
439
440    /**
441     * Returns true if the specified unit is in the given array of units.
442     */
443    private boolean unitInSet(Unit[] set, Unit unit) {
444        int size = set.length;
445        for (int i = 0; i < size; ++i)
446            if (set[i].equals(unit))
447                return true;
448        return false;
449    }
450
451    /**
452     * Method that adds the specified unit to the specified array of units by creating a
453     * new array and adding the unit to the end of it.
454     */
455    private Unit[] addUnitToSet(Unit[] set, Unit unit) {
456        int size = set.length;
457        Unit[] newArray = new Unit[size + 1];
458        System.arraycopy(set, 0, newArray, 0, size);
459        newArray[size] = unit;
460        return newArray;
461    }
462
463    /**
464     * Return the duration of time units for this unit set.
465     * 
466     * @return The units of durations of time.
467     */
468    public Unit<Duration> time() {
469        return time;
470    }
471
472    /**
473     * Method used to set the time units in this unit set. All units that have a
474     * time/duration component will be changed to use this time value after a call to
475     * "makeCoherent()".
476     *
477     * @param unit the time unit to set this unit set to.
478     * @throws ConversionException if the provided unit is not compatible with time
479     * (seconds).
480     * @see #makeConsistent()
481     */
482    public void setTime(Unit<Duration> unit) throws ConversionException {
483        if (!unit.isCompatible(time))
484            throw new ConversionException("Time units must be compatible with seconds. Input unit = " + unit);
485        if (!unit.equals(time)) {
486            Unit old = time;
487            time = unit;
488            isConsistent = false;
489            if (!time.equals(SECOND)) {
490                isCoherent = false;
491                type = UnitSystem.CUSTOM;
492            }
493            propertyChange.firePropertyChange("Time", old, unit);
494        }
495    }
496
497    /**
498     * Return the length units for this unit set.
499     * 
500     * @return The length units.
501     */
502    public Unit<Length> length() {
503        return length;
504    }
505
506    /**
507     * Method used to set the length units in this unit set. All units that have a length
508     * component will be changed to use this length value after a call to "makeCoherent()".
509     *
510     * @param unit the length unit to set this unit set to.
511     * @throws ConversionException if the provided unit is not compatible with length
512     * (meters).
513     * @see #makeConsistent()
514     */
515    public void setLength(Unit<Length> unit) throws ConversionException {
516        if (!unit.isCompatible(length))
517            throw new ConversionException("Length units must be compatible with meters. Input unit = " + unit);
518        if (!unit.equals(length)) {
519            Unit old = length;
520            type = UnitSystem.CUSTOM;
521            isConsistent = false;
522            isCoherent = false;
523            length = unit;
524            propertyChange.firePropertyChange("Length", old, unit);
525        }
526    }
527
528    /**
529     * Return the mass units for this unit set.
530     * 
531     * @return The mass units.
532     */
533    public Unit<Mass> mass() {
534        return mass;
535    }
536
537    /**
538     * Method used to set the mass units in this unit set. All units that have a mass
539     * component will be changed to use this mass value after a call to "makeCoherent()".
540     *
541     * @param unit the mass unit to set this unit set to.
542     * @throws ConversionException if the provided unit is not compatible with mass (kg).
543     * @see #makeConsistent()
544     */
545    public void setMass(Unit<Mass> unit) throws ConversionException {
546        if (!unit.isCompatible(mass))
547            throw new ConversionException("Mass units must be compatible with kilograms. Input unit = " + unit);
548        if (!unit.equals(mass)) {
549            Unit old = mass;
550            type = UnitSystem.CUSTOM;
551            isConsistent = false;
552            isCoherent = false;
553            mass = unit;
554            propertyChange.firePropertyChange("Mass", old, unit);
555        }
556    }
557
558    /**
559     * Return the angular measure units for this unit set.
560     * 
561     * @return The angular measure units.
562     */
563    public Unit<Angle> angle() {
564        return angle;
565    }
566
567    /**
568     * Method used to set the angular measure units in this unit set. All units that have an angular measure
569     * component will be changed to use this value after a call to "makeCoherent()".
570     *
571     * @param unit the angle unit to set this unit set to.
572     * @throws ConversionException if the provided unit is not compatible with angles (radian).
573     * @see #makeConsistent()
574     */
575    public void setAngle(Unit<Angle> unit) throws ConversionException {
576        if (!unit.isCompatible(angle))
577            throw new ConversionException("Angle units must be compatible with radians. Input unit = " + unit);
578        if (!unit.equals(angle)) {
579            Unit old = angle;
580            angle = unit;
581            isConsistent = false;
582            propertyChange.firePropertyChange("Angle", old, unit);
583        }
584    }
585
586    /**
587     * Return the area units.
588     * 
589     * @return Area units
590     */
591    public Unit<Area> area() {
592        return area;
593    }
594
595    /**
596     * Return the volume units.
597     * 
598     * @return Volume units
599     */
600    public Unit<Volume> volume() {
601        return volume;
602    }
603
604    /**
605     * Return the velocity units.
606     * 
607     * @return Velocity units
608     */
609    public Unit<Velocity> velocity() {
610        return velocity;
611    }
612    
613    /**
614     * Return the acceleration units.
615     * 
616     * @return Acceleration units
617     */
618    public Unit<Acceleration> acceleration() {
619        return accel;
620    }
621    
622    /**
623     * Return the force units.
624     * 
625     * @return Force units
626     */
627    public Unit<Force> force() {
628        return force;
629    }
630    
631    /**
632     * Return the inertia units.
633     * 
634     * @return Inertia units
635     */
636    public Unit<Inertia> inertia() {
637        return inertia;
638    }
639
640    /**
641     * Return the mass density units.
642     * 
643     * @return Mass density units.
644     */
645    public Unit<VolumetricDensity> massDensity() {
646        return massDensity;
647    }
648
649    /**
650     * Return the angular velocity units.
651     * 
652     * @return Angular velocity units.
653     */
654    public Unit<AngularVelocity> angularVelocity() {
655        return angularVelocity;
656    }
657    
658    /**
659     * Return the torque units.
660     * 
661     * @return The torque units.
662     */
663    public Unit<Torque> torque() {
664        return torque;
665    }
666    
667    /**
668     * Creates and returns a copy of this object.
669     * 
670     * @return A clone of this UnitSet.
671     */
672    @Override
673    public Object clone() {
674        Object result = null;
675
676        try {
677            // Make a shallow copy.
678            result = super.clone();
679
680        } catch (Exception e) {
681            // Can't happen if this object implements Cloneable.
682            e.printStackTrace();
683        }
684
685        return result;
686    }
687
688    /**
689     * Compares this UnitSet against the specified object for strict equality.
690     *
691     * @param obj the object to compare with.
692     * @return <code>true</code> if this UnitSet is identical to that object;
693     *         <code>false</code> otherwise.
694     */
695    @Override
696    public boolean equals(Object obj) {
697        if (this == obj)
698            return true;
699        if ((obj == null) || (obj.getClass() != this.getClass()))
700            return false;
701
702        UnitSet that = (UnitSet)obj;
703        if (type != that.type)
704            return false;
705        if (!time.equals(that.time))
706            return false;
707        if (!angle.equals(that.angle))
708            return false;
709        if (!length.equals(that.length))
710            return false;
711        if (!mass.equals(that.mass))
712            return false;
713        if (!area.equals(that.area))
714            return false;
715        if (!volume.equals(that.volume))
716            return false;
717        if (!velocity.equals(that.velocity))
718            return false;
719        if (!accel.equals(that.accel))
720            return false;
721        if (!massDensity.equals(that.massDensity))
722            return false;
723        if (!inertia.equals(that.inertia))
724            return false;
725        if (!angularVelocity.equals(that.angularVelocity))
726            return false;
727        
728        return torque.equals(that.torque);
729    }
730
731    /**
732     * Returns the hash code for this UnitSet.
733     *
734     * @return the hash code value.
735     */
736    @Override
737    public int hashCode() {
738        int hash = 7;
739        hash = 53 * hash + this.type.hashCode();
740        hash = 53 * hash + this.time.hashCode();
741        hash = 53 * hash + this.length.hashCode();
742        hash = 53 * hash + this.mass.hashCode();
743        hash = 53 * hash + this.angle.hashCode();
744        
745        hash = 53 * hash + this.area.hashCode();
746        hash = 53 * hash + this.volume.hashCode();
747        hash = 53 * hash + this.velocity.hashCode();
748        hash = 53 * hash + this.accel.hashCode();
749        hash = 53 * hash + this.massDensity.hashCode();
750        hash = 53 * hash + this.inertia.hashCode();
751        hash = 53 * hash + this.angularVelocity.hashCode();
752        hash = 53 * hash + this.torque.hashCode();
753        
754        return hash;
755    }
756    
757    /**
758     * Use this object to notify uses of changes in the inputs to a body part.
759     */
760    private transient final PropertyChangeSupport propertyChange = new PropertyChangeSupport(this);
761    
762    public synchronized void addPropertyChangeListener(PropertyChangeListener l) {
763        propertyChange.addPropertyChangeListener(l);
764    }
765
766    public synchronized void addPropertyChangeListener(String propertyName, PropertyChangeListener l) {
767        propertyChange.addPropertyChangeListener(propertyName, l);
768    }
769
770    public synchronized void removePropertyChangeListener(PropertyChangeListener l) {
771        propertyChange.removePropertyChangeListener(l);
772    }
773
774    public synchronized void removePropertyChangeListener(String propertyName, PropertyChangeListener l) {
775        propertyChange.removePropertyChangeListener(propertyName, l);
776    }
777
778    public synchronized void clearPropertyChangeListeners() {
779        PropertyChangeListener[] listeners = propertyChange.getPropertyChangeListeners();
780        for (PropertyChangeListener l : listeners) {
781            this.removePropertyChangeListener(l);
782        }
783    }
784    
785        /**
786        * Holds the default XML representation for this class.
787        */
788        protected static final XMLFormat<UnitSet> XML = new XMLFormat<UnitSet>(UnitSet.class) {
789
790                @Override
791                public UnitSet newInstance(Class<UnitSet> cls, XMLFormat.InputElement xml) throws XMLStreamException {
792            
793            //  Parse out the unit system type.
794            UnitSystem[] systems = UnitSystem.values();
795                        int typeIdx = xml.getAttribute("type", 0);
796            UnitSystem type = systems[typeIdx];
797            
798            //  Create the new unit set.
799            UnitSet db = new UnitSet(type);
800            
801            //  Parse out the units.
802            boolean isConsistent = xml.getAttribute("isConsistent", true);
803            Unit time = Unit.valueOf(xml.getAttribute("time", db.time.toString()));
804            Unit length = Unit.valueOf(xml.getAttribute("length", db.length.toString()));
805            Unit mass = Unit.valueOf(xml.getAttribute("mass", db.mass.toString()));
806            Unit angle = Unit.valueOf(xml.getAttribute("angle", db.angle.toString()));
807            db.setTime(time);
808            db.setLength(length);
809            db.setMass(mass);
810            db.setAngle(angle);
811            
812            if (isConsistent)
813                db.makeConsistent();
814            
815            return db;
816        }
817        
818                @Override
819                public void read(XMLFormat.InputElement xml, UnitSet db) throws XMLStreamException {
820            //  Nothing to do.  Already read everything in in "newInstance".
821                }
822
823                @Override
824                public void write(UnitSet db, XMLFormat.OutputElement xml) throws XMLStreamException {
825                        xml.setAttribute("type", db.type.ordinal());
826            xml.setAttribute("isConsistent", db.isConsistent);
827            xml.setAttribute("time", db.time.toString());
828            xml.setAttribute("length", db.length.toString());
829            xml.setAttribute("mass", db.mass.toString());
830            xml.setAttribute("angle", db.angle.toString());
831        }
832        };
833        
834}