/******************************************************************************* * Copyright (c) 2008 Conselleria de Infraestructuras y Transporte, Generalitat * de la Comunitat Valenciana . 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: Francisco Javier Cano Muñoz (Prodevelop) – Initial implementation. * ******************************************************************************/ package org.eclipse.papyrus.uml.diagram.common.util; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.Platform; import org.eclipse.papyrus.uml.diagram.common.Activator; /** * A basic simple IExtensionPoint parser. It parses an IExtensionPoint and * stores values in instances of the specified Classes specified. It uses java * reflection to get the elements and their attributes to parse. It can handle * String, Boolean and Object attributes. It can handle any IExtensionPoint as * long as the proper Classes are provided. <br> * It needs the IExtensionPoint string ID. It needs an Array of Classes to * create instances of that kind. If the Classes are internal to another Class, * an instance of the enclosing Class is required. <br> * The Classes provided must follow these rules: * <ul> * <li>Each Class simple name must be equal to one of the elements defined in the IExtensionPoint to be parsed.</li> * <li>There must be one Class for each one of the elements defined in the IExtensionPoint.</li> * <li>Each Class must have one attribute of type String, Boolean or Object for each attribute defined for its matching element in the * IExtensionPoint.</li> * <li>Each Class must have one attribute of type List for each type of child element it can have as defined in the IExtensionPoint. The name of this * List attribute will be equal to the type of elements to be stored in it.</li> * <li>Use Boolean instead of boolean, this allows that field to be null, indicating it has not been initialized.</li> * </ul> * <br> * The method parseExtensionPoint will return a List of Objects with a data structure similar to the * one defined in the IExtensionPoint. * * @author <a href="mailto:fjcano@prodevelop.es">Francisco Javier Cano Muñoz</a> * */ public class ExtensionPointParser { /** * <Class>es from which create instances. */ private Class<Object>[] classes = null; /** * Enclosing instance for internal <Class>es. */ private Object enclosingInstance = null; /** * <IExtensionPoint> full name identifier. */ private String extensionPointID = null; /** * Constructor without enclosing <Class>. * * @param extensionPointID * @param classes */ public ExtensionPointParser(String extensionPointID, Class<Object>[] classes) { this.classes = classes; this.extensionPointID = extensionPointID; } /** * Constructor with enclosing <Class>. * * @param extensionPointID * @param classes * @param enclosingInstance */ // @unused public ExtensionPointParser(String extensionPointID, Class<Object>[] classes, Object enclosingInstance) { this.classes = classes; this.extensionPointID = extensionPointID; this.enclosingInstance = enclosingInstance; } public Class<Object>[] getClasses() { return classes; } public String getExtensionPointID() { return extensionPointID; } public Class getEnclosingClass() { if(getEnclosingInstance() != null) { return getEnclosingInstance().getClass(); } return null; } public Object getEnclosingInstance() { return enclosingInstance; } /** * Parses the <IExtensionPoint> specified in the constructor. Returns a * <List> of <Object> instances created from the <Class>es provided in the * constructor. Fields with null value in the returned <Object>s have not * been initialized, as those values were not specified in the * <IExtensionPoint>. * * @return */ public List<Object> parseExtensionPoint() { if(getExtensionPointID() == null || getClasses() == null || getClasses().length <= 0) { return Collections.EMPTY_LIST; } IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry(); IExtensionPoint extensionPoint = extensionRegistry.getExtensionPoint(getExtensionPointID()); // In case the extension point does not exist, show an error if(extensionPoint == null) { Activator.getDefault().logError("The Extension Point ID provided does not exist", null); return Collections.EMPTY_LIST; } // parse every root IConfigurationElement in this IExtensionPoint. List<Object> objects = new ArrayList<Object>(); for(IConfigurationElement element : extensionPoint.getConfigurationElements()) { Object object = parseConfigurationElement(element); if(object != null) { objects.add(object); } } return objects; } /** * Parse an <IConfigurationElement>, including its attributes and its * children elements, * * @param element * @return */ protected Object parseConfigurationElement(IConfigurationElement element) { if(element == null) { return null; } Object object = createInstance(element); if(object == null) { return null; } // Added by gmerin to store the pluginID that contributed this object if(object instanceof IObjectWithContributorId) { String pluginID = element.getContributor().getName(); ((IObjectWithContributorId)object).setContributorId(pluginID); } // end gmerin for(Field field : object.getClass().getDeclaredFields()) { if(field.getType().isAssignableFrom(List.class) && !field.getType().isAssignableFrom(Object.class)) { // List fields are handled later continue; } String attribute = field.getName(); if(attribute == null || attribute.contains("$")) { // a field with "$" is a pointer to the enclosing instance; and // never accessible continue; } Object value = parseAttribute(element, attribute); if(!(value instanceof String)) { if(field.getType().isAssignableFrom(List.class)) { try { if(field.get(object) == null) { // if a list without value, set to EMPTY_LIST field.set(object, Collections.EMPTY_LIST); } } catch (IllegalArgumentException e) { error("Cannot acces field " + attribute, e); } catch (IllegalAccessException e) { error("Cannot acces field " + attribute, e); } } continue; } String valueString = (String)value; try { if(field.getType().isAssignableFrom(Object.class)) { // an Object field is instantiated Object instantiation = null; try { instantiation = element.createExecutableExtension(attribute); field.set(object, instantiation); } catch (CoreException e) { error("Cannot create instance of " + valueString, e); instantiation = null; } } else if(field.getType().isAssignableFrom(Boolean.class)) { // a Boolean field is parsed to its primitive value. field.set(object, Boolean.valueOf(valueString)); } else { field.set(object, valueString); } } catch (IllegalAccessException ex) { error("Cannot acces field " + attribute, ex); return null; } catch (IllegalArgumentException ex) { error("Illegal value for " + attribute, ex); return null; } } // parse all children element List<Object> objects = new ArrayList<Object>(); for(IConfigurationElement child : element.getChildren()) { Object parsedChild = parseConfigurationElement(child); if(parsedChild != null) { objects.add(parsedChild); } } // add those children element to the proper <List> attribute addChildrenToInstance(object, objects); // object ready! return object; } /** * Gets an attribute from an <IconfigurationElement>. * * @param element * @param attribute * @return */ protected Object parseAttribute(IConfigurationElement element, String attribute) { return element.getAttribute(attribute); } /** * Adds children instances to the proper <List> attribute of the instance * parent. * * @param instance * @param children */ protected void addChildrenToInstance(Object instance, List<Object> children) { for(Object child : children) { String name = child.getClass().getSimpleName(); try { Field field = instance.getClass().getField(name); Object value = field.get(instance); if(field.getType().isAssignableFrom(List.class)) { if(value == null) { value = new ArrayList<Object>(); field.set(instance, value); } ((List)value).add(child); } } catch (NoSuchFieldException ex) { error("No field named " + name, ex); return; } catch (IllegalAccessException ex) { error("Field " + name + " not accessible", ex); return; } } } /** * Creates an instance of an <Object> searching for the matching <Class> by * simple name. The list of <Class>es to be used are provided in the * constructor. Internal <Class>es must also provide an enclosing instance * in the constructor. * * @param element * @return */ protected Object createInstance(IConfigurationElement element) { String name = element.getName(); for(Class<Object> clazz : getClasses()) { if(clazz.getSimpleName().equals(name)) { try { Class[] parameters = null; if(getEnclosingClass() != null) { parameters = new Class[]{ getEnclosingClass() }; } else { parameters = new Class[0]; } Constructor<Object> constructor = clazz.getConstructor(parameters); Object[] arguments = null; if(getEnclosingInstance() != null) { arguments = new Object[]{ getEnclosingInstance() }; } else { arguments = new Object[0]; } return constructor.newInstance(arguments); } catch (IllegalAccessException ex) { error("Cannot access constructor for " + name, ex); return null; } catch (IllegalArgumentException ex) { error("Cannot create instance of type " + name, ex); return null; } catch (InstantiationException ex) { error("Cannot create instance of type " + name, ex); return null; } catch (NoSuchMethodException ex) { error("Cannot find constructor for " + name, ex); return null; } catch (InvocationTargetException ex) { error("Cannot invoke constructor for " + name, ex); return null; } } } return null; } private void error(String message, Throwable throwable) { Activator.getDefault().logError(message, throwable); } }