/* * @(#)AttributeKey.java * * Copyright (c) 1996-2010 The authors and contributors of JHotDraw. * You may not use, copy or modify this file, except in compliance with the * accompanying license terms. */ package org.jhotdraw.draw; import javax.annotation.Nullable; import java.io.Serializable; import java.util.*; import javax.swing.undo.*; import org.jhotdraw.util.*; /** * An <em>attribute key</em> provides typesafe access to an attribute of * a {@link Figure}. * <p> * An AttributeKey has a name, a type and a default value. The default value * is returned by Figure.get, if a Figure does not have an attribute * of the specified key. * <p> * The following code example shows how to set and get an attribute on a Figure. * <pre> * Figure aFigure; * AttributeKeys.STROKE_COLOR.put(aFigure, Color.blue); * </pre> * <p> * See {@link AttributeKeys} for a list of useful attribute keys. * * @author Werner Randelshofer * @version $Id$ */ public class AttributeKey<T> implements Serializable { private static final long serialVersionUID=1L; /** * Holds a String representation of the attribute key. */ private String key; /** * Holds the default value. */ @Nullable private T defaultValue; /** * Specifies whether null values are allowed. */ private boolean isNullValueAllowed; /** * Holds labels for the localization of the attribute. */ private ResourceBundleUtil labels; /** This variable is used as a "type token" so that we can check for * assignability of attribute values at runtime. */ private Class<T> clazz; /** Creates a new instance with the specified attribute key, type token class, * default value null, and allowing null values. */ public AttributeKey(String key, Class<T> clazz) { this(key, clazz, null, true); } /** Creates a new instance with the specified attribute key, type token class, * and default value, and allowing null values. */ public AttributeKey(String key, Class<T> clazz, @Nullable T defaultValue) { this(key, clazz, defaultValue, true); } /** Creates a new instance with the specified attribute key, type token class, * default value, and allowing or disallowing null values. */ public AttributeKey(String key, Class<T> clazz, @Nullable T defaultValue, boolean isNullValueAllowed) { this(key, clazz, defaultValue, isNullValueAllowed, null); } /** Creates a new instance with the specified attribute key, type token class, * default value, and allowing or disallowing null values. * * @param key The key string. * @param clazz This is used as a "type token" for assignability checks * at runtime. * @param isNullValueAllowed whether null values are allowed. * @param labels ResourceBundle for human friendly representation of this * attribute key. The ResourceBundle must have a property named * {@code "attribute." + key + ".text"}. */ public AttributeKey(String key, Class<T> clazz, @Nullable T defaultValue, boolean isNullValueAllowed, @Nullable ResourceBundleUtil labels) { this.key = key; this.clazz = clazz; this.defaultValue = defaultValue; this.isNullValueAllowed = isNullValueAllowed; this.labels = (labels == null) ? ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels") : labels; } /** * Returns the key string. * @return key string. */ public String getKey() { return key; } /** * Returns a localized human friendly presentation of the key. * @return the presentation name of the key. */ public String getPresentationName() { return (labels == null) ? key : labels.getString("attribute." + key + ".text"); } /** * Returns the default value of the attribute. * * @return the default value. */ @Nullable public T getDefaultValue() { return defaultValue; } /** * Gets a clone of the value from the Figure. */ @SuppressWarnings("unchecked") @Nullable public T getClone(Figure f) { T value = f.get(this); try { return value == null ? null : clazz.cast(Methods.invoke(value, "clone")); } catch (NoSuchMethodException ex) { InternalError e = new InternalError(); e.initCause(ex); throw e; } } /** * Gets the value of the attribute denoted by this AttributeKey from * a Figure. * * @param f A figure. * @return The value of the attribute. */ @Nullable public T get(Figure f) { return f.get(this); } /** * Gets the value of the attribute denoted by this AttributeKey from * a Map. * * @param a A Map. * @return The value of the attribute. */ @SuppressWarnings("unchecked") @Nullable public T get(Map<AttributeKey<?>, Object> a) { return a.containsKey(this) ? (T) a.get(this) : defaultValue; } /** * Convenience method for setting a value on a Figure. * <p> * Note: Unlike in previous versions of JHotDraw 7, this method does * not call {@code f.willChange()} before setting the value, and * {@code f.changed()} afterwards. * * @param f the Figure * @param value the attribute value */ public void set(Figure f, @Nullable T value) { if (value == null && !isNullValueAllowed) { throw new NullPointerException("Null value not allowed for AttributeKey " + key); } f.set(this, value); } /** * Sets the attribute and returns an UndoableEditEvent which can be used * to undo it. * <p> * Note: Unlike in previous versions of JHotDraw 7, this method does * not call {@code f.willChange()} before setting the value, and * {@code f.changed()} afterwards. */ public UndoableEdit setUndoable(final Figure f, @Nullable final T value) { if (value == null && !isNullValueAllowed) { throw new NullPointerException("Null value not allowed for AttributeKey " + key); } final Object restoreData = f.getAttributesRestoreData(); f.set(this, value); UndoableEdit edit = new AbstractUndoableEdit() { private static final long serialVersionUID = 1L; @Override public String getPresentationName() { return AttributeKey.this.getPresentationName(); } @Override public void undo() { super.undo(); f.willChange(); f.restoreAttributesTo(restoreData); f.changed(); } @Override public void redo() { super.redo(); f.willChange(); f.set(AttributeKey.this, value); f.changed(); } }; return edit; } /** * Convenience method for setting a clone of a value on a figure. * <p> * Note: Unlike in previous versions of JHotDraw 7, this method does * not call {@code f.willChange()} before setting the value, and * {@code f.changed()} afterwards. * * @param f the Figure * @param value the attribute value */ public void setClone(Figure f, @Nullable T value) { try { f.set(this, value == null ? null : clazz.cast(Methods.invoke(value, "clone"))); } catch (NoSuchMethodException ex) { InternalError e = new InternalError(); e.initCause(ex); throw e; } } /** * Convenience method for putting a clone of a value on a map. * * @param a the map * @param value the attribute value */ public void putClone(Map<AttributeKey<?>, Object> a, @Nullable T value) { try { put(a, value == null ? null : clazz.cast(Methods.invoke(value, "clone"))); } catch (NoSuchMethodException ex) { InternalError e = new InternalError(); e.initCause(ex); throw e; } } /** * Use this method to perform a type-safe put operation of an attribute * into a Map. * * @param a An attribute map. * @param value The new value. * @return The old value. */ @SuppressWarnings("unchecked") @Nullable public T put(Map<AttributeKey<?>, Object> a, @Nullable T value) { if (value == null && !isNullValueAllowed) { throw new NullPointerException("Null value not allowed for AttributeKey " + key); } return (T) a.put(this, value); } /** * Returns true if null values are allowed. * @return true if null values are allowed. */ public boolean isNullValueAllowed() { return isNullValueAllowed; } /** * Returns true if the specified value is assignable with this key. * * @param value * @return True if assignable. */ public boolean isAssignable(@Nullable Object value) { if (value == null) { return isNullValueAllowed(); } return clazz.isInstance(value); } /** Returns the key string. */ @Override public String toString() { return key; } @Override public int hashCode() { return key.hashCode(); } @Override public boolean equals(Object that) { if (that instanceof AttributeKey) { return ((AttributeKey) that).key.equals(this.key); } return false; } }