/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * 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: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.g2d.element; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Set; import org.simantics.g2d.diagram.impl.AbstractHandlerClass; import org.simantics.g2d.element.handler.ElementHandler; import org.simantics.g2d.element.handler.HandleMouseEvent; import org.simantics.g2d.element.handler.InternalSize; import org.simantics.g2d.element.handler.Move; import org.simantics.g2d.element.handler.Rotate; import org.simantics.g2d.element.handler.SceneGraph; import org.simantics.g2d.element.handler.Transform; import org.simantics.g2d.element.handler.Validator; /** * ElementClass is a class of elements. It consists of element handlers. * Each handler contributes functions to some aspects of the diagram. *

* Handlers are ordered. For instance, if there are multiple Paint handlers, * the paint order is determined by the order of the handlers in the class. *

* The minimum requirement is implementation for the following interfaces: * @see Transform * @see InternalSize * * Typical element class has the following implementations: * @see Transform * @see SceneGraph * @see Move * @see Rotate * @see InternalSize or @see Scale * @see Selectable * * Interactive element classes have implementation to: * @see HandleMouseEvent * * See also: * @see Validator * * See package org.simantics.g2d.element.handler for handler interfaces. * See package org.simantics.g2d.element.handler.impl for common implementations. * Note that some implementations implement several interface. Also note that * some handler interfaces may be implemented several times (e.g. Paint, * Validator, ToolTip, Pick). In contrast, some handlers may have at most one * implementation, such as Transform, Move, Rotate, Scale, Size. *

* Tip: Use SymbolUtil (CanvasParticipant) to manage graphical resources (images). * * @author Toni Kalajainen */ public final class ElementClass extends AbstractHandlerClass { private static final long serialVersionUID = -446421782709451743L; /** Handler with this annotation may have at most one implementation */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public static @interface Single {} /** Handler with this annotation must be implemented (once or more) */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public static @interface Required {} private static final Class[] REQUIRED_HANDLERS = new Class[] {Transform.class, InternalSize.class}; private String id = ""; /** * Compile new element class from a set of handler * @param contributions * @return */ public static ElementClass compile(Collection contributions) { return new ElementClass(contributions); } public static ElementClass compile(Collection contributions, boolean check) { return new ElementClass(contributions, check); } /** * Compile new element class from a set of handler * @param contributions * @return */ public static ElementClass compile(ElementHandler... contributions) { if (contributions.length == 0) return new ElementClass(Arrays.asList(contributions)); ArrayList al = new ArrayList(contributions.length); for (ElementHandler eh : contributions) al.add(eh); return new ElementClass(al); } ElementClass(Collection contributions) { super(contributions); assertClassValid(contributions); } ElementClass(Collection contributions, boolean check) { super(contributions); if(check) assertClassValid(contributions); } /** * Validates that handler is valid. * * @param contributions * @return */ public static void assertClassValid(Collection contributions) { // 1. Verify requirements nextRequirement: for (Class requiredClass : REQUIRED_HANDLERS) { for (ElementHandler eh : contributions) if (requiredClass.isInstance(eh)) continue nextRequirement; throw new Error("Element class does not implement "+requiredClass.getName()); } // 2. Verify singletons // 2.1. Collect implemented handlers Set> implementedHandlerInterfaces = new HashSet>(); for (ElementHandler eh : contributions) _traverseElementHandlerInterfaces(eh.getClass(), implementedHandlerInterfaces); // 2.2. Verify singletons are implemented only once for (Class ehc : implementedHandlerInterfaces) { if (!_isSingletonHandler(ehc)) continue; int implementationCount = 0; for (ElementHandler eh : contributions) { if (!ehc.isInstance(eh)) continue; implementationCount++; } if (implementationCount>1) throw new Error("Element class has "+implementationCount+" implementations to a _singleton_ element handler \""+ehc.getName()+"\": " + contributions); } } private static boolean _isSingletonHandler(Class elementHandlerClass) { Single s = elementHandlerClass.getAnnotation(Single.class); return s != null; } @SuppressWarnings("unchecked") private static void _traverseElementHandlerInterfaces(Class clazz, Collection> result) { // Add implemented interfaces (that are inherited from ElementHandler) for (Class inf : clazz.getInterfaces()) { if (!ElementHandler.class.isAssignableFrom(inf)) continue; result.add((Class)inf); } // Traverse parent Class superType = clazz.getSuperclass(); if (superType!=null) _traverseElementHandlerInterfaces(superType, result); } @Override public String toString() { if (!id.isEmpty()) return id; StringBuilder sb = new StringBuilder(); sb.append("["); int i=0; for (ElementHandler eh : super.getAll()) { if (i++>0) sb.append(", "); sb.append( eh.getClass().getSimpleName() ); } sb.append("]"); return sb.toString(); } public ElementClass newClassWith(ElementHandler... addedHandlers) { if (addedHandlers.length == 0) return this; Collection newHandlers = new ArrayList(getAll()); for (ElementHandler h : addedHandlers) newHandlers.add(h); return ElementClass.compile(newHandlers).setId(id); } public ElementClass newClassWith(boolean check, ElementHandler... addedHandlers) { if (addedHandlers.length == 0) return this; Collection newHandlers = new ArrayList(getAll()); for (ElementHandler h : addedHandlers) newHandlers.add(h); return ElementClass.compile(newHandlers, check).setId(id); } public ElementClass newClassWith(Collection addedHandlers) { if (addedHandlers.isEmpty()) return this; Collection newHandlers = new ArrayList(getAll()); newHandlers.addAll(addedHandlers); return ElementClass.compile(newHandlers).setId(id); } public ElementClass setId(String id) { this.id = id; return this; } public String getId() { return id; } }