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}