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