/* * ============================================================================ * 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 static org.beanlet.common.BeanletConstants.*; import org.beanlet.annotation.FieldElement; import org.beanlet.annotation.MethodParameterElement; import java.lang.reflect.InvocationTargetException; import org.beanlet.annotation.AnnotationValueResolver; import org.beanlet.annotation.AnnotationProxy; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.xpath.XPath; import org.beanlet.CollectionValue; import org.beanlet.Entry; import org.beanlet.Inject; import org.beanlet.MapValue; import org.beanlet.Value; import org.beanlet.annotation.ConstructorElement; import org.beanlet.annotation.ConstructorParameterElement; import org.beanlet.annotation.Element; import org.beanlet.annotation.MethodElement; import org.beanlet.common.AbstractElementAnnotationFactory; import org.beanlet.common.AbstractProvider; import org.beanlet.common.BeanletConstants; import org.beanlet.plugin.ElementAnnotationContext; import org.beanlet.plugin.ElementAnnotationFactory; import org.beanlet.plugin.NestedBeanletFactory; import org.beanlet.plugin.spi.ElementAnnotationFactoryProvider; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * * @author Leon van Zantvoort */ public final class InjectElementAnnotationFactoryProviderImpl extends AbstractProvider implements ElementAnnotationFactoryProvider { public List<ElementAnnotationFactory> getElementAnnotationFactories() { final XPath xpath = XPathFactory.newInstance().newXPath(); xpath.setNamespaceContext(BeanletConstants.BEANLET_NAMESPACE_CONTEXT); ElementAnnotationFactory factory = new AbstractElementAnnotationFactory<Inject>() { public String getNamespaceURI() { return BEANLET_NAMESPACE_URI; } public String getNodeName() { return "inject"; } public Class<Inject> annotationType() { return Inject.class; } public boolean isMatch(Element element, Inject annotation) { final Class<?> destinationType; final Class<?> sourceType; if (element instanceof FieldElement) { if (Modifier.isStatic(element.getMember().getModifiers())) { return false; // Replaced by @StaticFactory } else { destinationType = ((FieldElement) element).getField(). getType(); } } else if (element instanceof MethodElement) { Class<?>[] types = ((MethodElement) element).getMethod(). getParameterTypes(); if (types.length == 0) { if (Modifier.isStatic(element.getMember().getModifiers())) { return false; // Replaced by @StaticFactory } else { return false; } } else if (types.length == 1) { destinationType = types[0]; } else { return false; } } else if (element instanceof MethodParameterElement) { // PENDING: add check. Class<?>[] types = ((MethodParameterElement) element). getMethod().getParameterTypes(); destinationType = types[((MethodParameterElement) element). getParameter()]; } else if (element instanceof ConstructorElement) { Class<?>[] types = ((ConstructorElement) element). getConstructor().getParameterTypes(); if (types.length == 1) { destinationType = types[0]; } else { return false; // Replaced by @StaticFactory } } else if (element instanceof ConstructorParameterElement) { // PENDING: add check. Class<?>[] types = ((ConstructorParameterElement) element). getConstructor().getParameterTypes(); destinationType = types[((ConstructorParameterElement) element). getParameter()]; } else { return false; } assert destinationType != null; Value value = annotation.value(); final Class<?> tmpSourceType; if (value.nill() || !value.ref().equals("")) { tmpSourceType = null; } else if (!value.value().equals("") || value.empty()) { tmpSourceType = null; } else { CollectionValue collection = annotation.collection(); if (collection.value().length > 0 || collection.empty()) { if ((Collection.class.isAssignableFrom(destinationType) || destinationType.isArray()) && collection.type().equals(ArrayList.class)) { tmpSourceType = destinationType; } else { tmpSourceType = collection.type(); } } else { MapValue map = annotation.map(); if (map.value().length > 0 || map.empty()) { if (Map.class.isAssignableFrom(destinationType) && map.type().equals(HashMap.class)) { tmpSourceType = destinationType; } else { tmpSourceType = map.type(); } } else { // Type not supported, it's now up to Value.type(). tmpSourceType = null; } } } if (tmpSourceType == null) { if (value.type().equals(Object.class)) { sourceType = tmpSourceType; } else { sourceType = value.type(); } } else { sourceType = tmpSourceType; } if (sourceType == null) { return true; } else { return destinationType.isAssignableFrom(sourceType); } } public Object getValueFromNode(Node node, String elementName, Class type, final Object parentValue, final ElementAnnotationContext ctx) throws Throwable { if (elementName.equals("value")) { Node n = (Node) xpath.evaluate( "./:value", node, XPathConstants.NODE); return AnnotationProxy.newProxyInstance(Value.class, ctx.getClassLoader(), new ValueAnnotationResolver( n == null ? node : n, (Value) parentValue, ctx.getNestedBeanletFactory())); } else if (elementName.equals("collection")) { Node n = (Node) xpath.evaluate("./:collection", node, XPathConstants.NODE); if (n == null) { return null; // PENDING: or return parentValue instead? } Node t = (Node) xpath.evaluate("@type", n, XPathConstants.NODE); if (t == null) { t = (Node) xpath.evaluate("../@type", n, XPathConstants.NODE); } final Node synced = n.getAttributes().getNamedItem("synced"); final Node unmodifiable = n.getAttributes().getNamedItem("unmodifiable"); final NodeList values = (NodeList) xpath.evaluate( "./:value", n, XPathConstants.NODESET); final Node collectionType = t; AnnotationValueResolver resolver = new AnnotationValueResolver() { public Object getValue(Method method, ClassLoader loader) throws Throwable { try { if (method.getName().equals("type")) { Object v = null; if (collectionType != null) { v = loader.loadClass( collectionType.getNodeValue()); } else if (parentValue != null) { v = method.invoke(parentValue); } if (collectionType == null) { v = method.getDefaultValue(); } return v; } else if (method.getName().equals("empty")) { return values.getLength() == 0; } else if (method.getName().equals("synced")) { return synced != null && Boolean.parseBoolean( synced.getNodeValue()); } else if (method.getName().equals("unmodifiable")) { return unmodifiable != null && Boolean.parseBoolean( unmodifiable.getNodeValue()); } else if (method.getName().equals("value")) { List<Value> l = new ArrayList<Value>(); if (parentValue != null) { Value[] pv = (Value[]) method. invoke(parentValue); for (int i = 0; i < pv.length; i++) { l.add(pv[i]); } } for (int i = 0; i < values.getLength(); i++) { l.add(AnnotationProxy. newProxyInstance(Value.class, loader, new ValueAnnotationResolver( values.item(i), null, ctx.getNestedBeanletFactory()))); } return l.toArray(new Value[l.size()]); } else { return method.getDefaultValue(); } } catch (InvocationTargetException e) { throw e.getTargetException(); } } }; return AnnotationProxy.newProxyInstance(CollectionValue.class, ctx.getClassLoader(), resolver); } else if (elementName.equals("map")) { Node n = (Node) xpath.evaluate("./:map", node, XPathConstants.NODE); if (n == null) { return null; // PENDING: or return parentValue instead? } Node t = (Node) xpath.evaluate("@type", n, XPathConstants.NODE); if (t == null) { t = (Node) xpath.evaluate("../@type", n, XPathConstants.NODE); } final Node synced = n.getAttributes().getNamedItem("synced"); final Node unmodifiable = n.getAttributes().getNamedItem("unmodifiable"); final NodeList entries = (NodeList) xpath.evaluate( "./:entry", n, XPathConstants.NODESET); final Node mapType = t; AnnotationValueResolver resolver = new AnnotationValueResolver() { public Object getValue(Method method, ClassLoader loader) throws Throwable { if (method.getName().equals("type")) { Object v = null; if (mapType != null) { v = loader.loadClass(mapType.getNodeValue()); } else if (parentValue != null) { v = ((MapValue) parentValue).type(); } if (mapType == null) { v = method.getDefaultValue(); } return v; } else if (method.getName().equals("empty")) { return entries.getLength() == 0; } else if (method.getName().equals("synced")) { return synced != null && Boolean.parseBoolean( synced.getNodeValue()); } else if (method.getName().equals("unmodifiable")) { return unmodifiable != null && Boolean.parseBoolean( unmodifiable.getNodeValue()); } else if (method.getName().equals("value")) { List<Entry> l = new ArrayList<Entry>(); if (parentValue != null) { Entry[] pv = (Entry[]) method. invoke(parentValue); for (int i = 0; i < pv.length; i++) { l.add(pv[i]); } } for (int i = 0; i < entries.getLength(); i++) { final int ix = i; l.add(AnnotationProxy. newProxyInstance(Entry.class, loader, new AnnotationValueResolver() { public Object getValue(Method method, ClassLoader loader) throws Throwable { final AnnotationValueResolver resolver; if (method.getName().equals("key")) { Node entryNode = entries.item(ix); NamedNodeMap map = entryNode.getAttributes(); Node keyAttribute = map.getNamedItem("key"); if (keyAttribute != null) { resolver = new ValueAnnotationResolver(keyAttribute.getNodeValue()); } else { Node keyNode = (Node) xpath.evaluate("./:key", entryNode, XPathConstants.NODE); if (keyNode != null) { resolver = new ValueAnnotationResolver(keyNode, null, ctx.getNestedBeanletFactory()); } else { resolver = new ValueAnnotationResolver(); } } } else if (method.getName().equals("value")) { Node entryNode = entries.item(ix); NamedNodeMap map = entryNode.getAttributes(); Node valueAttribute = map.getNamedItem("value"); if (valueAttribute != null) { resolver = new ValueAnnotationResolver(valueAttribute.getNodeValue()); } else { Node valueNode = (Node) xpath.evaluate("./:value", entryNode, XPathConstants.NODE); if (valueNode != null) { resolver = new ValueAnnotationResolver(valueNode, null, ctx.getNestedBeanletFactory()); } else { resolver = new ValueAnnotationResolver(); } } } else { throw new AssertionError(method); } return AnnotationProxy. newProxyInstance(Value.class, loader, resolver); } })); } return l.toArray(new Entry[l.size()]); } else { return method.getDefaultValue(); } } }; return AnnotationProxy.newProxyInstance(MapValue.class, ctx.getClassLoader(), resolver); } else { return super.getValueFromNode(node, elementName, type, parentValue, ctx); } } }; return Collections.singletonList(factory); } private static class ValueAnnotationResolver implements AnnotationValueResolver { private final String value; private final Node node; private final Value parentValue; private final XPath xpath; private final NestedBeanletFactory nestedFactory; public ValueAnnotationResolver() { this.value = null; this.node = null; this.parentValue = null; this.xpath = null; this.nestedFactory = null; } public ValueAnnotationResolver(String value) { this.value = value; this.node = null; this.parentValue = null; this.xpath = null; this.nestedFactory = null; } public ValueAnnotationResolver(Node node, Value parentValue, NestedBeanletFactory nestedFactory) { this.value = null; this.node = node; this.parentValue = parentValue; this.xpath = XPathFactory.newInstance().newXPath(); this.nestedFactory = nestedFactory; xpath.setNamespaceContext(BeanletConstants.BEANLET_NAMESPACE_CONTEXT); } public Object getValue(Method method, ClassLoader loader) throws Throwable { final Node n; if (value != null) { if (method.getName().equals("empty")) { return value.equals(""); } else if (method.getName().equals("value")) { return value; } n = null; } else if (node == null) { if (method.getName().equals("nill")) { return true; } n = null; } else { if (method.getName().equals("empty")) { Node valueNode = (Node) xpath.evaluate("@value", node, XPathConstants.NODE); if (valueNode != null && valueNode.getNodeValue().equals("")) { return true; } else if (parentValue != null) { return parentValue.empty(); } else { return method.getDefaultValue(); } } else if (method.getName().equals("ref")) { Node beanletNode = (Node) xpath.evaluate("./:beanlet", node, XPathConstants.NODE); if (beanletNode != null) { return nestedFactory.create(beanletNode); } } n = (Node) xpath.evaluate("@" + method.getName(), node, XPathConstants.NODE); } Object v = null; if (n != null) { v = AbstractElementAnnotationFactory.valueOf(n.getNodeValue(), method.getReturnType(), loader); } if (v == null && parentValue != null) { try { v = method.invoke(parentValue); } catch (InvocationTargetException e) { throw e.getTargetException(); } } if (v == null) { v = method.getDefaultValue(); } return v; } } }