Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* Copyright 2012 Sven Ewald
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmlbeam;
import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathVariableResolver;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xmlbeam.XBProjector.IOBuilder;
import org.xmlbeam.XBProjector.InternalProjection;
import org.xmlbeam.annotation.XBDelete;
import org.xmlbeam.annotation.XBDocURL;
import org.xmlbeam.annotation.XBOverride;
import org.xmlbeam.annotation.XBRead;
import org.xmlbeam.annotation.XBUpdate;
import org.xmlbeam.annotation.XBValue;
import org.xmlbeam.annotation.XBWrite;
import org.xmlbeam.dom.DOMAccess;
import org.xmlbeam.types.TypeConverter;
import org.xmlbeam.util.intern.DOMHelper;
import org.xmlbeam.util.intern.MethodParamVariableResolver;
import org.xmlbeam.util.intern.Preprocessor;
import org.xmlbeam.util.intern.ReflectionHelper;
import org.xmlbeam.util.intern.duplex.DuplexExpression;
import org.xmlbeam.util.intern.duplex.DuplexXPathParser;
import org.xmlbeam.util.intern.duplex.ExpressionType;
import org.xmlbeam.util.intern.duplex.XBPathParsingException;
/**
* This class implements the "magic" behind projection methods. Each projection is linked with a
* ProjectionInvocatonHandler which handles method invocations on the projections. Notice that this
* class is not part of the public API. You should not get in touch with this class at all. See
* {@link org.xmlbeam.XBProjector} for API usage.
*
* @author Sven Ewald
*/
@SuppressWarnings("serial")
final class ProjectionInvocationHandler implements InvocationHandler, Serializable {
private Map getDefaultInvokers(final Object defaultInvokerObject) {
final ReflectionInvoker reflectionInvoker = new ReflectionInvoker(defaultInvokerObject);
final Map invokers = new HashMap();
for (Method m : DOMAccess.class.getMethods()) {
invokers.put(MethodSignature.forMethod(m), reflectionInvoker);
}
invokers.put(MethodSignature.forVoidMethod("toString"), reflectionInvoker);
invokers.put(MethodSignature.forSingleParam("equals", Object.class), reflectionInvoker);
invokers.put(MethodSignature.forVoidMethod("hashCode"), reflectionInvoker);
return invokers;//Collections.unmodifiableMap(invokers);
}
private static class ReflectionInvoker implements InvocationHandler, Serializable {
protected final Object obj;
ReflectionInvoker(final Object obj) {
this.obj = obj;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
try {
return method.invoke(obj, args);
} catch (InvocationTargetException e) {
throw e.getCause() == null ? e : e.getCause();
}
}
}
private static class MixinInvoker extends ReflectionInvoker {
private final Class> projectionInterface;
MixinInvoker(final Object obj, final Class> projectionInterface) {
super(obj);
this.projectionInterface = projectionInterface;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
injectMeAttribute((InternalProjection) proxy, obj, projectionInterface);
try {
return super.invoke(proxy, method, args);
} catch (InvocationTargetException e) {
throw e.getCause() == null ? e : e.getCause();
}
}
}
private static abstract class ProjectionMethodInvocationHandler implements InvocationHandler, Serializable {
private static final InvocationContext EMPTY_INVOCATION_CONTEXT = new InvocationContext(null, null, null, null);
static class InvocationContext {
public String getResolvedXPath() {
return resolvedXPath;
}
public XPath getxPath() {
return xPath;
}
public XPathExpression getxPathExpression() {
return xPathExpression;
}
public DuplexExpression getDuplexExpression() {
return duplexExpression;
}
public InvocationContext(final String resolvedXPath, final XPath xPath, final XPathExpression xPathExpression, final DuplexExpression duplexExpression) {
this.resolvedXPath = resolvedXPath;
this.xPath = xPath;
this.xPathExpression = xPathExpression;
this.duplexExpression = duplexExpression;
}
final String resolvedXPath;
final XPath xPath;
final XPathExpression xPathExpression;
final DuplexExpression duplexExpression;
/**
* @param resolvedXpath
* @return true if this invocation context may be reused for given path
*/
public boolean isStillValid(final String resolvedXpath) {
return resolvedXpath.equals(this.resolvedXPath);
}
/**
* @return Format pattern for the expression within with this context.
*/
public String getExpressionFormatPattern() {
return duplexExpression.getExpressionFormatPattern();
}
}
protected final Method method;
protected final String annotationValue;
protected final XBProjector projector;
protected final Node node;
private final String docAnnotationValue;
private final boolean isVoidMethod;
protected InvocationContext lastInvocationContext = EMPTY_INVOCATION_CONTEXT;
protected final Map methodParameterIndexes;
ProjectionMethodInvocationHandler(final Node node, final Method method, final String annotationValue, final XBProjector projector) {
this.method = method;
this.annotationValue = annotationValue;
this.projector = projector;
this.node = node;
final XBDocURL annotation = method.getAnnotation(XBDocURL.class);
this.docAnnotationValue = annotation == null ? null : annotation.value();
this.isVoidMethod = !ReflectionHelper.hasReturnType(method);
methodParameterIndexes = ReflectionHelper.getMethodParameterIndexes(method);
}
protected Node getNodeForMethod(final Method method, final Object[] args) throws SAXException, IOException, ParserConfigurationException {
if (docAnnotationValue != null) {
String uri = projector.config().getExternalizer().resolveURL(docAnnotationValue, method, args);
final Map requestParams = ((IOBuilder) projector.io()).filterRequestParamsFromParams(uri, args);
uri = Preprocessor.applyParams(uri, methodParameterIndexes, args);
return DOMHelper.getDocumentFromURL(projector.config().createDocumentBuilder(), uri, requestParams, method.getDeclaringClass());
}
return node;
}
protected String resolveXPath(final Object[] args) {
return Preprocessor.applyParams(projector.config().getExternalizer().resolveXPath(annotationValue, method, args), methodParameterIndexes, args);
}
/**
* Determine a methods return value that does not depend on the methods execution. Possible
* values are void or the proxy itself (would be "this").
*
* @param method
* @return
*/
protected Object getProxyReturnValueForMethod(final Object proxy, final Method method, final Integer numberOfChanges) {
if (isVoidMethod) {
return null;
}
if (method.getReturnType().equals(method.getDeclaringClass())) {
return proxy;
}
if ((numberOfChanges != null) && (method.getReturnType().isAssignableFrom(Integer.class) || method.getReturnType().isAssignableFrom(int.class))) {
return numberOfChanges;
}
throw new IllegalArgumentException("Method " + method + " has illegal return type \"" + method.getReturnType() + "\". I don't know what to return. I expected void or " + method.getDeclaringClass().getSimpleName());
}
abstract protected Object invokeProjection(final String resolvedXpath, final Object proxy, final Object[] args) throws Throwable;
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
final String xPath = resolveXPath(args);
final String resolvedXpath = Preprocessor.applyParams(xPath, methodParameterIndexes, args);
return invokeProjection(resolvedXpath, proxy, args);
}
}
private static abstract class XPathInvocationHandler extends ProjectionMethodInvocationHandler {
private XPathInvocationHandler(final Node node, final Method method, final String annotationValue, final XBProjector projector) {
super(node, method, annotationValue, projector);
}
@Override
final protected Object invokeProjection(final String resolvedXpath, final Object proxy, final Object[] args) throws Throwable {
final XPath xPath = projector.config().createXPath(DOMHelper.getOwnerDocumentFor(node));
if (!lastInvocationContext.isStillValid(resolvedXpath)) {
final DuplexExpression duplexExpression = new DuplexXPathParser().compile(resolvedXpath);
String strippedXPath = duplexExpression.getExpressionAsStringWithoutFormatPatterns();
if (duplexExpression.isUsingVariables()) {
XPathVariableResolver peviousResolver = xPath.getXPathVariableResolver();
xPath.setXPathVariableResolver(new MethodParamVariableResolver(method, args, duplexExpression, projector.config().getStringRenderer(), peviousResolver));
}
final XPathExpression xPathExpression = xPath.compile(strippedXPath);
lastInvocationContext = new InvocationContext(resolvedXpath, xPath, xPathExpression, duplexExpression);
}
return invokeXpathProjection(lastInvocationContext, proxy, args);
}
abstract protected Object invokeXpathProjection(final InvocationContext invocationContext, final Object proxy, final Object[] args) throws Throwable;
}
private static class ReadInvocationHandler extends XPathInvocationHandler {
private final boolean absentIsEmpty;
private final boolean wrappedInOptional;
private final Class> returnType;
private final Class> targetComponentType;
private final Class> exceptionType;
private final boolean isConvertable;
private final boolean isReturnAsNode;
private final boolean isEvaluateAsList;
private final boolean isEvaluateAsArray;
private final boolean isEvaluateAsSubProjection;
private final boolean isThrowIfAbsent;
ReadInvocationHandler(final Node node, final Method method, final String annotationValue, final XBProjector projector, final boolean absentIsEmpty) {
super(node, method, annotationValue, projector);
wrappedInOptional = ReflectionHelper.isOptional(method.getGenericReturnType());
returnType = wrappedInOptional ? ReflectionHelper.getParameterType(method.getGenericReturnType()) : method.getReturnType();
Class>[] exceptionTypes = method.getExceptionTypes();
exceptionType = exceptionTypes.length > 0 ? exceptionTypes[0] : null;
this.isConvertable = projector.config().getTypeConverter().isConvertable(returnType);
this.isReturnAsNode = Node.class.isAssignableFrom(returnType);
this.isEvaluateAsList = List.class.equals(returnType);
this.isEvaluateAsArray = returnType.isArray();
this.targetComponentType = isEvaluateAsList || isEvaluateAsArray ? findTargetComponentType(method) : null;
this.isEvaluateAsSubProjection = returnType.isInterface();
this.isThrowIfAbsent = exceptionType != null;
// Throwing exception overrides empty default value.
this.absentIsEmpty = absentIsEmpty && (!isThrowIfAbsent);
}
/**
* When reading collections, determine the collection component type.
*
* @param method
* @return
*/
private static Class> findTargetComponentType(final Method method) {
if (method.getReturnType().isArray()) {
return method.getReturnType().getComponentType();
}
assert List.class.equals(method.getReturnType());
final Type type = method.getGenericReturnType();
if (!(type instanceof ParameterizedType) || (((ParameterizedType) type).getActualTypeArguments() == null) || (((ParameterizedType) type).getActualTypeArguments().length < 1)) {
throw new IllegalArgumentException("When using List as return type for method " + method + ", please specify a generic type for the List. Otherwise I do not know which type I should fill the List with.");
}
assert ((ParameterizedType) type).getActualTypeArguments().length == 1 : "";
Type componentType = ((ParameterizedType) type).getActualTypeArguments()[0];
if (!(componentType instanceof Class)) {
throw new IllegalArgumentException("I don't know how to instantiate the generic type for the return type of method " + method);
}
return (Class>) componentType;
}
private List> evaluateAsList(final XPathExpression expression, final Node node, final Method method, final InvocationContext invocationContext) throws XPathExpressionException {
assert targetComponentType != null;
final NodeList nodes = (NodeList) expression.evaluate(node, XPathConstants.NODESET);
final List