001/**
002 * Note -- Holds a textual note String located at a point in model space with a fixed size
003 * and orientation on the screen.
004 *
005 * Copyright (C) 2014-2015, Joseph A. Huwaldt. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or modify it under the terms
008 * of the GNU Lesser General Public License as published by the Free Software Foundation;
009 * either version 2.1 of the License, or (at your option) any later version.
010 *
011 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
012 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
013 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public License along with
016 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
017 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
018 */
019package geomss.geom;
020
021import java.awt.Font;
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 javolution.context.ObjectFactory;
028import javolution.lang.ValueType;
029import javolution.xml.XMLFormat;
030import javolution.xml.stream.XMLStreamException;
031
032/**
033 * Represents a textual note located at a point in model space with a fixed size and
034 * orientation on the screen.
035 *
036 * <p> Modified by: Joseph A. Huwaldt </p>
037 *
038 * @author Joseph A. Huwaldt, Date: February 5, 2014
039 * @version November 26, 2015
040 */
041@SuppressWarnings({"serial", "CloneableImplementsClone"})
042public final class Note extends GenScreenNote implements ValueType {
043
044    /**
045     * The text string displayed in the note.
046     */
047    private String text;
048
049    /**
050     * Holds the location of the note and defines it's units.
051     */
052    private Point location;
053
054    /**
055     * The font used to display the note.
056     */
057    private Font font;
058
059    /**
060     * Construct and return a new instance of a Note that uses the specified text string,
061     * the specified display font, and is located at the specified location in space.
062     *
063     * @param text     The text to be displayed in this geometry object. May not be null.
064     * @param location The location of this geometry object in model space. May not be null.
065     * @param font     The font used to display this note. May not be null.
066     * @return A new Note using the specified inputs.
067     */
068    public static Note valueOf(CharSequence text, GeomPoint location, Font font) {
069        requireNonNull(text);
070        requireNonNull(location);
071        requireNonNull(font);
072
073        Note note = FACTORY.object();
074        note.text = text.toString();
075        note.location = location.immutable();
076        note.font = font;
077
078        return note;
079    }
080
081    /**
082     * Construct and return a new instance of a Note that uses the specified text string,
083     * the default display font at the specified size, and is located at the specified
084     * location in space.
085     *
086     * @param text       The text to be displayed in this geometry object. May not be
087     *                   null.
088     * @param location   The location of this geometry object in model space. May not be
089     *                   null.
090     * @param fontPoints The size of the default font in pixels.
091     * @return A new Note using the specified inputs.
092     */
093    public static Note valueOf(CharSequence text, GeomPoint location, int fontPoints) {
094        return Note.valueOf(text, location, DEFAULT_FONT.deriveFont((float)fontPoints));
095    }
096
097    /**
098     * Construct and return a new instance of a Note that uses the specified text string,
099     * the default display font, and is located at the specified location in space.
100     *
101     * @param text     The text to be displayed in this geometry object. May not be null.
102     * @param location The location of this geometry object in model space. May not be null.
103     * @return A new Note using the specified inputs.
104     */
105    public static Note valueOf(CharSequence text, GeomPoint location) {
106        return Note.valueOf(text, location, DEFAULT_FONT);
107    }
108
109    /**
110     * Returns a new Note instance that is identical to the specified Note.
111     *
112     * @param note the Note to be copied into a new Note.  May not be null.
113     * @return A new not identical to the input note.
114     */
115    public static Note valueOf(Note note) {
116        return copyOf(requireNonNull(note));
117    }
118
119    /**
120     * Return the text string associated with this note object.
121     *
122     * @return The text string associated with this note object.
123     */
124    @Override
125    public String getNote() {
126        return text;
127    }
128
129    /**
130     * Return the location of this note in model space.
131     *
132     * @return The location of this note in model space.
133     */
134    @Override
135    public Point getLocation() {
136        return location;
137    }
138
139    /**
140     * Return the font used to display this note.
141     *
142     * @return The font used to display this note.
143     */
144    @Override
145    public Font getFont() {
146        return font;
147    }
148
149    /**
150     * Return an immutable version of this note.
151     *
152     * @return An immutable version of this note.
153     */
154    @Override
155    public Note immutable() {
156        return this;
157    }
158
159    /**
160     * Return a new note object identical to this one, but with the specified font.
161     *
162     * @param font The font to use in the copy of this note returned. May not be null.
163     * @return A new note object identical to this one, but with the specified font.
164     */
165    @Override
166    public Note changeFont(Font font) {
167        Note note = Note.valueOf(text, location, requireNonNull(font));
168        copyState(note);
169        return note;
170    }
171
172    /**
173     * Return a new note object identical to this one, but with the specified location in
174     * model space.
175     *
176     * @param location The location for the copy of this note returned. May not be null.
177     * @return A new note object identical to this one, but with the specified location in
178     *         model space.
179     */
180    @Override
181    public Note changeLocation(GeomPoint location) {
182        Note note = Note.valueOf(text, requireNonNull(location), font);
183        copyState(note);
184        return note;
185    }
186
187    /**
188     * Returns the number of physical dimensions of the geometry element. This
189     * implementation will return the physical dimensions of the point indicating the
190     * location of the note in space.
191     *
192     * @return The number of physical dimensions of the geometry element.
193     */
194    @Override
195    public int getPhyDimension() {
196        return location.getPhyDimension();
197    }
198
199    /**
200     * Return <code>true</code> if this Note contains valid and finite numerical
201     * components. A value of <code>false</code> will be returned if any of the location
202     * coordinate values are NaN or Inf.
203     *
204     * @return true if this Note contains valid and finite numerical components.
205     */
206    @Override
207    public boolean isValid() {
208        return location.isValid();
209    }
210
211    /**
212     * Returns a copy of this Note instance
213     * {@link javolution.context.AllocatorContext allocated} by the calling thread
214     * (possibly on the stack).
215     *
216     * @return an identical and independent copy of this note.
217     */
218    @Override
219    public Note copy() {
220        return copyOf(this);
221    }
222
223    /**
224     * Return a copy of this object with any transformations or subranges removed
225     * (applied).
226     *
227     * @return A copy of this object with any transformations or subranges removed.
228     */
229    @Override
230    public Note copyToReal() {
231        return copy();
232    }
233
234    /**
235     * Returns the unit in which the note location Point is stored.
236     *
237     * @return The unit in which the note location Point is stored.
238     */
239    @Override
240    public final Unit<Length> getUnit() {
241        return location.getUnit();
242    }
243
244    /**
245     * Returns the equivalent to this note but with the location stated in the specified
246     * unit.
247     *
248     * @param unit the length unit of the note to be returned. May not be null.
249     * @return an equivalent of this note but with location stated in the specified unit.
250     * @throws ConversionException if the the input unit is not a length unit.
251     */
252    @Override
253    public Note to(Unit<Length> unit) throws ConversionException {
254        if (unit.equals(getUnit()))
255            return this;
256
257        Note note = FACTORY.object();
258        note.text = text;
259        note.location = location.to(unit);
260        copyState(note);
261
262        return note;
263    }
264
265    /**
266     * Return the equivalent of this note converted to the specified number of physical
267     * dimensions. If the number of dimensions is greater than this element, then zeros
268     * are added to the additional dimensions. If the number of dimensions is less than
269     * this element, then the extra dimensions are simply dropped (truncated). If the new
270     * dimensions are the same as the dimension of this element, then this element is
271     * simply returned.
272     *
273     * @param newDim The dimension of the note to return.
274     * @return The equivalent to this note converted to the new dimensions.
275     */
276    @Override
277    public Note toDimension(int newDim) {
278        int thisDim = this.getPhyDimension();
279        if (newDim == thisDim)
280            return this;
281
282        //  Convert the underlying geometry.
283        Point nloc = location.toDimension(newDim);
284
285        //  Create and return a new note with the new geometry.
286        Note note = Note.valueOf(text, nloc, font);
287        copyState(note);
288
289        return note;
290    }
291
292    /**
293     * Compares this Note against the specified object for strict equality (same values
294     * and same units).
295     *
296     * @param obj the object to compare with.
297     * @return <code>true</code> if this note is identical to that note;
298     *         <code>false</code> otherwise.
299     */
300    @Override
301    public boolean equals(Object obj) {
302        if (this == obj)
303            return true;
304        if ((obj == null) || (obj.getClass() != this.getClass()))
305            return false;
306
307        Note that = (Note)obj;
308        return this.text.equals(that.text)
309                && this.location.equals(that.location)
310                && super.equals(obj);
311    }
312
313    /**
314     * Returns the hash code for this Note object.
315     *
316     * @return the hash code value.
317     */
318    @Override
319    public int hashCode() {
320        return 31*super.hashCode() + Objects.hash(text, location);
321    }
322
323    /**
324     * Holds the default XML representation for this object.
325     */
326    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
327    protected static final XMLFormat<Note> XML = new XMLFormat<Note>(Note.class) {
328
329        @Override
330        public Note newInstance(Class<Note> cls, XMLFormat.InputElement xml) throws XMLStreamException {
331            return FACTORY.object();
332        }
333
334        @Override
335        public void read(XMLFormat.InputElement xml, Note obj) throws XMLStreamException {
336            //  Read in the font information.
337            String fontStr = xml.getAttribute("font", DEFAULT_FONT_CODE);
338            Font font = Font.decode(fontStr);
339
340            GenScreenNote.XML.read(xml, obj);     // Call parent read.
341
342            //  Read in the text string.
343            String text = xml.get("Note", String.class);
344
345            //  Read in the location.
346            Point location = xml.get("Location", Point.class);
347
348            //  Fill in the object definition.
349            obj.text = text;
350            obj.font = font;
351            obj.location = location;
352        }
353
354        @Override
355        public void write(Note obj, XMLFormat.OutputElement xml) throws XMLStreamException {
356            //  Write out a font string.
357            Font font = obj.getFont();
358            String fontStr = encodeFont(font.getName(), font.getStyle(), font.getSize());
359            xml.setAttribute("font", fontStr);
360
361            GenScreenNote.XML.write(obj, xml);    // Call parent write.
362
363            //  Write out the text string.
364            xml.add(obj.getNote(), "Note", String.class);
365
366            //  Write out the location of the note in model space.
367            xml.add(obj.getLocation(), "Location", Point.class);
368        }
369    };
370
371    ///////////////////////
372    // Factory creation. //
373    ///////////////////////
374    private Note() { }
375
376    @SuppressWarnings("unchecked")
377    private static final ObjectFactory<Note> FACTORY = new ObjectFactory<Note>() {
378        @Override
379        protected Note create() {
380            return new Note();
381        }
382
383        @Override
384        protected void cleanup(Note obj) {
385            obj.reset();
386            obj.text = null;
387            obj.location = null;
388        }
389    };
390
391    @SuppressWarnings("unchecked")
392    private static Note copyOf(Note original) {
393        Note obj = FACTORY.object();
394        obj.text = original.text;
395        obj.location = original.location.copy();
396        obj.font = original.font;
397        original.copyState(obj);
398        return obj;
399    }
400}