/*******************************************************************************
* Copyright (c) 2007, 2014 compeople AG and others.
* 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:
* compeople AG - initial API and implementation
*******************************************************************************/
package org.eclipse.riena.internal.core.injector.extension;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IContributor;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.RegistryFactory;
import org.eclipse.riena.core.injector.extension.ExtensionDescriptor;
/**
* The <code>ExtensionMapper</code> maps interfaces to extensions. Extension
* properties (attributes, sub-elements and element value) can then accessed by
* <i>getters</i> in the interface definition. It is only necessary to define
* the interfaces for the mapping. The Riena extension injector creates dynamic
* proxies fulfilling these interfaces for retrieving the data from the
* <code>ExtensionRegistry</code>.<br>
*
* The ExtensionMapper does not evaluate the extension schema, so it can only
* trust that the extension and the interface match.
* <p>
* The basic rules for the mapping are:
* <ul>
* <li>one interface maps to one extension element type</li>
* <li>the interface has to be annotated with <code>@ExtensionInterface</code></li>
* <li>interface methods can contain <i>getters</i> prefixed with:
* <ul>
* <li>get...</li>
* <li>is...</li>
* <li>create...</li>
* </ul>
* Such prefixed methods enforce a default mapping to attribute or element
* names, i.e. the remainder of the methods name is interpreted as the attribute
* or element name. A simple name mangling is performed, e.g for the method
* <code>getDatabaseURL</code> the mapping looks for the attribute name
* <code>databaseURL</code>.
* <li>To enforce another name mapping a method can be annotated with
* <code>@MapName("name")</code>. The <i>name</i> specifies the name of the
* element or attribute. The extension element�s value can be retrieved by
* annotating the method with <code>@MapContent()</code>. The return type must
* be <code>String</code>. The method names of such annotated methods can be
* arbitrary</li>
* </ul>
* The return type of a method indicates how the value of an attribute will be
* converted. If the return type is
* <ul>
* <li>a <i>primitive</i> type or <code>java.lang.String</code>than the mapping
* converts the attribute's value to the corresponding type.</li>
* <li>an interface or an array of interfaces annotated with
* <code>@ExtensionInterface</code> than the mapping tries to resolve to a
* nested element or to nested elements.</li>
* <li><code>org.osgi.framework.Bundle</code> than the methods returns the
* contributing bundle.</li>
* <li><code>org.eclipse.core.runtime.IConfigurationElement</code> than the
* methods returns the underlying configuration element.</li>
* <li><code>java.lang.Class</code> than the attribute is interpreted as a class
* name and a class instance will be returned.</li>
* <li><code>an enum</code> than the attribute is interpreted as the value (case
* insensitive) of the corresponding enum type. <code>null</code> will be
* returned if the attribute�s value is not given, i.e. <code>null</code> or the
* empty string.</li>
* <li>and finally if none of the above matches the mapping tries to create an
* new instance of the attribute�s value (interpreted as class name) each time
* it is called. If the extension attribute is not defined <code>null</code>
* will be returned.</li>
* </ul>
*/
public final class ExtensionMapper {
private ExtensionMapper() {
throw new IllegalStateException("Should never be invoked!"); //$NON-NLS-1$
}
/**
* Static method to read extensions
*
* @param <T>
* @param symbolReplace
* on true symbol replacement occurs (via
* {@code StringVariableManager})
* @param extensionDesc
* @param componentType
* @return
* @throws IllegalArgumentException
* if extension point is not existent
*/
public static <T> T[] map(final boolean symbolReplace, final ExtensionDescriptor extensionDesc,
final Class<T> componentType, final boolean nonSpecific) {
final IExtensionRegistry extensionRegistry = RegistryFactory.getRegistry();
final List<Object> list = new ArrayList<Object>();
boolean atLeastOneExtensionPointExists = false;
for (final String extensionPointId : extensionDesc.getExtensionPointId().compatibleIds()) {
final int lastDot = extensionPointId.lastIndexOf('.');
if (lastDot < 1) {
throw new IllegalStateException("ExtensionPointId has no namespace!"); //$NON-NLS-1$
}
final IExtensionPoint extensionPoint = extensionRegistry.getExtensionPoint(extensionPointId);
if (extensionPoint == null) {
continue;
}
atLeastOneExtensionPointExists = true;
if (nonSpecific) {
if (extensionDesc.isHomogeneous()) {
for (final IConfigurationElement element : extensionPoint.getConfigurationElements()) {
list.add(InterfaceBeanFactory.newInstance(symbolReplace, componentType, element));
}
} else {
list.add(InterfaceBeanFactory
.newInstance(symbolReplace, componentType, new Wrapper(extensionPoint)));
}
} else {
for (final IExtension extension : extensionPoint.getExtensions()) {
list.add(InterfaceBeanFactory.newInstance(symbolReplace, componentType, new Wrapper(extension)));
}
}
}
if (!atLeastOneExtensionPointExists) {
throw new IllegalArgumentException("Extension point " + extensionDesc.getExtensionPointId() //$NON-NLS-1$
+ " does not exist"); //$NON-NLS-1$
}
return list.toArray((T[]) Array.newInstance(componentType, list.size()));
}
/**
* Wrap an IExtension or an IExtensionPoint so that it behaves almost like a
* IConfigurationElement.
*/
private final static class Wrapper implements IConfigurationElement {
private final IExtensionPoint wrappedExtensionPoint;
private final IExtension wrappedExtension;
private Wrapper(final IExtensionPoint extensionPoint) {
Assert.isNotNull(extensionPoint, "wrappedExtensionPoint must not be null."); //$NON-NLS-1$
this.wrappedExtensionPoint = extensionPoint;
this.wrappedExtension = null;
}
private Wrapper(final IExtension extension) {
Assert.isNotNull(extension, "extension must not be null."); //$NON-NLS-1$
this.wrappedExtension = extension;
this.wrappedExtensionPoint = null;
}
/*
* {@inheritDoc}
*/
public Object createExecutableExtension(final String propertyName) throws CoreException {
throw new UnsupportedOperationException("IExtension/Point does not support createExecutableExtension()"); //$NON-NLS-1$
}
/*
* {@inheritDoc}
*/
public String getAttribute(final String name) {
return null;
}
/*
* {@inheritDoc}
*/
public String getAttribute(final String attrName, final String locale) {
return null;
}
/*
* {@inheritDoc}
*/
public String getAttributeAsIs(final String name) {
return null;
}
/*
* {@inheritDoc}
*/
public String[] getAttributeNames() {
return new String[0];
}
/*
* {@inheritDoc}
*/
public IConfigurationElement[] getChildren() {
if (wrappedExtension != null) {
return wrappedExtension.getConfigurationElements();
}
final List<IConfigurationElement> elements = new ArrayList<IConfigurationElement>();
for (final IExtension extension : wrappedExtensionPoint.getExtensions()) {
elements.addAll(Arrays.asList(extension.getConfigurationElements()));
}
return elements.toArray(new IConfigurationElement[elements.size()]);
}
/*
* {@inheritDoc}
*/
public IConfigurationElement[] getChildren(final String name) {
final IConfigurationElement[] configurationElements = wrappedExtension != null ? wrappedExtension
.getConfigurationElements() : wrappedExtensionPoint.getConfigurationElements();
final List<IConfigurationElement> elements = new ArrayList<IConfigurationElement>();
for (final IConfigurationElement configurationElement : configurationElements) {
if (configurationElement.getName().equals(name)) {
elements.add(configurationElement);
}
}
return elements.toArray(new IConfigurationElement[elements.size()]);
}
/*
* {@inheritDoc}
*/
public IContributor getContributor() {
return wrappedExtension != null ? wrappedExtension.getContributor() : wrappedExtensionPoint
.getContributor();
}
/*
* {@inheritDoc}
*/
public IExtension getDeclaringExtension() {
throw new UnsupportedOperationException("IExtensionPoint does not support getDeclaringExtension()"); //$NON-NLS-1$
}
/*
* {@inheritDoc}
*/
public String getName() {
return wrappedExtension != null ? wrappedExtension.getLabel() : wrappedExtensionPoint.getLabel();
}
/*
* {@inheritDoc}
*/
@SuppressWarnings("deprecation")
public String getNamespace() {
// This method is also @deprecated so it must use the @deprecated methods
return wrappedExtension != null ? wrappedExtension.getNamespace() : wrappedExtensionPoint.getNamespace();
}
/*
* {@inheritDoc}
*/
public String getNamespaceIdentifier() {
return wrappedExtension != null ? wrappedExtension.getNamespaceIdentifier() : wrappedExtensionPoint
.getNamespaceIdentifier();
}
/*
* {@inheritDoc}
*/
public Object getParent() {
throw new UnsupportedOperationException("IExtensionPoint does not support getParent()"); //$NON-NLS-1$
}
/*
* {@inheritDoc}
*/
public String getValue() {
throw new UnsupportedOperationException("IExtensionPoint does not support getValue()"); //$NON-NLS-1$
}
/*
* {@inheritDoc}
*/
public String getValue(final String locale) {
throw new UnsupportedOperationException("IExtensionPoint does not support getValue(String locale)"); //$NON-NLS-1$
}
/*
* {@inheritDoc}
*/
public String getValueAsIs() {
throw new UnsupportedOperationException("IExtensionPoint does not support getValueAsIs()"); //$NON-NLS-1$
}
/*
* {@inheritDoc}
*/
public boolean isValid() {
return wrappedExtension != null ? wrappedExtension.isValid() : wrappedExtensionPoint.isValid();
}
}
}