Avoid unnecessary ElementClass validation work
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / element / ElementClass.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.g2d.element;
13
14 import java.lang.annotation.ElementType;
15 import java.lang.annotation.Retention;
16 import java.lang.annotation.RetentionPolicy;
17 import java.lang.annotation.Target;
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.HashSet;
22 import java.util.Set;
23
24 import org.simantics.g2d.diagram.impl.AbstractHandlerClass;
25 import org.simantics.g2d.element.handler.ElementHandler;
26 import org.simantics.g2d.element.handler.HandleMouseEvent;
27 import org.simantics.g2d.element.handler.InternalSize;
28 import org.simantics.g2d.element.handler.Move;
29 import org.simantics.g2d.element.handler.Rotate;
30 import org.simantics.g2d.element.handler.SceneGraph;
31 import org.simantics.g2d.element.handler.Transform;
32 import org.simantics.g2d.element.handler.Validator;
33
34 /**
35  * ElementClass is a class of elements. It consists of element handlers.
36  * Each handler contributes functions to some aspects of the diagram.
37  * <p>
38  * Handlers are ordered. For instance, if there are multiple Paint handlers,
39  * the paint order is determined by the order of the handlers in the class.
40  * <p>
41  * The minimum requirement is implementation for the following interfaces:
42  *    @see Transform
43  *    @see InternalSize
44  * 
45  * Typical element class has the following implementations:
46  *    @see Transform
47  *    @see SceneGraph
48  *    @see Move
49  *    @see Rotate
50  *    @see InternalSize or @see Scale
51  *    @see Selectable
52  * 
53  * Interactive element classes have implementation to:
54  *    @see HandleMouseEvent
55  * 
56  * See also:
57  *    @see Validator
58  * 
59  * See package org.simantics.g2d.element.handler for handler interfaces.
60  * See package org.simantics.g2d.element.handler.impl for common implementations.
61  * Note that some implementations implement several interface. Also note that
62  * some handler interfaces may be implemented several times (e.g. Paint,
63  * Validator, ToolTip, Pick). In contrast, some handlers may have at most one
64  * implementation, such as Transform, Move, Rotate, Scale, Size.
65  * <p>
66  * Tip: Use SymbolUtil (CanvasParticipant) to manage graphical resources (images).
67  * 
68  * @author Toni Kalajainen
69  */
70 public final class ElementClass extends AbstractHandlerClass<ElementHandler> {
71
72     private static final long serialVersionUID = -446421782709451743L;
73
74     /** Handler with this annotation may have at most one implementation */
75     @Retention(RetentionPolicy.RUNTIME)
76     @Target(ElementType.TYPE)
77     public static @interface Single {}
78
79     /** Handler with this annotation must be implemented (once or more) */
80     @Retention(RetentionPolicy.RUNTIME)
81     @Target(ElementType.TYPE)
82     public static @interface Required {}
83
84     private static final Class<?>[] REQUIRED_HANDLERS =
85         new Class<?>[] {Transform.class, InternalSize.class};
86
87     private String id = "";
88
89     /**
90      * Compile new element class from a set of handler
91      * @param contributions
92      * @return
93      */
94     public static ElementClass compile(Collection<ElementHandler> contributions)
95     {
96         return new ElementClass(contributions);
97     }
98
99     public static ElementClass compile(Collection<ElementHandler> contributions, boolean check)
100     {
101         return new ElementClass(contributions, check);
102     }
103
104     /**
105      * Compile new element class from a set of handler
106      * @param contributions
107      * @return
108      */
109     public static ElementClass compile(ElementHandler... contributions)
110     {
111         if (contributions.length == 0)
112             return new ElementClass(Arrays.asList(contributions));
113         ArrayList<ElementHandler> al = new ArrayList<ElementHandler>(contributions.length);
114         for (ElementHandler eh : contributions)
115             al.add(eh);
116         return new ElementClass(al);
117     }
118
119     ElementClass(Collection<ElementHandler> contributions) {
120         super(contributions);
121         assertClassValid(contributions);
122     }
123
124     ElementClass(Collection<ElementHandler> contributions, boolean check) {
125         super(contributions);
126         if(check) assertClassValid(contributions);
127     }
128
129     /**
130      * Validates that handler is valid.
131      * 
132      * @param contributions
133      * @return
134      */
135     public static void assertClassValid(Collection<ElementHandler> contributions)
136     {
137         // 1. Verify requirements
138         nextRequirement:
139             for (Class<?> requiredClass : REQUIRED_HANDLERS) {
140                 for (ElementHandler eh : contributions)
141                     if (requiredClass.isInstance(eh))
142                         continue nextRequirement;
143                 throw new Error("Element class does not implement "+requiredClass.getName());
144             }
145
146     // 2. Verify singletons
147     // 2.1. Collect implemented handlers
148     Set<Class<ElementHandler>> implementedHandlerInterfaces = new HashSet<Class<ElementHandler>>();
149     for (ElementHandler eh : contributions)
150         _traverseElementHandlerInterfaces(eh.getClass(), implementedHandlerInterfaces);
151
152     // 2.2. Verify singletons are implemented only once
153     for (Class<ElementHandler> ehc : implementedHandlerInterfaces)
154     {
155         if (!_isSingletonHandler(ehc)) continue;
156         int implementationCount = 0;
157         for (ElementHandler eh : contributions)
158         {
159             if (!ehc.isInstance(eh)) continue;
160             implementationCount++;
161         }
162         if (implementationCount>1)
163             throw new Error("Element class has "+implementationCount+" implementations to a _singleton_ element handler \""+ehc.getName()+"\": " + contributions);
164     }
165     }
166
167     private static boolean _isSingletonHandler(Class<ElementHandler> elementHandlerClass)
168     {
169         Single s = elementHandlerClass.getAnnotation(Single.class);
170         return s != null;
171     }
172
173     @SuppressWarnings("unchecked")
174     private static void _traverseElementHandlerInterfaces(Class<?> clazz, Collection<Class<ElementHandler>> result)
175     {
176         // Add implemented interfaces (that are inherited from ElementHandler)
177         for (Class<?> inf : clazz.getInterfaces())
178         {
179             if (!ElementHandler.class.isAssignableFrom(inf)) continue;
180             result.add((Class<ElementHandler>)inf);
181         }
182
183         // Traverse parent
184         Class<?> superType = clazz.getSuperclass();
185         if (superType!=null)
186             _traverseElementHandlerInterfaces(superType, result);
187     }
188
189     @Override
190     public String toString() {
191         if (!id.isEmpty())
192             return id;
193
194         StringBuilder sb = new StringBuilder();
195         sb.append("[");
196         int i=0;
197         for (ElementHandler eh : super.getAll())
198         {
199             if (i++>0) sb.append(", ");
200             sb.append( eh.getClass().getSimpleName() );
201         }
202         sb.append("]");
203         return sb.toString();
204     }
205
206     public ElementClass newClassWith(ElementHandler... addedHandlers) {
207         if (addedHandlers.length == 0)
208             return this;
209         Collection<ElementHandler> newHandlers = new ArrayList<ElementHandler>(getAll());
210         for (ElementHandler h : addedHandlers)
211             newHandlers.add(h);
212         return ElementClass.compile(newHandlers).setId(id);
213     }
214
215     public ElementClass newClassWith(boolean check, ElementHandler... addedHandlers) {
216         if (addedHandlers.length == 0)
217             return this;
218         Collection<ElementHandler> newHandlers = new ArrayList<ElementHandler>(getAll());
219         for (ElementHandler h : addedHandlers)
220             newHandlers.add(h);
221         return ElementClass.compile(newHandlers, check).setId(id);
222     }
223
224     public ElementClass newClassWith(Collection<ElementHandler> addedHandlers) {
225         if (addedHandlers.isEmpty())
226             return this;
227         Collection<ElementHandler> newHandlers = new ArrayList<ElementHandler>(getAll());
228         newHandlers.addAll(addedHandlers);
229         return ElementClass.compile(newHandlers).setId(id);
230     }
231
232     public ElementClass setId(String id) {
233         this.id = id;
234         return this;
235     }
236
237     public String getId() {
238         return id;
239     }
240
241 }