001/**
002 * ModelNoteTrans -- A GeomTransform that has a GenModelNote for a child.
003 *
004 * Copyright (C) 2014-2018, 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.1 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 Lesser 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 geomss.geom;
019
020import jahuwaldt.js.param.Parameter;
021import java.awt.Font;
022import java.text.MessageFormat;
023import java.util.Objects;
024import static java.util.Objects.requireNonNull;
025import javax.measure.converter.ConversionException;
026import javax.measure.quantity.Dimensionless;
027import javax.measure.quantity.Length;
028import javax.measure.unit.Unit;
029import javax.swing.event.ChangeListener;
030import javolution.context.ObjectFactory;
031import javolution.lang.Immutable;
032import javolution.xml.XMLFormat;
033import javolution.xml.stream.XMLStreamException;
034
035/**
036 * A {@link GeomTransform} element that refers to a {@link GenModelNote} object and
037 * masquerades as a GenModelNote object itself.
038 *
039 * <p> Modified by: Joseph A. Huwaldt </p>
040 *
041 * @author Joseph A. Huwaldt, Date: February 10, 2014
042 * @version April 10, 2018
043 */
044@SuppressWarnings({"serial", "CloneableImplementsClone"})
045public final class ModelNoteTrans extends GenModelNote implements GeomTransform<GenModelNote> {
046
047    /**
048     * The transformation represented by this transformation element.
049     */
050    private GTransform _TM;
051
052    /**
053     * The object that is the child of this transform.
054     */
055    private GenModelNote _child;
056
057    /**
058     * Reference to a change listener for this object's child.
059     */
060    private ChangeListener _childChangeListener = new ForwardingChangeListener(this);
061
062    /**
063     * Returns a 3D {@link ModelNoteTrans} instance holding the specified
064     * {@link GenModelNote} and {@link GTransform}.
065     *
066     * @param child     The note that is the child of this transform element (may not be
067     *                  <code>null</code>).
068     * @param transform The transform held by this transform element (may not be
069     *                  <code>null</code>).
070     * @return the transform element having the specified values.
071     * @throws DimensionException if the input element is not 3D.
072     */
073    public static ModelNoteTrans newInstance(GenModelNote child, GTransform transform) {
074        requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child"));
075        requireNonNull(transform, MessageFormat.format(RESOURCES.getString("paramNullErr"), "transform"));
076
077        if (child.getPhyDimension() != 3)
078            throw new DimensionException(RESOURCES.getString("dimensionNot3").
079                    replace("<TYPE/>", "note").replace("<DIM/>", String.valueOf(child.getPhyDimension())));
080
081        ModelNoteTrans obj = FACTORY.object();
082        obj._TM = transform;
083        obj._child = child;
084        child.copyState(obj);
085
086        //  Listen for changes to the child object and pass them on.
087        if (!(child instanceof Immutable))
088            child.addChangeListener(obj._childChangeListener);
089
090        return obj;
091    }
092
093    /**
094     * Returns the transformation represented by this transformation element.
095     */
096    @Override
097    public GTransform getTransform() {
098        return _TM;
099    }
100
101    /**
102     * Returns the total transformation represented by an entire chain of GeomTransform
103     * objects below this one.
104     */
105    @Override
106    public GTransform getTotalTransform() {
107        return GeomUtil.getTotalTransform(this);
108    }
109
110    /**
111     * Sets the transformation represented by this transformation element.
112     *
113     * @param transform The transform to set this transform element to (may not be
114     *                  <code>null</code>).
115     */
116    @Override
117    public void setTransform(GTransform transform) {
118        requireNonNull(transform, MessageFormat.format(RESOURCES.getString("paramNullErr"), "transform"));
119        _TM = transform;
120        fireChangeEvent();
121    }
122
123    /**
124     * Returns the child object transformed by this transform element.
125     */
126    @Override
127    public GenModelNote getChild() {
128        return _child;
129    }
130
131    /**
132     * Return the text string associated with this note object.
133     *
134     * @return The text string associated with this note object.
135     */
136    @Override
137    public String getNote() {
138        return _child.getNote();
139    }
140
141    /**
142     * Return the location of this note in model space.
143     *
144     * @return The location of this note in model space.
145     */
146    @Override
147    public Point getLocation() {
148        return _TM.transform(_child.getLocation());
149    }
150
151    /**
152     * Return the vector indicating the horizontal axis direction for the text.
153     *
154     * @return The vector indicating the horizontal axis direction for the text.
155     */
156    @Override
157    public GeomVector<Dimensionless> getXHat() {
158        return _child.getXHat().getTransformed(_TM);
159    }
160
161    /**
162     * Return the vector indicating the vertical axis direction (or ascent direction) for
163     * the text.
164     *
165     * @return The vector indicating the vertical axis direction (or ascent direction) for
166     *         the text.
167     */
168    @Override
169    public GeomVector<Dimensionless> getYHat() {
170        return _child.getYHat().getTransformed(_TM);
171    }
172
173    /**
174     * Return the height of the text box in model units.
175     *
176     * @return The height of the text box in model units.
177     */
178    @Override
179    public Parameter<Length> getHeight() {
180        double zScale = _TM.getScaleVector().getValue(2);
181        return _child.getHeight().times(zScale);
182    }
183
184    /**
185     * Return the font used to display this note.
186     *
187     * @return The font used to display this note.
188     */
189    @Override
190    public Font getFont() {
191        return _child.getFont();
192    }
193
194    /**
195     * Return an immutable version of this note.
196     *
197     * @return An immutable version of this note.
198     */
199    @Override
200    public ModelNote immutable() {
201        return copyToReal();
202    }
203
204    /**
205     * Return a new note object identical to this one, but with the specified font.
206     *
207     * @param font The font for the copy of this note to use. May not be null.
208     * @return A new note object identical to this one, but with the specified font.
209     */
210    @Override
211    public ModelNoteTrans changeFont(Font font) {
212        requireNonNull(font);
213        ModelNoteTrans note = ModelNoteTrans.newInstance(_child.changeFont(font), _TM);
214        copyState(note);
215        return note;
216    }
217
218    /**
219     * Return a new note object identical to this one, but with the specified location in
220     * model space.
221     *
222     * @param location The location for the copy of this note.  May not be null.
223     * @return A new note object identical to this one, but with the specified location in
224     *         model space.
225     */
226    @Override
227    public ModelNoteTrans changeLocation(GeomPoint location) {
228        requireNonNull(location);
229        ModelNoteTrans note = ModelNoteTrans.newInstance(_child.changeLocation(location), _TM);
230        copyState(note);
231        return note;
232    }
233
234    /**
235     * Return a new note object identical to this one, but with the specified height in
236     * model space.
237     *
238     * @param height The height for the copy of this note.  May not be null.
239     * @return A new note object identical to this one, but with the specified height in
240     *         model space.
241     */
242    @Override
243    public ModelNoteTrans changeHeight(Parameter<Length> height) {
244        requireNonNull(height);
245        ModelNoteTrans note = ModelNoteTrans.newInstance(_child.changeHeight(height), _TM);
246        copyState(note);
247        return note;
248    }
249
250    /**
251     * Return a copy of the child object transformed by this transformation.
252     *
253     * @return A copy of the child object transformed by this transformation.
254     */
255    @Override
256    public ModelNote copyToReal() {
257        //  Transform the geometry.
258        Point location = getLocation();
259        Parameter<Length> height = getHeight();
260
261        //  Create a new note from the old one.
262        ModelNote note = _child.immutable().changeLocation(location).changeHeight(height);
263        copyState(note);
264
265        return note;
266    }
267
268    /**
269     * Returns the number of physical dimensions of the geometry element. This
270     * implementation always returns 3.
271     */
272    @Override
273    public int getPhyDimension() {
274        return 3;
275    }
276
277    /**
278     * Return <code>true</code> if this Note contains valid and finite numerical
279     * components. A value of <code>false</code> will be returned if any of the coordinate
280     * values are NaN or Inf.
281     */
282    @Override
283    public boolean isValid() {
284        return _child.isValid();
285    }
286
287    /**
288     * Returns a copy of this ModelNoteTrans instance
289     * {@link javolution.context.AllocatorContext allocated} by the calling thread
290     * (possibly on the stack).
291     *
292     * @return an identical and independent copy of this note.
293     */
294    @Override
295    public ModelNoteTrans copy() {
296        return copyOf(this);
297    }
298
299    /**
300     * Returns the unit in which the note location Point is stored.
301     */
302    @Override
303    public final Unit<Length> getUnit() {
304        return _child.getUnit();
305    }
306
307    /**
308     * Returns the equivalent to this note but with the location stated in the specified
309     * unit.
310     *
311     * @param unit the length unit of the note to be returned. May not be null.
312     * @return an equivalent of this note but with location stated in the specified unit.
313     * @throws ConversionException if the the input unit is not a length unit.
314     */
315    @Override
316    public ModelNoteTrans to(Unit<Length> unit) throws ConversionException {
317        if (unit.equals(getUnit()))
318            return this;
319
320        ModelNoteTrans note = ModelNoteTrans.newInstance(_child.to(unit), _TM);
321        copyState(note);
322        return note;
323    }
324
325    /**
326     * Return the equivalent of this note converted to the specified number of physical
327     * dimensions. This implementation will throw an exception if the specified dimension
328     * is anything other than 3.
329     *
330     * @param newDim The dimension of the point to return. MUST equal 3.
331     * @return The equivalent of this note converted to the new dimensions.
332     * @throws IllegalArgumentException if the new dimension is anything other than 3.
333     */
334    @Override
335    public ModelNoteTrans toDimension(int newDim) {
336        if (newDim == 3)
337            return this;
338
339        throw new IllegalArgumentException(
340                MessageFormat.format(RESOURCES.getString("dimensionNot3_2"), this.getClass().getName()));
341    }
342
343    /**
344     * Compares this ModelNoteTrans against the specified object for strict equality (same
345     * values and same units).
346     *
347     * @param obj the object to compare with.
348     * @return <code>true</code> if this note is identical to that note;
349     *         <code>false</code> otherwise.
350     */
351    @Override
352    public boolean equals(Object obj) {
353        if (this == obj)
354            return true;
355        if ((obj == null) || (obj.getClass() != this.getClass()))
356            return false;
357
358        ModelNoteTrans that = (ModelNoteTrans)obj;
359        return this._TM.equals(that._TM)
360                && this._child.equals(that._child)
361                && super.equals(obj);
362    }
363
364    /**
365     * Returns the hash code for this parameter.
366     *
367     * @return the hash code value.
368     */
369    @Override
370    public int hashCode() {
371        return 31*super.hashCode() + Objects.hash(_TM, _child);
372    }
373
374    /**
375     * Holds the default XML representation for this object.
376     */
377    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
378    protected static final XMLFormat<ModelNoteTrans> XML = new XMLFormat<ModelNoteTrans>(ModelNoteTrans.class) {
379
380        @Override
381        public ModelNoteTrans newInstance(Class<ModelNoteTrans> cls, XMLFormat.InputElement xml) throws XMLStreamException {
382            return FACTORY.object();
383        }
384
385        @Override
386        public void read(XMLFormat.InputElement xml, ModelNoteTrans obj) throws XMLStreamException {
387            GenModelNote.XML.read(xml, obj);     // Call parent read.
388
389            obj._TM = xml.getNext();
390            GenModelNote child = xml.getNext();
391            obj._child = child;
392
393            //  Listen for changes to the child object and pass them on.
394            if (!(child instanceof Immutable))
395                child.addChangeListener(obj._childChangeListener);
396
397        }
398
399        @Override
400        public void write(ModelNoteTrans obj, XMLFormat.OutputElement xml) throws XMLStreamException {
401            GenModelNote.XML.write(obj, xml);    // Call parent write.
402
403            xml.add(obj._TM);
404            xml.add(obj._child);
405        }
406    };
407
408    ///////////////////////
409    // Factory creation. //
410    ///////////////////////
411    private ModelNoteTrans() { }
412
413    @SuppressWarnings("unchecked")
414    private static final ObjectFactory<ModelNoteTrans> FACTORY = new ObjectFactory<ModelNoteTrans>() {
415        @Override
416        protected ModelNoteTrans create() {
417            return new ModelNoteTrans();
418        }
419
420        @Override
421        protected void cleanup(ModelNoteTrans obj) {
422            obj.reset();
423            obj._TM = null;
424            if (!(obj._child instanceof Immutable))
425                obj._child.removeChangeListener(obj._childChangeListener);
426            obj._child = null;
427        }
428    };
429
430    @SuppressWarnings("unchecked")
431    private static ModelNoteTrans copyOf(ModelNoteTrans original) {
432        ModelNoteTrans obj = FACTORY.object();
433        obj._TM = original._TM.copy();
434        obj._child = original._child.copy();
435        original.copyState(obj);
436        if (!(obj._child instanceof Immutable))
437            obj._child.addChangeListener(obj._childChangeListener);
438        return obj;
439    }
440
441}