/*******************************************************************************
* Copyright (c) 2006-2012
* Software Technology Group, Dresden University of Technology
* DevBoost GmbH, Berlin, Amtsgericht Charlottenburg, HRB 140026
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Software Technology Group - TU Dresden, Germany;
* DevBoost GmbH - Berlin, Germany
* - initial API and implementation
******************************************************************************/
/*
* @(#)AttributeKey.java 3.0 2009-04-19
*
* Copyright (c) 1996-2009 by the original authors of JHotDraw
* and all its contributors.
* All rights reserved.
*
* The copyright of this software is owned by the authors and
* contributors of the JHotDraw project ("the copyright holders").
* You may not use, copy or modify this software, except in
* accordance with the license agreement you entered into with
* the copyright holders. For details see accompanying license terms.
*/
package org.jhotdraw.draw;
import java.io.Serializable;
import java.util.*;
import javax.swing.undo.*;
import org.jhotdraw.util.*;
/**
* AttributeKey provides typesafe access to figure attributes.
* <p>
* An AttributeKey has a name, a type and a default value. The default value
* is returned by Figure.getAttribute, if a Figure does not have an attribute
* of the specified key.
* <p>
* The following code example shows how to basicSet and get an attribute on a Figure.
* <pre>
* Figure aFigure;
* AttributeKeys.STROKE_COLOR.set(aFigure, Color.blue);
* </pre>
* <p>
* See {@link AttributeKeys} for a list of useful attribute keys.
*
* @author Werner Randelshofer
* @version 3.0 2009-04-19 Added explicit
* <br>2.1 2009-04-15 Added method getPresentationName. The labels are now
* part of the attribute key.
* <br>2.0.1 2008-02-13 Fixed comments. Removed equals and hashCode.
* <br>2.0 2007-05-12 Removed basicSet methods.
* <br>1.2 2007-04-10 Convenience methods for getting and setting a clone
* of an attribute added.
* <br>1.1 2006-12-29 Support for getting/setting attribute keys on a
* Map added.
* <br>1.0.1 2006-07-14 Null values are not returned anymore when null
* values are not allowed.
* <br>1.0 7. Juni 2006 Created.
*/
public class AttributeKey<T> implements Serializable {
/**
* Holds a String representation of the attribute key.
*/
private String key;
/**
* Holds the default value.
*/
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, 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, T defaultValue, boolean isNullValueAllowed) {
this.key = key;
this.clazz = clazz;
this.defaultValue = defaultValue;
this.isNullValueAllowed = isNullValueAllowed;
}
/** 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, T defaultValue, boolean isNullValueAllowed, ResourceBundleUtil labels) {
this.key = key;
this.clazz = clazz;
this.defaultValue = defaultValue;
this.isNullValueAllowed = isNullValueAllowed;
this.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.
*/
public T getDefaultValue() {
return defaultValue;
}
/**
* Gets a clone of the value from the Figure.
*/
@SuppressWarnings("unchecked")
public T getClone(Figure f) {
T value = get(f);
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.
*/
public T get(Figure f) {
T value = (T) f.getAttribute(this);
return (value == null && !isNullValueAllowed) ? defaultValue : value;
}
/**
* 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")
public T get(Map<AttributeKey, Object> a) {
T value = (T) a.get(this);
return (value == null && !isNullValueAllowed) ? defaultValue : value;
}
/**
* Convenience method for setting a value on the
* specified figure and calling willChange before and changed
* after setting the value.
*
* @param f the Figure
* @param value the attribute value
*/
public void set(Figure f, T value) {
f.willChange();
basicSet(f, value);
f.changed();
}
/**
* Sets a value on the specified figure without invoking {@code willChange}
* and {@code changed} on the figure.
* <p>
* This method can be used to efficiently build a drawing from an
* {@link InputFormat}.
*
* @param f the Figure
* @param value the attribute value
*/
public void basicSet(Figure f, T value) {
if (value == null && !isNullValueAllowed) {
throw new NullPointerException("Null value not allowed for AttributeKey " + key);
}
f.setAttribute(this, value);
}
/**
* Sets the attribute and returns an UndoableEditEvent which can be used
* to undo it.
*/
public UndoableEdit setUndoable(final Figure figure, final T value) {
if (value == null && !isNullValueAllowed) {
throw new NullPointerException("Null value not allowed for AttributeKey " + key);
}
final Object restoreData = figure.getAttributesRestoreData();
figure.willChange();
figure.setAttribute(this, value);
figure.changed();
UndoableEdit edit = new AbstractUndoableEdit() {
@Override
public String getPresentationName() {
return AttributeKey.this.getPresentationName();
}
@Override
public void undo() {
super.undo();
figure.willChange();
figure.restoreAttributesTo(restoreData);
figure.changed();
}
@Override
public void redo() {
super.redo();
figure.willChange();
figure.setAttribute(AttributeKey.this, value);
figure.changed();
}
};
return edit;
}
/**
* Convenience method for seting a clone of a value on the
* specified figure and calling willChange before and changed
* after setting the value.
*
* @param f the Figure
* @param value the attribute value
*/
public void setClone(Figure f, T value) {
f.willChange();
basicSetClone(f, value);
f.changed();
}
/**
* Sets a clone of a value on the specified figure, without invoking
* {@code willChange} and {@code changed} on the figure.
* <p>
* This method can be used to efficiently build a drawing from an
* {@link InputFormat}.
*
* @param f the Figure
* @param value the attribute value
*/
public void basicSetClone(Figure f, T value) {
try {
basicSet(f, 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 typeface put operation of an attribute
* into a Map.
*
* @param a An attribute map.
* @param value The new value.
*/
public void set(Map<AttributeKey, Object> a, T value) {
put(a, value);
}
/**
* Use this method to perform a typeface put operation of an attribute
* into a Map.
*
* @param a An attribute map.
* @param value The new value.
* @return The old value.
*/
@SuppressWarnings("unchecked")
public T put(Map<AttributeKey, Object> a, T value) {
if (value == null && !isNullValueAllowed) {
throw new NullPointerException("Null value not allowed for AttributeKey " + key);
}
return (T) a.put(this, value);
}
/**
* Sets a clone of the value to the Figure without firing events.
*/
@SuppressWarnings("unchecked")
public void setClone(Map<AttributeKey, Object> a, T value) {
try {
set(a, value == null ? null : (T) Methods.invoke(value, "clone"));
} catch (NoSuchMethodException ex) {
InternalError e = new InternalError();
e.initCause(ex);
throw e;
}
}
/**
* 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(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;
}
}