001/**
002 * AbstractGeomElement -- Partial implementation of the GeomElement interface.
003 *
004 * Copyright (C) 2002-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 geomss.geom;
019
020import java.util.*;
021import static java.util.Objects.isNull;
022import static java.util.Objects.nonNull;
023import static java.util.Objects.requireNonNull;
024import java.util.concurrent.CopyOnWriteArrayList;
025import javax.swing.event.ChangeEvent;
026import javax.swing.event.ChangeListener;
027import javolution.lang.Reusable;
028import javolution.text.Text;
029import javolution.text.TextBuilder;
030import javolution.util.FastMap;
031import javolution.xml.XMLFormat;
032import javolution.xml.stream.XMLStreamException;
033
034/**
035 * A partial implementation of the {@link GeomElement} interface.
036 *
037 * <p> Modified by: Joseph A. Huwaldt </p>
038 *
039 * @author Joseph A. Huwaldt, Date: March 31, 2000
040 * @version January 30, 2017
041 *
042 * @param <T> The sub-type of this AbstractGeomElement element.
043 */
044@SuppressWarnings("serial")
045public abstract class AbstractGeomElement<T extends AbstractGeomElement> implements GeomElement<T>, Reusable {
046
047    /**
048     * The key used to store a J3DGeomGroup instance in the user data of the supplied
049     * GeomElement object.
050     */
051    private static final String USERDATA_KEY = "J3DGeomGroup";
052
053    /**
054     * The resource bundle for this package.
055     */
056    public static final ResourceBundle RESOURCES
057            = ResourceBundle.getBundle("geomss.geom.GeomResources", java.util.Locale.getDefault());
058
059    //  The next ID number to return.
060    private static long _nextID = 0;
061
062    //  The unique ID number assigned to this instance.
063    private final long _id = newID();
064
065    /**
066     * Name of this geometry element.
067     */
068    private String _name;
069
070    /**
071     * The list of change listeners.
072     */
073    private transient List<ChangeListener> _changeListeners = null;
074
075    //  Reference data for this element.
076    private HashMap<Object, Object> _userData = null;
077
078    /**
079     * Returns a new and unique ID number that can be used by objects in the geometry
080     * package to identify themselves.
081     */
082    private synchronized static long newID() {
083        long id = _nextID++;
084        return id;
085    }
086
087    /**
088     * Return the unique ID number of this geometry element.
089     */
090    @Override
091    public long getID() {
092        return _id;
093    }
094
095    /**
096     * Return the name of this geometry element. If the name is not defined, then
097     * <code>null</code> is returned.
098     */
099    @Override
100    public String getName() {
101        return _name;
102    }
103
104    /**
105     * Change the name of this geometry element to the specified name (may be
106     * <code>null</code> to clear the name and make it undefined).
107     */
108    @Override
109    public void setName(String name) {
110        if (!Objects.equals(name, _name)) {
111            _name = name;
112            fireChangeEvent();  //  Notify change listeners.
113        }
114    }
115
116    /**
117     * Add a listener that is notified of changes to the state of this element.
118     */
119    @Override
120    public synchronized void addChangeListener(ChangeListener listener) {
121        requireNonNull(listener);
122        if (isNull(_changeListeners))
123            _changeListeners = new CopyOnWriteArrayList();
124        if (!_changeListeners.contains(listener))
125            _changeListeners.add(listener);
126    }
127
128    /**
129     * Remove a listener from receiving notifications of changes to the state of this
130     * element.
131     */
132    @Override
133    public void removeChangeListener(ChangeListener listener) {
134        if (nonNull(_changeListeners))
135            _changeListeners.remove(listener);
136    }
137
138    /**
139     * Fire a ChangeEvent to any interested listeners indicating that the state of this
140     * geometry element has changed in some way.
141     */
142    protected void fireChangeEvent() {
143        if (nonNull(_changeListeners) && _changeListeners.size() > 0) {
144            ChangeEvent event = new ChangeEvent(this);
145            Iterator<ChangeListener> it = _changeListeners.iterator();
146            while (it.hasNext()) {
147                ChangeListener listener = it.next();
148                listener.stateChanged(event);
149            }
150        }
151    }
152
153    /**
154     * Returns a copy of this GeomElement instance
155     * {@link javolution.context.AllocatorContext allocated} by the calling thread
156     * (possibly on the stack).
157     *
158     * @return an identical and independent copy of this geometry element.
159     * @throws java.lang.CloneNotSupportedException Never thrown.
160     * @see #copy() 
161     */
162    @Override
163    @SuppressWarnings("CloneDoesntCallSuperClone")
164    public Object clone() throws CloneNotSupportedException {
165        return copy();
166    }
167
168
169    /**
170     * Compares the specified object with this object for equality. Returns true if and
171     * only if both AbstractGeomElement objects have the same name.
172     *
173     * @param obj the object to compare with.
174     * @return <code>true</code> if this object is identical to that object;
175     *         <code>false</code> otherwise.
176     */
177    @Override
178    public boolean equals(Object obj) {
179        if (this == obj)
180            return true;
181        if ((obj == null) || (obj.getClass() != this.getClass()))
182            return false;
183
184        AbstractGeomElement that = (AbstractGeomElement)obj;
185        return Objects.equals(this._name, that._name);
186    }
187
188    /**
189     * Returns the hash code for this <code>AbstractGeomElement</code>.
190     *
191     * @return the hash code value.
192     */
193    @Override
194    public int hashCode() {
195        return Objects.hash(_name);
196    }
197
198    /**
199     * Returns the text representation of this geometry element.
200     */
201    @Override
202    public Text toText() {
203        TextBuilder tmp = TextBuilder.newInstance();
204        tmp.append("{type=");
205        tmp.append(this.getClass().getName());
206        tmp.append(", id=");
207        tmp.append(getID());
208        tmp.append(", name=");
209        tmp.append(_name);
210        tmp.append(", size=");
211        tmp.append(size());
212        tmp.append(", phyDim=");
213        tmp.append(getPhyDimension());
214        tmp.append(", parDim=");
215        tmp.append(getParDimension());
216        tmp.append(", boundsMin=");
217        tmp.append(getBoundsMin());
218        tmp.append(", boundsMax=");
219        tmp.append(getBoundsMax());
220        tmp.append('}');
221        Text txt = tmp.toText();
222        TextBuilder.recycle(tmp);
223        return txt;
224    }
225
226    /**
227     * Returns a string representation of this geometry element.
228     */
229    @Override
230    public String toString() {
231        return toText().toString();
232    }
233
234    /**
235     * Compares this geometry element with the specified element for order (where order is
236     * determined by the element's name). Returns a negative integer, zero, or a positive
237     * integer as this object is less than, equal to, or greater than the specified
238     * object. This method delegates to the String.compareTo() method using the string
239     * representation of this element.
240     *
241     * @param otherElement The geometry element this one is being compared to.
242     */
243    @Override
244    public int compareTo(Object otherElement) {
245        String thisStr = this.toString();
246        String otherStr = otherElement.toString();
247        return thisStr.compareTo(otherStr);
248    }
249
250    /**
251     * Return any user defined object associated with this geometry element and the
252     * specified key. If there is no user data with the specified key, then
253     * <code>null</code> is returned.
254     *
255     * @param key the key whose associated value is to be returned. May not be null.
256     * @return The user data associated with "key" or null if the specified key could not
257     *         be found.
258     */
259    @Override
260    public Object getUserData(Object key) {
261        requireNonNull(key);
262        if (isNull(_userData))
263            return null;
264        return _userData.get(key);
265    }
266
267    /**
268     * Returns a new Map containing all the user objects associated with this geometry
269     * element.
270     */
271    @Override
272    public Map<Object, Object> getAllUserData() {
273        if (isNull(_userData))
274            return FastMap.newInstance();
275        FastMap<Object, Object> map = FastMap.newInstance();
276        map.putAll(_userData);
277        map.remove(USERDATA_KEY); //  Do not return this internal use user data to the user.
278        return map;
279    }
280
281    /**
282     * Set the user defined object associated with this geometry element and the specified
283     * key. This can be used to store any type of information with a geometry element that
284     * could be useful.
285     *
286     * @param key   the key with which the specified value is to be associated. May not be
287     *              null.
288     * @param value the value to be associated with the specified key. May not be null.
289     */
290    @Override
291    public void putUserData(Object key, Object value) {
292        requireNonNull(key);
293        requireNonNull(value);
294        if (isNull(_userData))
295            _userData = new HashMap();
296        _userData.put(key, value);
297    }
298
299    /**
300     * Add all the user defined data in the supplied Map to this objects map of user
301     * defined data.
302     *
303     * @param data The Map of user defined data to be added to this object's map of user
304     *             defined data.
305     */
306    @Override
307    public void putAllUserData(Map data) {
308        if (isNull(data) || data.isEmpty())
309            return;
310        if (isNull(_userData))
311            _userData = new HashMap();
312        _userData.putAll(data);
313    }
314
315    /**
316     * Removes the entry for the specified user object key if present.
317     *
318     * @param key the key whose mapping is to be removed from the map. May not be null.
319     */
320    @Override
321    public void removeUserData(Object key) {
322        requireNonNull(key);
323        if (isNull(_userData))
324            return;
325        
326        _userData.remove(key);
327    }
328
329    /**
330     * Resets the internal state of this object to its default values. Subclasses that
331     * override this method must call <code>super.reset();</code> to ensure that the state
332     * is reset properly.
333     */
334    @Override
335    public void reset() {
336        _name = null;
337        if (nonNull(_changeListeners))
338            _changeListeners.clear();
339        
340        if (nonNull(_userData))
341            _userData.clear();
342    }
343
344    /**
345     * Copy the state of this GeomElement object to the target object. This is required to
346     * make a shallow copy of this object or to make an object that has the same features
347     * as this object.
348     * <p>
349     * This implementation copies the object's name and user data.
350     * </p>
351     *
352     * @param <T>    The type of this geometry element.
353     * @param target The target AbstractGeomElement object.
354     * @return A reference to <code>target</code> is returned for convenience.
355     */
356    protected <T extends AbstractGeomElement> T copyState(T target) {
357        target.setName(getName());
358        if (nonNull(_userData) && !_userData.isEmpty()) {
359            Map<Object, Object> userData = getAllUserData();
360            target.putAllUserData(userData);
361        }
362        return target;
363    }
364
365    /**
366     * Holds the default XML representation for this object.
367     */
368    protected static final XMLFormat<AbstractGeomElement> XML
369            = new XMLFormat<AbstractGeomElement>(AbstractGeomElement.class) {
370
371                @Override
372                public void read(XMLFormat.InputElement xml, AbstractGeomElement obj) throws XMLStreamException {
373                    String name = xml.getAttribute("name", (String)null);
374                    obj.setName(name);
375                    FastMap userData = xml.get("UserData", FastMap.class);
376                    if (nonNull(userData)) {
377                        obj.putAllUserData(userData);
378                        FastMap.recycle(userData);
379                    }
380                }
381
382                @Override
383                public void write(AbstractGeomElement obj, XMLFormat.OutputElement xml) throws XMLStreamException {
384                    String name = obj.getName();
385                    if (nonNull(name))
386                        xml.setAttribute("name", name);
387                    FastMap userData = (FastMap)obj.getAllUserData();
388                    if (!userData.isEmpty())
389                        xml.add(userData, "UserData", FastMap.class);
390                }
391            };
392
393}