blob: 9806f26f151d54e373449690e8fbef70e442c290 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2002, 2010 IBM Corporation 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:
* IBM Corporation - initial API and implementation
****************************************************************************/
package org.eclipse.gmf.runtime.common.core.service;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.gmf.runtime.common.core.internal.CommonCorePlugin;
import org.eclipse.gmf.runtime.common.core.internal.CommonCoreStatusCodes;
import org.eclipse.gmf.runtime.common.core.util.Log;
import org.osgi.framework.Bundle;
import com.ibm.icu.util.StringTokenizer;
/**
* Concrete subclasses can be used to assist in parsing service provider
* descriptors to filter out and delay loading of service providers that do not
* apply.
* <P>
* This abstract class contains a set of useful utilities for such concrete
* subclasses.
*
* @author melaasar, mmostafa
*/
public class AbstractProviderConfiguration {
/**
* The name of the 'object' XML attribute.
*/
protected static final String OBJECT = "object"; //$NON-NLS-1$
/**
* The name of the 'id' XML attribute.
*/
protected static final String ID = "id"; //$NON-NLS-1$
/**
* The name of the 'class' XML attribute.
*/
protected static final String CLASS = "class"; //$NON-NLS-1$
/**
* The name of the 'method' XML attribute.
*/
protected static final String METHOD = "method"; //$NON-NLS-1$
/**
* The name of the 'method' XML attribute.
*/
protected static final String STATIC_METHOD = "staticMethod"; //$NON-NLS-1$
/**
* The name of the 'name' XML attribute.
*/
protected static final String NAME = "name"; //$NON-NLS-1$
/**
* The name of the 'value' XML attribute.
*/
protected static final String VALUE = "value"; //$NON-NLS-1$
/**
* The name of the 'notValue' XML attribute.
*/
protected static final String NOT_VALUE = "notValue"; //$NON-NLS-1$
/**
* The name of the 'null' XML attribute value.
*/
protected static final String NULL = "null"; //$NON-NLS-1$
/**
* the name of the context param
*/
protected static final String contextParam = "%Context"; //$NON-NLS-1$
/**
* A map to store previously successful class lookups.
*/
private static Map isAssignableTable = new HashMap();
/**
* A map to store previously failed class lookups.
*/
private static Map isNotAssignableTable = new HashMap();
/**
* A map to hold the bundle to exception list
*/
private static Map bundleToExceptionsSetMap = new HashMap();
/**
* a map of classes that get asked for methods they do not contain, by
* the provider, the map is a class to a Set of method signatures
*/
private static ClassToMethodSignaturesSetMap passiveClasses =
new ClassToMethodSignaturesSetMap();
/**
* a class to cach passive classes, passive classes are the classes we asked
* for a method with a specific signature and they faild to find it. The cach used
* so in the next time we can tell if the method does not exists oin the class
* without calling getMethod by reflection, which improves the performance
* @author mmostafa
*
*/
private static class ClassToMethodSignaturesSetMap{
/**
* internal map for the cach, it is a map of Class to Set of method signature Strings
*/
Map classToMethodSignaturesSetMap = new HashMap();
/**
* adds a class and a method signature to the passive class cach
* @param clazz the class
* @param signature the method signature
*/
public void addMethod(Class clazz, String signature){
Set signatures = (Set)classToMethodSignaturesSetMap.get(clazz);
if (signatures==null){
signatures = new HashSet();
classToMethodSignaturesSetMap.put(clazz,signatures);
}
signatures.add(signature);
}
/**
* check if the class and the method signatrue are contained in the apssive collection,
* which means we do need need to call get method oon the class becuase we will not
* find it, this helps improving the performance.
* @param clazz
* @param signature
* @return
*/
public boolean contains(Class clazz, String signature){
Set signatures = (Set)classToMethodSignaturesSetMap.get(clazz);
if (signatures==null)
return false;
return signatures.contains(signature);
}
}
/**
* internal class used to cach Methods, so we do not call getMethod too often
* @author mmostafa
*
*/
private static class ClassToMethodSignatureToMethodCach{
/**
* internal map to hold the cached data, it is a map of Class => Map
* of Singature string => method
*/
Map classToMethodSignatureToMethod = new HashMap();
/**
* adds a <code>Method</code> to the cach
* @param clazz the class we got the method from
* @param methodSignature the method signature
* @param method the <code>Method</code>
*/
public void addMethod(Class clazz,String methodSignature, Method method ){
Map signatureToMethodMap = (Map)classToMethodSignatureToMethod.get(clazz);
if (signatureToMethodMap==null){
signatureToMethodMap = new HashMap();
classToMethodSignatureToMethod.put(clazz,signatureToMethodMap);
}
signatureToMethodMap.put(methodSignature,method);
}
/**
* gets a method from the cach using the class that owns it and the method
* signature.
* @param clazz the class that owns the method
* @param methodSignature the method signature
* @return the <code>Method</code> if found any, otherwise null
*/
public Method getMethod(Class clazz,String methodSignature){
Map signatureToMethodMap = (Map)classToMethodSignatureToMethod.get(clazz);
if (signatureToMethodMap !=null){
return (Method)signatureToMethodMap.get(methodSignature);
}
return null;
}
}
/**
* map for class to Method signature to method cach
*/
private static ClassToMethodSignatureToMethodCach
classToMethodSignatureToMethodCach = new ClassToMethodSignatureToMethodCach();
/**
* Gets the class name of <code>object</code>.
* @param object the object for which the class name is to be found.
* @return the class name
*/
static String getClassName( Object object ) {
String cn = object.getClass().getName();
return cn.substring( cn.lastIndexOf('.')+1);
}
/**
* A descriptor for an XML configuration element that identifies a class by
* name and optionally its methods.
*/
public static class ObjectDescriptor {
/**
* The name of the class.
*/
private String contextClassName;
/**
* The ID of the plugin that contains the class.
*/
private String contextClassPlugin;
/**
* <code>true</code> if a syntax error has occurred,
* <code>false</code> otherwise.
*/
private boolean syntaxError;
/**
* A list of method descriptors for the class.
*/
private final List methods;
/**
* A list of method descriptors for the class.
*/
private final List staticMethods;
/**
* Creates a new object descriptor from its configuration element.
*
* @param configElement
* The configuration element.
*/
public ObjectDescriptor(IConfigurationElement configElement) {
this(configElement, CLASS);
}
/**
* Creates a new object descriptor from its configuration element.
*
* @param configElement
* The configuration element.
* @param classNameTag
* The name of the 'class' XML attribute.
*/
public ObjectDescriptor(
IConfigurationElement configElement,
String classNameTag) {
String s = configElement.getAttribute(classNameTag);
if (s != null) {
int start = s.indexOf("(");//$NON-NLS-1$
if (start != -1) {
contextClassName = s.substring(0, start).trim();
int end = s.indexOf(")");//$NON-NLS-1$
if (end != -1 && end > start+1)
contextClassPlugin = s.substring(start+1, end);
} else
contextClassName = s.trim();
}
IConfigurationElement[] methodConfigs =
configElement.getChildren(METHOD);
IConfigurationElement[] staticMethodConfigs =
configElement.getChildren(STATIC_METHOD);
if (methodConfigs.length != 0) {
methods = new ArrayList(methodConfigs.length);
for (int i = 0; i < methodConfigs.length; i++) {
String name = methodConfigs[i].getAttribute(NAME);
if (name != null) {
try {
MethodDescriptor methodDescriptor =
new MethodDescriptor(name);
ValueDescriptor value =
new ValueDescriptor(methodConfigs[i]);
if (value != null)
methods.add(new MethodValueEntry(methodDescriptor, value));
} catch (Exception e) {
syntaxError = true;
Log.error(CommonCorePlugin.getDefault(), CommonCoreStatusCodes.SERVICE_FAILURE, configElement.getDeclaringExtension().getContributor().getName()+ ".plugin.xml extension [" + configElement.getDeclaringExtension().getExtensionPointUniqueIdentifier() + "]: invalid syntax for method [" + name + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
} else {
syntaxError = true;
Log.error(CommonCorePlugin.getDefault(), CommonCoreStatusCodes.SERVICE_FAILURE, configElement.getDeclaringExtension().getContributor().getName()+ ".plugin.xml extension [" + configElement.getDeclaringExtension().getExtensionPointUniqueIdentifier() + "] : missing method name"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
else
methods = Collections.EMPTY_LIST;
if (staticMethodConfigs.length != 0) {
staticMethods = new ArrayList(staticMethodConfigs.length);
for (int i = 0; i < staticMethodConfigs.length; i++) {
String name = staticMethodConfigs[i].getAttribute(NAME);
if (name != null) {
try {
StaticMethodDescriptor methodDescriptor =
new StaticMethodDescriptor(name);
ValueDescriptor value =
new ValueDescriptor(staticMethodConfigs[i]);
if (value != null)
staticMethods.add(new MethodValueEntry(methodDescriptor, value));
} catch (Exception e) {
syntaxError = true;
Log.error(CommonCorePlugin.getDefault(), CommonCoreStatusCodes.SERVICE_FAILURE, configElement.getDeclaringExtension().getContributor().getName()+ ".plugin.xml extension [" + configElement.getDeclaringExtension().getExtensionPointUniqueIdentifier() + "]: invalid syntax for method [" + name + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
} else {
syntaxError = true;
Log.error(CommonCorePlugin.getDefault(), CommonCoreStatusCodes.SERVICE_FAILURE, configElement.getDeclaringExtension().getContributor().getName()+ ".plugin.xml extension [" + configElement.getDeclaringExtension().getExtensionPointUniqueIdentifier() + "] : missing method name"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}else
staticMethods = Collections.EMPTY_LIST;
if (contextClassName != null)
contextClassName = contextClassName.intern();
if (contextClassPlugin != null)
contextClassPlugin = contextClassPlugin.intern();
}
/**
* Tests if the object descriptor applies to the given context object.
*
* @param object
* The context object.
* @return <code>true</code> if it applies; <code>false</code>
* otherwise
*/
public boolean sameAs(Object object) {
if (syntaxError)
return false;
Object targetObject = object;
if (contextClassName != null) {
if (!isAssignableTo(object.getClass(), contextClassName)) {
targetObject = getAdapter(object, contextClassName, contextClassPlugin);
if (targetObject == null)
return false;
}
}
for(Iterator iter = methods.iterator(); iter.hasNext();) {
MethodValueEntry entry = (MethodValueEntry)iter.next();
Object methodValue = invokeMethod(entry.method, targetObject);
if (methodValue == null || !entry.value.sameAs(methodValue))
return false;
}
for(Iterator iter = staticMethods.iterator(); iter.hasNext();) {
MethodValueEntry entry = (MethodValueEntry)iter.next();
Object methodValue = invokeStaticMethod((StaticMethodDescriptor)entry.method, targetObject);
if (methodValue == null || !entry.value.sameAs(methodValue))
return false;
}
return true;
}
}
/**
* A descriptor for an XML configuration element that identifies a method by
* name and its formal parameters.
*/
private static class MethodDescriptor {
protected String dataForIntialize = NULL;
/**
* The method name.
*/
private String name;
/**
* The array of method parameters.
*/
private Object parameterObjects[];
/**
* The array of method parameter types.
*/
private Class parameterTypes[];
/**
* The next cascading method descriptor.
*/
private MethodDescriptor next;
/**
* The list of method parameters.
*/
private List parameters;
/**
* the method signature
*
*/
private String signature = null;
protected MethodDescriptor(){
// empty
}
/**
* Creates a new method descriptor from a string representing the
* method's full cascading invocation with parameters.
* <P>
* The format of the string is:
* <P>
* <code>method_name([params])[.method_name([params])]*</code>
* <P>
* Where:
* <UL>
* <LI>the <i>params </i> are comma-separated string literals without
* double quotes.</LI>
* <LI>only string <i>params </i> are allowed (no texual representation
* of non-string params are allowed)</LI>
* </UL>
* <P>
* For example:
* <P>
* <code>getPropertyValue(Source_Connection).getName()</code>
*
* @param string
* the method invocation string
*/
public MethodDescriptor(String string) {
dataForIntialize = string;
}
protected boolean isInitialized(){
return (dataForIntialize==null);
}
protected void initialize() {
//check if already initialized
if (isInitialized())
return;
try {
// set method name
dataForIntialize = parseName(dataForIntialize.trim());
// set method parameters
dataForIntialize = parseParameterList(dataForIntialize.trim());
// fill the parameter objects and types arrays
if (parameters != null && !parameters.isEmpty()) {
Collections.reverse(parameters);
parameterObjects = parameters.toArray();
parameterTypes = new Class[parameterObjects.length];
for (int i = 0; i < parameterObjects.length; i++) {
String p = (String) parameterObjects[i];
int objIndex = p.indexOf("[object]"); //$NON-NLS-1$
boolean isObject = objIndex >= 0;
int parseAsIndex = p.indexOf(":::"); //$NON-NLS-1$
try {
if (isObject && (parseAsIndex >= 0))
// assume order: [object] before type:::param
assert (objIndex < parseAsIndex);
if (parseAsIndex >= 0) {
// "type:::param"
String parseAs =
p.substring((isObject ? 8 : 0), parseAsIndex);
String value =
p.substring(parseAsIndex + 3, p.length());
if (parseAs.equalsIgnoreCase("int")) { //$NON-NLS-1$
parameterTypes[i] = Integer.class;
parameterObjects[i] = Integer.decode(value);
} else if (parseAs.equalsIgnoreCase("bool")) { //$NON-NLS-1$
parameterTypes[i] = Boolean.class;
parameterObjects[i] = Boolean.valueOf(value);
} else if (parseAs.equalsIgnoreCase("double")) { //$NON-NLS-1$
parameterTypes[i] = Double.class;
parameterObjects[i] = Double.valueOf(value);
}
// if [object] present, set type to Object
if (isObject)
parameterTypes[i] = Object.class;
} else if (isObject) { // "[object]param"
String value = p.substring(8, p.length());
parameterTypes[i] = Object.class;
parameterObjects[i] = value;
} else // "param"
parameterTypes[i] = String.class;
} catch (Exception e) {
String value =
p.substring(
((parseAsIndex >= 0) ? parseAsIndex + 3 : 0),
p.length());
parameterObjects[i] = value;
parameterTypes[i] = String.class;
}
}
}
parameters = null;
// set method parameters
if (dataForIntialize.length() != 0) {
if (dataForIntialize.charAt(0) != '.')
throw new IllegalArgumentException();
next = new MethodDescriptor(dataForIntialize.substring(1).trim());
}
if (this.name != null)
name = name.intern();
}finally{
dataForIntialize = null;
}
}
/**
* Parses and returns the method name in a method invocation string.
*
* @param string
* the method invocation string
* @return the method name
*/
protected String parseName(String string) {
int index = string.indexOf('(');
if (index == -1)
throw new IllegalArgumentException();
name = string.substring(0, index).trim();
return string.substring(index + 1);
}
/**
* Parses a method invocation string for the list of parameters, which
* are placed in the <code>parameters</code> field.
*
* @param string
* the method invocation string
* @return the end part of the method invocation string that has not
* been parsed.
*/
protected String parseParameterList(String string) {
int index = -1;
String paramStr = null;
while (paramStr == null) {
index = string.indexOf(')', index + 1);
if (index == -1)
throw new IllegalArgumentException();
if (index == 0 || string.charAt(index - 1) != '\\')
paramStr = string.substring(0, index);
}
if (paramStr.length() != 0) {
parameters = new ArrayList();
parseParameters(paramStr.trim());
}
return string.substring(index + 1);
}
/**
* Parses a string containing a list of method parameters and stores
* them in the <code>parameters</code> field.
*
* @param string
* the comma-separated list of method parameters.
*/
private void parseParameters(String string) {
int index = string.indexOf(',');
if (index != -1 && string.charAt(index - 1) != '\\') {
parseParameters(string.substring(index + 1).trim());
parameters.add(string.substring(0, index));
} else
parameters.add(string);
}
/**
* Returns the method name.
*
* @return the method name
*/
public String getName() {
return name;
}
/**
* Sets the method name.
* @param the method name
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns an array of string params.
*
* @return the parameters
*/
public Object[] getParameters() {
return parameterObjects;
}
/**
* Returns an array of parameter classes.
*
* @return the parameter types
*/
public Class[] getParameterTypes() {
return parameterTypes;
}
/**
* sets the the array of params.
* @param paramters
*/
protected void setParameters(Object[] paramters) {
parameterObjects = paramters;
}
/**
* sets the the array of parameter types.
* @param paramtersTypes
*/
public void setParameterTypes(Class[] paramterTypes) {
this.parameterTypes = paramterTypes;
}
/**
* Returns the next cascading method descriptor, if any.
*
* @return the next method descriptor, or <code>null</code> if there
* is none
*/
public MethodDescriptor getNext() {
return next;
}
/**
* sets the next cascading method descriptor, if any.
* @param next
*/
protected void setNext(MethodDescriptor next) {
this.next = next;
}
/**
* Gets the Paramters List
* @return The list of method parameters.
*/
protected List getParamtersList(){
return parameters;
}
/**
* utility method used to get the signature of the method this method descriptor
* descripe.
* @return the signature of the method
*/
public String getSignature(){
if (this.signature==null){
StringBuffer sb =
new StringBuffer();
sb.append(name);
sb.append('(');
if(parameterTypes!=null)
for(int index= 0 ; index < parameterTypes.length ; index++){
Class clazz = parameterTypes[index];
sb.append(clazz.getName());
if(index<parameterTypes.length-1)
sb.append(',');
}
sb.append(')');
signature = sb.toString();
}
return signature;
}
}
private static class StaticMethodDescriptor extends MethodDescriptor {
/**
* the plugin Name
*/
private String pluginID;
/**
* the Class Name
*/
private String className;
/**
* Creates a new method descriptor from a string representing the
* method's full cascading invocation with parameters.
* <P>
* The format of the string is:
* <P>
* <code>PluginID\ClassName.method_name([params])[.method_name([params])]*</code>
* <P>
* Where:
* <UL>
* <LI>the <i>params </i> are comma-separated string literals without
* double quotes.</LI>
* <LI>only string <i>params </i> are allowed (no texual representation
* of non-string params are allowed)</LI>
* <LI>to identify a parameter as the current context you put %
* </UL>
* <P>
* For example:
* <P>
* <code>MyPluginID\MyClass.MyStaticFunction(%,"some value")</code>
*
* @param string
* the method invocation string
*/
public StaticMethodDescriptor(String string) {
dataForIntialize = string;
}
public void initialize() {
// check if already initialized
if (isInitialized())
return;
try {
// set plugin ID
dataForIntialize = parsePluginID(dataForIntialize.trim());
// set class Name
dataForIntialize = parseClassName(dataForIntialize.trim());
// set method name
dataForIntialize = parseName(dataForIntialize.trim());
// set method parameters
dataForIntialize = parseParameterList(dataForIntialize.trim());
List parameters = getParamtersList();
// fill the parameter objects and types arrays
if (parameters != null && !parameters.isEmpty()) {
Collections.reverse(parameters);
Object[] parameterObjects = parameters.toArray();
Class[] parameterTypes = new Class[parameterObjects.length];
for (int i = 0; i < parameterObjects.length; i++) {
String p = (String) parameterObjects[i];
int objIndex = p.indexOf("[object]"); //$NON-NLS-1$
boolean isObject = objIndex >= 0;
int parseAsIndex = p.indexOf(":::"); //$NON-NLS-1$
try {
if (isObject && (parseAsIndex >= 0))
// assume order: [object] before type:::param
assert (objIndex < parseAsIndex);
if (parseAsIndex >= 0) {
// "type:::param"
String parseAs =
p.substring((isObject ? 8 : 0), parseAsIndex);
String value =
p.substring(parseAsIndex + 3, p.length());
if (parseAs.equalsIgnoreCase("int")) { //$NON-NLS-1$
parameterTypes[i] = Integer.class;
parameterObjects[i] = Integer.decode(value);
} else if (parseAs.equalsIgnoreCase("bool")) { //$NON-NLS-1$
parameterTypes[i] = Boolean.class;
parameterObjects[i] = Boolean.valueOf(value);
} else if (parseAs.equalsIgnoreCase("double")) { //$NON-NLS-1$
parameterTypes[i] = Double.class;
parameterObjects[i] = Double.valueOf(value);
}
// if [object] present, set type to Object
if (isObject)
parameterTypes[i] = Object.class;
} else if (isObject) { // "[object]param"
String value = p.substring(8, p.length());
parameterTypes[i] = Object.class;
parameterObjects[i] = value;
} else if (p.startsWith(contextParam)){// "param"
parameterTypes[i] = getParameterType(p);
parameterObjects[i] = "%Context"; //$NON-NLS-1$
}
else
parameterTypes[i] = String.class;
} catch (Exception e) {
String value =
p.substring(
((parseAsIndex >= 0) ? parseAsIndex + 3 : 0),
p.length());
parameterObjects[i] = value;
parameterTypes[i] = String.class;
}
}
setParameters(parameterObjects);
setParameterTypes(parameterTypes);
}
parameters = null;
// set method parameters
if (dataForIntialize.length() != 0) {
if (dataForIntialize.charAt(0) != '.')
throw new IllegalArgumentException();
setNext(new MethodDescriptor(dataForIntialize.substring(1).trim()));
}
if (getName() != null)
setName(getName().intern());
}finally{
dataForIntialize = null;
}
}
/**
* parse the passed paramter to extract the paramter's class
* @param p the parapemter
* @return
*/
private Class getParameterType(String parameter) {
int startIndex = parameter.indexOf("["); //$NON-NLS-1$
int endIndex = parameter.indexOf("]"); //$NON-NLS-1$
if(startIndex==-1 || endIndex==-1)
throw new IllegalArgumentException();
String parameterTypeString= parameter.substring(startIndex+1,endIndex).trim();
endIndex = parameterTypeString.indexOf('/');
if(endIndex==-1 || endIndex==parameterTypeString.length()-1)
throw new IllegalArgumentException();
String parameterPluginID = parameterTypeString.substring(0,endIndex).trim();
String parameterClassName = parameterTypeString.substring(endIndex + 1);
Class clazz = loadClass(parameterClassName,parameterPluginID);
if(clazz==null)
clazz = Object.class;
return clazz;
}
/**
* Parses and returns the Plugin ID in a method invocation string.
*
* @param string
* the method invocation string
* @return the plugin name
*/
private String parsePluginID(String string) {
int index = string.indexOf('/');
if (index == -1)
throw new IllegalArgumentException();
pluginID = string.substring(0, index).trim();
return string.substring(index + 1);
}
/**
* Parses and returns the Plugin ID in a method invocation string.
*
* @param string
* the method invocation string
* @return the plugin name
*/
private String parseClassName(String string) {
int index = string.indexOf('(');
if (index == -1)
throw new IllegalArgumentException();
index = string.lastIndexOf('.',index);
if (index == -1)
throw new IllegalArgumentException();
className = string.substring(0, index).trim();
return string.substring(index + 1);
}
public String getPluginID(){
return pluginID;
}
public String getClassName(){
return className;
}
}
/**
* A descriptor for an XML configuration element that identifies a method
* result by its type and <code>toString()</code> value.
*/
private static class ValueDescriptor {
/**
* The valid value literals.
*/
private Set valueLiterals;
/**
* The invalid valud literals.
*/
private Set notValueLiterals;
/**
* The valid value objects.
*/
private List valueObjects;
/**
* The invalid value objects.
*/
private List notValueObjects;
/**
* Creates a new value descriptor from its configuration element.
*
* @param configElement
* The configuration element.
*/
public ValueDescriptor(IConfigurationElement configElement) {
valueLiterals = new HashSet();
String s = configElement.getAttribute(VALUE);
if (s != null)
parseValueLiteralString(s, valueLiterals);
notValueLiterals = new HashSet();
s = configElement.getAttribute(NOT_VALUE);
if (s != null)
parseValueLiteralString(s, notValueLiterals);
IConfigurationElement[] valueConfigs = configElement.getChildren(VALUE);
valueObjects = new ArrayList(valueConfigs.length);
for (int i=0; i<valueConfigs.length; i++)
valueObjects.add(new ObjectDescriptor(valueConfigs[i]));
IConfigurationElement[] notValueConfigs = configElement.getChildren(NOT_VALUE);
notValueObjects = new ArrayList(notValueConfigs.length);
for (int i=0; i<notValueConfigs.length; i++)
notValueObjects.add(new ObjectDescriptor(notValueConfigs[i]));
}
/**
* Parse the string <code>s</code>, which is a comma-separated list
* of value literals and place them in the given <code>list</code>.
*
* @param s
* the string to be parsed
* @param list
* the set of literal string values from <code>s</code>.
*/
private void parseValueLiteralString(String s, Set list) {
// parse the string comma-separated string literals ignoring escaped commas
int start = 0;
int end = s.indexOf(',');
while (end != -1) {
if (s.charAt(end-1) == '\\') {
s = s.substring(0, end-1) + s.substring(end);
end = s.indexOf(',', end);
continue;
}
list.add(s.substring(start, end).trim().intern());
start = end +1;
end = s.indexOf(',', start);
}
list.add(s.substring(start).trim().intern());
}
/**
* Returns <code>true</code> if I am the same as <code>object</code>,
* <code>false</code> otherwise.
*
* @param object
* the object to be tested
* @return <code>true</code> if I am the same as <code>object</code>,
* <code>false</code> otherwise.
*/
public boolean sameAs(Object object) {
if (!valueLiterals.isEmpty()) {
if (!valueLiterals.contains(object.toString()))
return false;
}
if (!notValueLiterals.isEmpty()) {
if (notValueLiterals.contains(object.toString()))
return false;
}
if (!valueObjects.isEmpty()) {
if (!isObjectinList(object, valueObjects))
return false;
}
if (!notValueObjects.isEmpty()) {
if (isObjectinList(object, notValueObjects))
return false;
}
return true;
}
/**
* Answers whether or not an object in <code>list</code> is the
* {@link #sameAs(Object)}<code>object</code>.
*
* @param object
* the object to find
* @param list
* the list of objects
* @return <code>true</code> if an object in <code>list</code> is
* the {@link #sameAs(Object)}<code>object</code>,
* <code>false</code> otherwise.
*/
private boolean isObjectinList(Object object, List list) {
Iterator i = list.iterator();
while (i.hasNext()) {
if (((ObjectDescriptor)i.next()).sameAs(object))
return true;
}
return false;
}
}
/**
* Describes a method value using a method descriptor and a value descriptor.
*/
private static class MethodValueEntry {
/**
* The method descriptor.
*/
public MethodDescriptor method;
/**
* The value descriptor.
*/
public ValueDescriptor value;
/**
* Creates a new method value entry.
* @param method the method descriptor
* @param value the value descriptor
*/
public MethodValueEntry(MethodDescriptor method, ValueDescriptor value) {
super();
this.method = method;
this.value = value;
}
}
/**
* A helper method to return a list of objects whose ids are given in a
* comma-separated string and whose instances are given in an object map.
*
* @param objectsIds
* A comma-separated object ids string
* @param objectMap
* A map of object ids to their instances
* @param configElement
* The configuration element, used for error logging
* @return a list of object instances whose ids are given or
* <code>null</code> if no ids matched any instances
*/
protected static List getObjectList(String objectsIds, Map objectMap, IConfigurationElement configElement) {
if (objectsIds == null)
return null;
StringTokenizer ids = new StringTokenizer(objectsIds.trim(), ","); //$NON-NLS-1$
if (!ids.hasMoreTokens())
return null;
List objectList = new ArrayList();
while (ids.hasMoreTokens()) {
String objectId = ids.nextToken().trim();
Object objectVal = objectMap.get(objectId);
if (objectVal != null)
objectList.add(objectVal);
else {
Log.error(CommonCorePlugin.getDefault(), CommonCoreStatusCodes.SERVICE_FAILURE, configElement.getDeclaringExtension().getContributor().getName()+ ".plugin.xml extension [" + configElement.getDeclaringExtension().getExtensionPointUniqueIdentifier() + "]: object id (" + objectId + ") is not in the list " + objectMap.keySet()); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$
}
}
return objectList;
}
/**
* Parses the comma-separated <code>s</code> string and returns a set of
* the individual entries in the string.
*
* @param s
* A comma-separated string
* @return a set of the individual entries in the string.
*/
protected static Set getStrings(String s) {
if (s == null)
return null;
Set stringList = new HashSet();
StringTokenizer ids = new StringTokenizer(s.trim(), ","); //$NON-NLS-1$
while (ids.hasMoreTokens()) {
stringList.add(ids.nextToken().trim());
}
return stringList.isEmpty() ? null : stringList;
}
/**
* Tests if an object matches at least one in the list of object descriptors
* passed.
*
* @param object
* the object for which to find a match
* @param objects
* the list of object in which to find a match
* @return <code>true</code> if there was a match, <code>false</code>
* otherwise
*/
protected static boolean objectMatches(Object object, List objects) {
if (object != null) {
for (Iterator i = objects.iterator(); i.hasNext();) {
ObjectDescriptor desc = (ObjectDescriptor) i.next();
if (desc.sameAs(object))
return true;
}
}
return false;
}
/**
* A utility method to load a class using its name and a given class loader.
*
* @param className
* The class name
* @param bundle
* The class loader
* @return The loaded class or <code>null</code> if could not be loaded
*/
/*protected static Class loadClass(String className, Bundle bundle) {
try {
return bundle.loadClass(className);
} catch (ClassNotFoundException e) {
return null;
}
}*/
/**
* A utility method to load a class using its name and a given class loader.
*
* @param className
* The class name
* @param bundle
* The class loader
* @return The loaded class or <code>null</code> if could not be loaded
*/
protected static Class loadClass(String className, String pluginId) {
StringBuffer keyStringBuf = new StringBuffer(className.length()
+ pluginId.length() + 2); // 2 is for . and extra.
keyStringBuf.append(pluginId);
keyStringBuf.append('.');
keyStringBuf.append(className);
String keyString = keyStringBuf.toString();
WeakReference ref = (WeakReference) successLookupTable.get(keyString);
Class found = (ref != null) ? (Class) ref.get()
: null;
if (found == null) {
if (ref != null)
successLookupTable.remove(keyString);
if (!failureLookupTable.contains(keyString)) {
try {
Bundle bundle = basicGetPluginBundle(pluginId);
if (bundle!=null){
// never load the class if the bundle is not active other wise
// we will cause the plugin to load
// unless the class is in the exception list
int state = bundle.getState();
if ( state == org.osgi.framework.Bundle.ACTIVE || isInExceptionList(bundle,className)){
found = bundle.loadClass(className);
successLookupTable.put(keyString, new WeakReference(found));
if (state == org.osgi.framework.Bundle.ACTIVE){
bundleToExceptionsSetMap.remove(bundle);
}
}
}else{
failureLookupTable.add(keyString);
}
} catch (ClassNotFoundException e) {
failureLookupTable.add(keyString);
}
}
}
return found;
}
private static boolean isInExceptionList(Bundle bundle, String className) {
String packageName = className.substring(0,className.lastIndexOf('.'));
Set exceptionSet = (Set)bundleToExceptionsSetMap.get(bundle);
if (exceptionSet==null){
Dictionary dict = bundle.getHeaders();
String value = (String)dict.get("Eclipse-LazyStart"); //$NON-NLS-1$
if (value!=null){
int index = value.indexOf("exceptions"); //$NON-NLS-1$
if (index!=-1){
try {
int start = value.indexOf('"',index+1);
int end = value.indexOf('"',start+1);
String exceptions = value.substring(start+1,end);
exceptionSet = new HashSet(2);
StringTokenizer tokenizer = new StringTokenizer(exceptions, ","); //$NON-NLS-1$
while (tokenizer.hasMoreTokens()) {
exceptionSet.add(tokenizer.nextToken().trim());
}
}catch(IndexOutOfBoundsException exception){
// this means the MF did not follow the documented format for the exceptions list
// so i'll consider it empty
exceptionSet = Collections.EMPTY_SET;
}
}else{
exceptionSet = Collections.EMPTY_SET;
}
}else{
exceptionSet = Collections.EMPTY_SET;
}
bundleToExceptionsSetMap.put(bundle, exceptionSet);
}
return exceptionSet.contains(packageName);
}
/**
* Given a bundle id, it checks if the bundle is found and activated. If it
* is, the method returns the bundle, otherwise it returns <code>null</code>.
*
* @param pluginId
* the bundle ID
* @return the bundle, if found
*/
protected static Bundle getPluginBundle(String pluginId) {
Bundle bundle = basicGetPluginBundle(pluginId);
if (null != bundle && bundle.getState() == org.osgi.framework.Bundle.ACTIVE)
return bundle;
return null;
}
private static Bundle basicGetPluginBundle(String pluginId) {
return Platform.getBundle(pluginId);
}
/**
* Tests if the given class is assignable to the given class name. Optimized
* to look first in a cache of previously retrieved results.
*
* @param clazz
* the class to be tested
* @param className
* the class name to test against
* @return <code>true</code> if the class is assignable to the class name,
* <code>false</code> otherwise.
*/
protected static boolean isAssignableTo(Class clazz, String className) {
if (clazz == null)
return false;
if ( contains(isNotAssignableTable, clazz, className) ) {
return false;
}
if ( contains(isAssignableTable, clazz, className) ) {
return true;
}
boolean result = isAssignableToNoCache(clazz,className);
if (result) {
add(isAssignableTable, clazz, className);
} else {
add(isNotAssignableTable, clazz, className);
}
return result;
}
/**
* Tests if the given class is assignable to the given class name.
*
* @param clazz
* the class to be tested
* @param className
* the class name to test against
* @return <code>true</code> if the class is assignable to the class name,
* <code>false</code> otherwise.
*/
private static boolean isAssignableToNoCache(Class clazz, String className) {
// mgoyal: This approach isn't safe to use as it can cause incorrect
// plugin load. Documenting this approach for further analysis. Don't
// remove or uncomment this.
// try {
// if(clazz.getName().equals(className))
// return true;
//
// ClassLoader clsLoader = clazz.getClassLoader();
// if(clsLoader != null) {
// Class testCls = clsLoader.loadClass(className);
// if(testCls != null && testCls.isAssignableFrom(clazz))
// return true;
// }
// return false;
// } catch (ClassNotFoundException e) {
// return false;
// }
//
// test the class itself
if (clazz.getName().equals(className))
return true;
// test all the interfaces the class implements
Class[] interfaces = clazz.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
if (checkInterfaceHierarchy(interfaces[i], className))
return true;
}
// test superclass
return isAssignableTo(clazz.getSuperclass(), className);
}
/**
* A map of classes that have been successfully loaded, keyed on the class
* name optionally prepended by the plugin ID, if specified.
*/
private static Map successLookupTable = new HashMap();
/**
* A map of classes that could not be loaded, keyed on the class name
* optionally prepended by the plugin ID, if specified.
*/
private static Set failureLookupTable = new HashSet();
/**
* Gets an adapter for <code>object</code> to the class described by
* <code>className</code> qualified by the optional <code>pluginId</code>.
*
* @param object
* the object to be adapted
* @param className
* the name of the adapter class
* @param pluginId
* the optional plugin ID (can be <code>null/code>)
* @return the adapted object, or <code>null</code> if it couldn't be found
*/
protected static Object getAdapter(Object object, String className, String pluginId) {
if (!(object instanceof IAdaptable))
return null;
if(pluginId != null) {
Class theClass = loadClass(className,pluginId);
return theClass != null ? ((IAdaptable) object).getAdapter(theClass) : null;
}
return null;
}
/**
* A utility method to invoke a cascading list of methods.
*
* @param methodDescriptor
* the first method descriptor
* @param object
* The object to invoke the method on
* @return the value of the invokation
*/
protected static Object invokeMethod(MethodDescriptor methodDescriptor, Object object) {
String methodSignature = null;
Class clazz =null;
try {
if (methodDescriptor == null || object == null)
return null;
if (!methodDescriptor.isInitialized()){
methodDescriptor.initialize();
}
methodSignature = methodDescriptor.getSignature();
clazz = object.getClass();
if (passiveClasses.contains(clazz,methodSignature))
return null;
Method method = classToMethodSignatureToMethodCach.
getMethod(clazz,methodSignature);
if(method==null){
method = clazz.getMethod(methodDescriptor.getName(),
methodDescriptor.getParameterTypes());
classToMethodSignatureToMethodCach.addMethod(clazz,methodSignature,method);
}
Object valueObj =
method.invoke(object, methodDescriptor.getParameters());
if (methodDescriptor.getNext() == null)
return valueObj == null ? NULL : valueObj;
return invokeMethod(methodDescriptor.getNext(), valueObj);
} catch (Exception e) {
passiveClasses.addMethod(clazz,methodSignature);
return null;
}
}
/**
* A utility method to invoke a cascading list of methods.
*
* @param StaticMethodDescriptor
* the static method descriptor
* @param object
* The context object to use (it could be null)
* @return the value of the invokation
*/
protected static Object invokeStaticMethod(StaticMethodDescriptor methodDescriptor, Object object) {
try {
if (methodDescriptor == null)
return null;
if (!methodDescriptor.isInitialized()){
methodDescriptor.initialize();
}
Object[] valuesCopy = null;
if (methodDescriptor.getParameters() != null) {
valuesCopy = methodDescriptor.getParameters()
.clone();
for (int i = 0; i < valuesCopy.length; i++) {
if (valuesCopy[i].equals(contextParam)) {
valuesCopy[i] = object;
}
}
}
Method method = getStaticMethod(methodDescriptor);
Object valueObj = (method != null) ? method.invoke(object,
valuesCopy)
: null;
if (methodDescriptor.getNext() == null)
return valueObj == null ? NULL : valueObj;
return invokeMethod(methodDescriptor.getNext(), valueObj);
} catch (Exception e) {
return null;
}
}
/**
* utility method used to get a static method object
* @param pluginID the plugin that owns the class
* @param className the class to use to call hte static method
* @param methodName the method to get
* @param ParameterTypes the parameter types
* @return the method object
*/
private static Method getStaticMethod(StaticMethodDescriptor staticMethodDescriptor) {
Class theClass = loadClass(staticMethodDescriptor.getClassName(),
staticMethodDescriptor.getPluginID());
if (theClass==null)
return null;
Method theMethod = null;
try {
String methodSignature = staticMethodDescriptor.getSignature();
theMethod = classToMethodSignatureToMethodCach.getMethod(theClass,methodSignature);
if(theMethod==null){
theMethod = theClass.getMethod(staticMethodDescriptor.getName(),
staticMethodDescriptor.getParameterTypes());
classToMethodSignatureToMethodCach.addMethod(theClass,methodSignature,theMethod);
}
} catch (SecurityException e) {
// no special handling needed;
} catch (NoSuchMethodException e) {
// no special handling needed;
}
return theMethod;
}
/**
* Check the interfaces the whole way up. If one of them matches
* <code>className</code> return <code>true</code>. Optimized to look
* first in a cache of previously retrieved results.
*
* @param interfaceToCheck
* The interface whose name we are testing.
* @param className
* the name of the interface to we are trying to match
* @return <code>true</code> if one of the interfaces in the hierarchy
* matches <code>className</code>,<code>false</code>
* otherwise.
*/
private static boolean checkInterfaceHierarchy(Class interfaceToCheck, String className) {
if ( contains(isNotAssignableTable, interfaceToCheck, className) ) {
return false;
}
if ( contains(isAssignableTable, interfaceToCheck, className) ) {
return true;
}
boolean result = checkInterfaceHierarchyNoCache(interfaceToCheck,className);
if (result) {
add(isAssignableTable, interfaceToCheck, className);
} else {
add(isNotAssignableTable, interfaceToCheck, className);
}
return result;
}
/**
* Check the interfaces the whole way up. If one of them matches
* <code>className</code> return <code>true</code>.
*
* @param interfaceToCheck
* The interface whose name we are testing.
* @param className
* the name of the interface to we are trying to match
* @return <code>true</code> if one of the interfaces in the hierarchy
* matches <code>className</code>,<code>false</code>
* otherwise.
*/
private static boolean checkInterfaceHierarchyNoCache(Class interfaceToCheck, String className) {
if(interfaceToCheck.getName().equals(className))
return true;
Class[] superInterfaces = interfaceToCheck.getInterfaces();
for (int i = 0; i < superInterfaces.length; i++) {
if(checkInterfaceHierarchy(superInterfaces[i], className))
return true;
}
return false;
}
/**
* Determines whether the <code>map</code> contains an entry for the
* <key,value>pair.
*
* @param map
* the map in which to find the key and value
* @param key
* the key
* @param value
* the value
* @return <code>true</code> if the map contains the key/value pair,
* <code>false</code> otherwise
*/
private static boolean contains(Map map, Object key, String value) {
boolean result = false;
Object val = map.get(key);
if (val != null) {
Set values = (Set)val;
result = values.contains(value);
}
return result;
}
/**
* Adds the <key,value>pair to the <code>map</code>.
*
* @param map
* the map in which to add the value
* @param key
* the key
* @param value
* the value
*/
private static void add(Map map, Object key, String value) {
Set values = (Set)map.get(key);
if (values == null) {
values = new HashSet();
map.put(key, values);
}
values.add(value);
}
}