/******************************************************************************* * Copyright (c) 2008, 2014 Stuart McCulloch * 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: * Stuart McCulloch - initial API and implementation *******************************************************************************/ package org.eclipse.sisu.peaberry.eclipse; import static java.lang.Character.toLowerCase; import static org.eclipse.sisu.peaberry.eclipse.ExtensionBeanFactory.loadExtensionClass; import static org.eclipse.sisu.peaberry.eclipse.ExtensionBeanFactory.mapContent; import static org.eclipse.sisu.peaberry.eclipse.ExtensionBeanFactory.mapName; import java.lang.reflect.Array; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.core.runtime.ContributorFactoryOSGi; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.osgi.framework.Bundle; /** * {@link InvocationHandler} that maps method calls to bean properties. * * @author mcculls@gmail.com (Stuart McCulloch) */ final class ExtensionBeanHandler implements InvocationHandler { private static final String[] PREFIXES = {"is", "get", "create"}; // cache methods that always return the same result private final ConcurrentHashMap<Method, Object> cache; private final IConfigurationElement config; ExtensionBeanHandler(final IConfigurationElement config) { cache = new ConcurrentHashMap<Method, Object>(); this.config = config; } public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { Object result = cache.get(method); if (null != result) { return result; } if (method.getDeclaringClass() == Object.class) { if (null != args) { // must unwrap target so equals will work args[0] = unwrapExtensionBeanProxy(args[0]); } result = method.invoke(config, args); // hashCode, equals, etc... } else if (null == args) { result = invokeGetter(method); } else { throw new UnsupportedOperationException(method.toString()); } // "is" and "get" methods (with no arguments) always return constant values if (null != result && null == args && !method.getName().startsWith("create")) { cache.putIfAbsent(method, result); } return result; } private static Object unwrapExtensionBeanProxy(final Object instance) { if (Proxy.isProxyClass(instance.getClass())) { final Object handler = Proxy.getInvocationHandler(instance); if (handler instanceof ExtensionBeanHandler) { return ((ExtensionBeanHandler) handler).config; } } return instance; } private Object invokeGetter(final Method method) { final Class<?> resultType = method.getReturnType(); if (IConfigurationElement.class == resultType) { return config; } else if (Bundle.class == resultType) { return ContributorFactoryOSGi.resolve(config.getContributor()); } // map bean name to an XML attribute final String key = mapName(method, findPropertyName(method)); final String value = mapContent(config, key); if (null != value) { if (String.class == resultType) { return value; } else if (Class.class == resultType) { return loadExtensionClass(config, value); } else if (resultType.isPrimitive()) { return valueOf(resultType, value); } try { return config.createExecutableExtension(key); } catch (final CoreException e) {/* try nested getter */} // NOPMD } return invokeNestedGetter(resultType, key); } private Object invokeNestedGetter(final Class<?> resultType, final String key) { final IConfigurationElement[] kids = config.getChildren(key); if (resultType.isArray()) { final Class<?> componentType = resultType.getComponentType(); final Object[] results = (Object[]) Array.newInstance(componentType, kids.length); for (int i = 0; i < kids.length; i++) { results[i] = ExtensionBeanFactory.newInstance(componentType, kids[i]); } return results; } else if (kids.length > 0) { return ExtensionBeanFactory.newInstance(resultType, kids[0]); } return null; } private static String findPropertyName(final Method method) { final String name = method.getName(); for (final String prefix : PREFIXES) { if (name.startsWith(prefix)) { final int n = prefix.length(); return toLowerCase(name.charAt(n)) + name.substring(n + 1); } } return name; } private static Object valueOf(final Class<?> clazz, final String value) { // primitive String mappings... if (Boolean.class == clazz) { return Boolean.valueOf(value); } else if (Byte.class == clazz) { return Byte.valueOf(value); } else if (Character.class == clazz) { return value.charAt(0); } else if (Short.class == clazz) { return Short.valueOf(value); } else if (Integer.class == clazz) { return Integer.valueOf(value); } else if (Float.class == clazz) { return Float.valueOf(value); } else if (Long.class == clazz) { return Long.valueOf(value); } else if (Double.class == clazz) { return Double.valueOf(value); } throw new IllegalArgumentException("Unknown primitive type: " + clazz); } }