/* * ============================================================================ * GNU Lesser General Public License * ============================================================================ * * Beanlet - JSE Application Container. * Copyright (C) 2006 Leon van Zantvoort * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * Leon van Zantvoort * 243 Acalanes Drive #11 * Sunnyvale, CA 94086 * USA * * zantvoort@users.sourceforge.net * http://beanlet.org */ package org.beanlet.impl; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.beanlet.Attribute; import org.beanlet.AttributeAccessType; import org.beanlet.BeanletValidationException; import org.beanlet.annotation.AnnotationDomain; import org.beanlet.annotation.ElementAnnotation; import org.beanlet.annotation.FieldElement; import org.beanlet.annotation.MethodElement; import org.beanlet.common.Beanlets; import org.beanlet.common.event.AttributeReadEventImpl; import org.beanlet.common.event.AttributeWriteEventImpl; import org.beanlet.plugin.BeanletConfiguration; import org.jargo.ComponentConfiguration; import org.jargo.Event; /** * @author Leon van Zantvoort */ public final class Attributes { private static final Map<ComponentConfiguration, Attributes> cache = new HashMap<ComponentConfiguration, Attributes>(); public static Attributes getInstance() { return getInstance(null); } public static synchronized Attributes getInstance( final ComponentConfiguration<?> configuration) { Attributes attributes = cache.get(configuration); if (attributes == null) { if (configuration instanceof BeanletConfiguration) { attributes = new Attributes((BeanletConfiguration<?>) configuration); } else { attributes = new Attributes(); } configuration.getComponentUnit().addDestroyHook(new Runnable() { public void run() { synchronized (Attributes.class) { cache.remove(configuration); } } }); cache.put(configuration, attributes); } return attributes; } public static boolean isGetter(Method method) { if (method.getName().startsWith("get") && method.getName().length() > 3 && method.getParameterTypes().length == 0 && !method.getReturnType().equals(Void.TYPE)) { return true; } if (isIs(method)) { return true; } return false; } public static boolean isIs(Method method) { if (method.getName().startsWith("is") && method.getName().length() > 2 && method.getParameterTypes().length == 0 && (method.getReturnType().equals(Boolean.TYPE) || method.getReturnType().equals(Boolean.class))) { return true; } return false; } public static boolean isSetter(Method method) { if (method.getName().startsWith("set") && method.getName().length() > 3 && method.getParameterTypes().length == 1 && method.getReturnType().equals(Void.TYPE)) { return true; } return false; } public static String getAttributeName(Method method) { return getAttributeName(method, true); } private static String getAttributeName(Method method, boolean nillable) { final String attributeName; if ((method.getName().startsWith("get") || method.getName(). startsWith("set")) && method.getName().length() > 3) { attributeName = Character.toLowerCase(method.getName().charAt(3)) + method.getName().substring(4); } else if (method.getName().startsWith("is") && method.getName().length() > 2) { attributeName = Character.toLowerCase(method.getName().charAt(2)) + method.getName().substring(3); } else if (!nillable) { attributeName = method.getName(); } else { attributeName = null; } return attributeName; } private final AnnotationDomain domain; private final ProxyMethods proxyMethods; private final Map<String, Getter> getters; private final Map<String, Setter> setters; private final Map<Member, Getter> memberGetters; private final Map<Member, Setter> memberSetters; private Attributes() { this.domain = null; this.proxyMethods = ProxyMethods.getInstance(); this.getters = new HashMap<String, Getter>(); this.setters = new HashMap<String, Setter>(); this.memberGetters = new HashMap<Member, Getter>(); this.memberSetters = new HashMap<Member, Setter>(); } private Attributes(BeanletConfiguration configuration) { this.domain = configuration.getAnnotationDomain(); this.proxyMethods = ProxyMethods.getInstance(configuration); this.getters = new HashMap<String, Getter>(); this.setters = new HashMap<String, Setter>(); this.memberGetters = new HashMap<Member, Getter>(); this.memberSetters = new HashMap<Member, Setter>(); Class<?> type = configuration.getType(); for (ElementAnnotation<FieldElement, Attribute> e : domain.getDeclaration(Attribute.class). getTypedElements(FieldElement.class, type)) { Attribute attribute = e.getAnnotation(); Field field = e.getElement().getField(); String attributeName = attribute.name().equals("") ? field.getName() : attribute.name(); List accessTypes = Arrays.asList(attribute.accessType()); if (accessTypes.contains(AttributeAccessType.READ) || accessTypes.isEmpty()) { if (!getters.containsKey(attributeName)) { Getter getter = createFieldGetter(attributeName, field); getters.put(attributeName, getter); memberGetters.put(getter.getMember(), getter); } else { throw new BeanletValidationException( configuration.getComponentName(), "Duplicate readable attributes found: \'" + attributeName + "\'."); } } if (accessTypes.contains(AttributeAccessType.WRITE) || (accessTypes.isEmpty() && Modifier.isPublic(field.getModifiers()) && !Modifier.isFinal(field.getModifiers()))) { if (accessTypes.contains(AttributeAccessType.WRITE) && Modifier.isFinal(field.getModifiers())) { throw new BeanletValidationException( configuration.getComponentName(), "Writable attribute MUST NOT be a final field: '" + field + "\'."); } if (!setters.containsKey(attributeName)) { Setter setter = createFieldSetter(attributeName, field); setters.put(attributeName, setter); memberSetters.put(setter.getMember(), setter); } else { throw new BeanletValidationException( configuration.getComponentName(), "Duplicate writeable attributes found: \'" + attributeName + "\'."); } } } for (ElementAnnotation<MethodElement, Attribute> e : domain.getDeclaration(Attribute.class). getTypedElements(MethodElement.class, type)) { Attribute attribute = e.getAnnotation(); Method method = e.getElement().getMethod(); final String attributeName; if (attribute.name().equals("")) { attributeName = getAttributeName(method, false); } else { attributeName = attribute.name(); } if (isGetter(method)) { List accessTypes = Arrays.asList(attribute.accessType()); if (accessTypes.contains(AttributeAccessType.WRITE)) { throw new BeanletValidationException( configuration.getComponentName(), "Getter method MUST NOT specify WRITE access type: '" + method + "'."); } Getter getter = createMethodGetter(attributeName, method); if (!getters.containsKey(attributeName)) { Setter setter = setters.get(attributeName); if (setter == null || getter.getType(). equals(setter.getType())) { getters.put(attributeName, getter); memberGetters.put(getter.getMember(), getter); } else { throw new BeanletValidationException( configuration.getComponentName(), "Duplicate readable attributes found: '" + attributeName + "'."); } } else { throw new BeanletValidationException( configuration.getComponentName(), "Duplicate readable attributes: \'" + attributeName + "\'."); } } else if (isSetter(method)) { List accessTypes = Arrays.asList(attribute.accessType()); if (accessTypes.contains(AttributeAccessType.READ)) { throw new BeanletValidationException( configuration.getComponentName(), "Setter method MUST NOT specify READ access type: '" + method + "'."); } Setter setter = createMethodSetter(attributeName, method); if (!setters.containsKey(attributeName)) { Getter getter = getters.get(attributeName); if (getter == null || setter.getType(). equals(getter.getType())) { setters.put(attributeName, setter); memberSetters.put(setter.getMember(), setter); } else { throw new BeanletValidationException( configuration.getComponentName(), "Duplicate writeable attributes found: '" + attributeName + "'."); } } else { throw new BeanletValidationException( configuration.getComponentName(), "Duplicate writable attributes found: \'" + attributeName + "\'."); } } else { throw new BeanletValidationException( configuration.getComponentName(), "Invalid attribute method signature: \'" + attributeName + "\'."); } } Beanlets beanlets = Beanlets.getInstance(configuration); final List<Class<?>> classes; if (beanlets.isVanilla()) { // Automatically includes all implemented interfaces. classes = Collections.<Class<?>>singletonList(type); } else { classes = beanlets.getInterfaces(); } for (Class<?> cls : classes) { for (Method method : cls.getMethods()) { if (!cls.isInterface()) { // Object's methods are excluded as well. try { Object.class.getMethod(method.getName(), method.getParameterTypes()); continue; } catch (NoSuchMethodException e) { } } final String t; if (cls.isInterface()) { t = "Interface"; } else { t = "Superclass"; } if (isGetter(method)) { // Method is recognized as a getter. String attributeName = getAttributeName(method, false); if (!getters.containsKey(attributeName)) { // Getter has not yet been constructed. final Getter getter; if (cls.isAssignableFrom(type)) { // Getter is directly supported (implemented) by underlying class. getter = createMethodGetter(attributeName, method); } else { // Method is not supported by underlying class. if (proxyMethods.getProxyMethod() != null) { // Proxy method will be used to support getter. getter = createProxyMethodGetter(attributeName, method, method.getReturnType()); } else { // Not explicitely marked as attribute so skip method instead of exception. // // Leon van Zantvoort // 2011/01/09 continue; // throw new BeanletValidationException( // configuration.getComponentName(), t + // "Getter and setter type do not match: '" + // method + "'."); } } Setter setter = setters.get(attributeName); if (setter == null || getter.getType().equals( setter.getType())) { // Setter (counterpart) does not exist, or is of same type. getters.put(attributeName, getter); memberGetters.put(getter.getMember(), getter); } else { // Not explicitely marked as attribute so skip method instead of exception. // // Leon van Zantvoort // 2011/01/09 continue; // throw new BeanletValidationException( // configuration.getComponentName(), // "Duplicate writable attributes found: '" + // attributeName + "'."); } } } else if (isSetter(method)) { String attributeName = getAttributeName(method, false); if (!setters.containsKey(attributeName)) { final Setter setter; if (cls.isAssignableFrom(type)) { setter = createMethodSetter(attributeName, method); } else { if (proxyMethods.getProxyMethod() != null) { setter = createProxyMethodSetter(attributeName, method, method.getParameterTypes()[0]); } else { // Not explicitely marked as attribute so skip method instead of exception. // // Leon van Zantvoort // 2011/01/09 continue; // throw new BeanletValidationException( // configuration.getComponentName(), t + // " method not exposed as attribute: \'" + // method + "\'."); } } Getter getter = getters.get(attributeName); if (getter == null || setter.getType().equals( getter.getType())) { setters.put(attributeName, setter); memberSetters.put(setter.getMember(), setter); } else { // Not explicitely marked as attribute so skip method instead of exception. // // Leon van Zantvoort // 2011/01/09 continue; // throw new BeanletValidationException( // configuration.getComponentName(), // "Superclass" + attributeName + "\'."); } } } } } if (beanlets.isVanilla()) { for (Field field : type.getFields()) { String attributeName = field.getName(); if (!getters.containsKey(field)) { Getter getter = createFieldGetter(attributeName, field); getters.put(attributeName, getter); memberGetters.put(getter.getMember(), getter); } if (!Modifier.isFinal(field.getModifiers()) && !setters.containsKey(field)) { Setter setter = createFieldSetter(attributeName, field); setters.put(attributeName, setter); memberSetters.put(setter.getMember(), setter); } } } } public Event getEvent(Method method, Object[] args) { final Event event; if (isGetter(method)) { String attributeName = getAttributeName(method, false); if (getters.containsKey(attributeName)) { event = new AttributeReadEventImpl(attributeName); } else { event = null; } } else if (isSetter(method)) { String attributeName = getAttributeName(method, false); if (setters.containsKey(attributeName)) { event = new AttributeWriteEventImpl(attributeName, args[0]); } else { event = null; } } else { event = null; } return event; } public List<Class<?>> getInterfaces() { return Collections.emptyList(); } public Collection<Getter> getGetters() { return Collections.unmodifiableCollection(getters.values()); } public Getter getGetter(String attributeName) { return getters.get(attributeName); } public Getter getGetter(Member member) { return memberGetters.get(member); } public Collection<Setter> getSetters() { return Collections.unmodifiableCollection(setters.values()); } public Setter getSetter(String attributeName) { return setters.get(attributeName); } public Setter getSetter(Member member) { return memberSetters.get(member); } private Getter createProxyMethodGetter(final String name, final Method method, final Class<?> type) { final Method proxyMethod = proxyMethods.getProxyMethod(); assert proxyMethod != null; if (!Modifier.isPublic(proxyMethod.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { // PERMISSION: java.lang.reflect.ReflectPermission suppressAccessChecks proxyMethod.setAccessible(true); return null; } }); } final Attribute annotation = domain.getDeclaration(Attribute.class). getAnnotation(MethodElement.instance(method)); return new Getter() { public String getName() { return name; } public Member getMember() { return method; } public Class<?> getType() { return method.getReturnType(); } public String getDescription() { return annotation == null ? "" : annotation.description(); } public Object get(Object instance) throws Exception { try { return proxyMethod.invoke(instance, method.getName(), method.getParameterTypes(), new Object[0]); } catch (InvocationTargetException e) { try { throw e.getTargetException(); } catch (RuntimeException e2) { throw e2; } catch (Error e2) { throw e2; } catch (Exception e2) { throw e2; } catch (Throwable t) { throw new RuntimeException(t); } } } }; } private Getter createMethodGetter(final String name, final Method method) { if (!Modifier.isPublic(method.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { // PERMISSION: java.lang.reflect.ReflectPermission suppressAccessChecks method.setAccessible(true); return null; } }); } final Attribute annotation = domain.getDeclaration(Attribute.class). getAnnotation(MethodElement.instance(method)); return new Getter() { public String getName() { return name; } public Member getMember() { return method; } public Class<?> getType() { return method.getReturnType(); } public String getDescription() { return annotation == null ? "" : annotation.description(); } public Object get(Object instance) throws Exception { try { return method.invoke(instance); } catch (InvocationTargetException e) { try { throw e.getTargetException(); } catch (RuntimeException e2) { throw e2; } catch (Error e2) { throw e2; } catch (Exception e2) { throw e2; } catch (Throwable t) { throw new RuntimeException(t); } } } }; } private Getter createFieldGetter(final String name, final Field field) { if (!Modifier.isPublic(field.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { // PERMISSION: java.lang.reflect.ReflectPermission suppressAccessChecks field.setAccessible(true); return null; } }); } final Attribute annotation = domain.getDeclaration(Attribute.class). getAnnotation(FieldElement.instance(field)); return new Getter() { public String getName() { return name; } public Member getMember() { return field; } public Class<?> getType() { return field.getType(); } public String getDescription() { return annotation == null ? "" : annotation.description(); } public Object get(Object instance) throws Exception { return field.get(instance); } }; } private Setter createProxyMethodSetter(final String name, final Method method, final Class<?> type) { final Method proxyMethod = proxyMethods.getProxyMethod(); assert proxyMethod != null; if (!Modifier.isPublic(proxyMethod.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { // PERMISSION: java.lang.reflect.ReflectPermission suppressAccessChecks proxyMethod.setAccessible(true); return null; } }); } final Attribute annotation = domain.getDeclaration(Attribute.class). getAnnotation(MethodElement.instance(method)); return new Setter() { public String getName() { return name; } public Member getMember() { return method; } public Class<?> getType() { return method.getParameterTypes()[0]; } public String getDescription() { return annotation == null ? "" : annotation.description(); } public void set(Object instance, Object injection) throws Exception { try { proxyMethod.invoke(instance, method.getName(), method.getParameterTypes(), new Object[]{injection}); } catch (InvocationTargetException e) { try { throw e.getTargetException(); } catch (RuntimeException e2) { throw e2; } catch (Error e2) { throw e2; } catch (Exception e2) { throw e2; } catch (Throwable t) { throw new RuntimeException(t); } } } }; } private Setter createMethodSetter(final String name, final Method method) { if (!Modifier.isPublic(method.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { // PERMISSION: java.lang.reflect.ReflectPermission suppressAccessChecks method.setAccessible(true); return null; } }); } final Attribute annotation = domain.getDeclaration(Attribute.class). getAnnotation(MethodElement.instance(method)); return new Setter() { public String getName() { return name; } public Member getMember() { return method; } public Class<?> getType() { return method.getParameterTypes()[0]; } public String getDescription() { return annotation == null ? "" : annotation.description(); } public void set(Object instance, Object injection) throws Exception { try { method.invoke(instance, injection); } catch (InvocationTargetException e) { try { throw e.getTargetException(); } catch (RuntimeException e2) { throw e2; } catch (Error e2) { throw e2; } catch (Exception e2) { throw e2; } catch (Throwable t) { throw new RuntimeException(t); } } } }; } private Setter createFieldSetter(final String name, final Field field) { if (!Modifier.isPublic(field.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { // PERMISSION: java.lang.reflect.ReflectPermission suppressAccessChecks field.setAccessible(true); return null; } }); } final Attribute annotation = domain.getDeclaration(Attribute.class). getAnnotation(FieldElement.instance(field)); return new Setter() { public String getName() { return name; } public Member getMember() { return field; } public Class<?> getType() { return field.getType(); } public String getDescription() { return annotation == null ? "" : annotation.description(); } public void set(Object instance, Object injection) throws Exception { field.set(instance, injection); } }; } public static interface Getter { String getName(); Member getMember(); Class<?> getType(); String getDescription(); Object get(Object instance) throws Exception; } public static interface Setter { String getName(); Member getMember(); Class<?> getType(); String getDescription(); void set(Object instance, Object injection) throws Exception; } }