/*******************************************************************************
* 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)
{
assertClassValid(contributions);
return new ElementClass(contributions);
}
public static ElementClass compile(Collection contributions, boolean check)
{
if(check) assertClassValid(contributions);
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;
}
}