package org.archstudio.bna.keys; import java.awt.Dimension; import java.awt.Insets; import java.awt.Polygon; import java.awt.geom.Arc2D; import java.awt.geom.Area; import java.awt.geom.CubicCurve2D; import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.awt.geom.QuadCurve2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RectangularShape; import java.awt.geom.RoundRectangle2D; import java.util.Arrays; import java.util.List; import java.util.Set; import org.archstudio.bna.IThing; import org.archstudio.sysutils.CloneableObject; import org.archstudio.sysutils.FastMap; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.google.common.collect.Sets; /** * Captures a key that stores a value in an {@link IThing}. * * @param <V> * The type of value stored by the key, e.g., a Rectangle, RGB, an Integer, etc. */ @NonNullByDefault public class ThingKey<V> extends AbstractThingKey<V> { @SuppressWarnings("unchecked") public static final <V> IThingKey<V> create(Object id) { return create(id, (Function<V, V>) CLONE_FUNCTION, false); } @SuppressWarnings("unchecked") public static final <V> IThingKey<V> create(Object id, boolean nullable) { return identity(new ThingKey<V>(id, (Function<V, V>) CLONE_FUNCTION, nullable)); } public static final <V> IThingKey<V> create(Object id, Function<V, V> cloneFunction) { return identity(new ThingKey<V>(id, cloneFunction, false)); } public static final <V> IThingKey<V> create(Object id, Function<V, V> cloneFunction, boolean nullable) { return identity(new ThingKey<V>(id, cloneFunction, nullable)); } private static Function<Object, Object> CLONE_FUNCTION = new Function<Object, Object>() { @SuppressWarnings("unchecked") @Override public Object apply(Object input) { if (input == null) { return null; } Class<?> inputClass = input.getClass(); Function<Object, Object> fn = (Function<Object, Object>) CLONE_FUNCTIONS.get(inputClass); if (fn != null) { return fn.apply(input); } Class<?> superClass = inputClass; while (superClass != null) { for (Class<?> iface : superClass.getInterfaces()) { fn = (Function<Object, Object>) CLONE_FUNCTIONS.get(iface); if (fn != null) { CLONE_FUNCTIONS.put(inputClass, fn); return fn.apply(input); } } superClass = superClass.getSuperclass(); } CLONE_FUNCTIONS.put(inputClass, Functions.identity()); return input; } }; /* package */static FastMap<Class<?>, Function<?, ?>> CLONE_FUNCTIONS = new FastMap<>(true); static { CLONE_FUNCTIONS.put(List.class, new Function<List<Object>, List<Object>>() { @Override public List<Object> apply(List<Object> input) { List<Object> list = Lists.newArrayListWithExpectedSize(input.size()); list.addAll(Collections2.transform(input, CLONE_FUNCTION)); return list; } }); CLONE_FUNCTIONS.put(Set.class, new Function<Set<Object>, Set<Object>>() { @Override public Set<Object> apply(Set<Object> input) { Set<Object> set = Sets.newHashSetWithExpectedSize(input.size()); set.addAll(Collections2.transform(input, CLONE_FUNCTION)); return set; } }); CLONE_FUNCTIONS.put(CloneableObject.class, new Function<CloneableObject, CloneableObject>() { @Override public CloneableObject apply(CloneableObject input) { return (CloneableObject) input.clone(); } }); CLONE_FUNCTIONS.put(Dimension.class, new Function<Dimension, Dimension>() { @Override public Dimension apply(Dimension input) { return new Dimension(input.width, input.height); } }); CLONE_FUNCTIONS.put(Insets.class, new Function<Insets, Insets>() { @Override public Insets apply(Insets input) { return (Insets) input.clone(); } }); CLONE_FUNCTIONS.put(Point.class, new Function<Point, Point>() { @Override public Point apply(Point input) { return new Point(input.x, input.y); } }); CLONE_FUNCTIONS.put(Point2D.class, new Function<Point2D, Point2D>() { @Override public Point2D apply(Point2D input) { return (Point2D) input.clone(); } }); CLONE_FUNCTIONS.put(Rectangle.class, new Function<Rectangle, Rectangle>() { @Override public Rectangle apply(Rectangle input) { return new Rectangle(input.x, input.y, input.width, input.height); } }); CLONE_FUNCTIONS.put(RGB.class, new Function<RGB, RGB>() { @Override public RGB apply(RGB input) { return new RGB(input.red, input.green, input.blue); } }); CLONE_FUNCTIONS.put(Area.class, new Function<Area, Area>() { @Override public Area apply(Area input) { return (Area) input.clone(); } }); CLONE_FUNCTIONS.put(CubicCurve2D.class, new Function<CubicCurve2D, CubicCurve2D>() { @Override public CubicCurve2D apply(CubicCurve2D input) { return (CubicCurve2D) input.clone(); } }); CLONE_FUNCTIONS.put(Line2D.class, new Function<Line2D, Line2D>() { @Override public Line2D apply(Line2D input) { return (Line2D) input.clone(); } }); CLONE_FUNCTIONS.put(Path2D.class, new Function<Path2D, Path2D>() { @Override public Path2D apply(Path2D input) { return (Path2D) input.clone(); } }); CLONE_FUNCTIONS.put(Polygon.class, new Function<Polygon, Polygon>() { @Override public Polygon apply(Polygon input) { return new Polygon(Arrays.copyOf(input.xpoints, input.npoints), Arrays.copyOf(input.ypoints, input.npoints), input.npoints); } }); CLONE_FUNCTIONS.put(QuadCurve2D.class, new Function<QuadCurve2D, QuadCurve2D>() { @Override public QuadCurve2D apply(QuadCurve2D input) { return (QuadCurve2D) input.clone(); } }); CLONE_FUNCTIONS.put(java.awt.Rectangle.class, new Function<java.awt.Rectangle, java.awt.Rectangle>() { @Override public java.awt.Rectangle apply(java.awt.Rectangle input) { return (java.awt.Rectangle) input.clone(); } }); CLONE_FUNCTIONS.put(RectangularShape.class, new Function<RectangularShape, RectangularShape>() { @Override public RectangularShape apply(RectangularShape input) { return (RectangularShape) input.clone(); } }); for (Class<?> c : new Class<?>[] { Arc2D.class, Ellipse2D.class, Rectangle2D.class, RoundRectangle2D.class }) { CLONE_FUNCTIONS.put(c, CLONE_FUNCTIONS.get(RectangularShape.class)); } for (String suffix : new String[] { "$Float", ".Float", "$Double", ".Double" }) { for (Class<?> c : Lists.newArrayList(CLONE_FUNCTIONS.keySet())) { try { CLONE_FUNCTIONS.put(Class.forName(c.getName() + suffix), CLONE_FUNCTIONS.get(c)); } catch (ClassNotFoundException e) { } } } } @SuppressWarnings("unchecked") public static <T> T cloneValue(T value) { return ((Function<T, T>) CLONE_FUNCTION).apply(value); } protected ThingKey(Object id, @Nullable Function<V, V> cloneFunction, boolean nullable) { super(id, cloneFunction, nullable); } }