All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.exist.xquery.JavaCall Maven / Gradle / Ivy

There is a newer version: 6.3.0
Show newest version
/*
 * eXist-db Open Source Native XML Database
 * Copyright (C) 2001 The eXist-db Authors
 *
 * [email protected]
 * http://www.exist-db.org
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.exist.xquery;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import org.exist.dom.QName;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.JavaObjectValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.Type;

/**
 * A special function call to a Java method or constructor.
 * 
 * This class handles all function calls who's namespace URI
 * starts with "java:".
 * 
 * @author Wolfgang Meier
 */
public class JavaCall extends Function {

	private final QName qname;
	private String name;
	private Class myClass = null;
	private List candidateMethods = new ArrayList<>(5);

	/**
	 * Create new call on the Java method or constructor identified by the QName.
	 *
	 * @param context current context
	 * @param qname the name of the function
	 * @throws XPathException in case of a static error
	 */
	public JavaCall(XQueryContext context, QName qname) throws XPathException {
		super(context, null);
		this.qname = qname;
		String namespaceURI = context.getURIForPrefix(qname.getPrefix());
		if (!namespaceURI.startsWith("java:"))
			{throw new XPathException(this,
				"Internal error: prefix "
					+ qname.getPrefix()
					+ " does not "
					+ "resolve to a Java class");}
		namespaceURI = namespaceURI.substring("java:".length());

		try {
			LOG.debug("Trying to find class {}", namespaceURI);
			
			myClass = Class.forName(namespaceURI);
		} catch (final ClassNotFoundException e) {
			throw new XPathException(this, "Class: " + namespaceURI + " not found", e);
		}

		name = qname.getLocalPart();
		// convert hyphens into camelCase
		if (name.indexOf('-') > 0) {
			final StringBuilder buf = new StringBuilder();
			boolean afterHyphen = false;
			char ch;
			for (int i = 0; i < name.length(); i++) {
				ch = name.charAt(i);
				if (ch == '-')
					{afterHyphen = true;}
				else {
					if (afterHyphen) {
						buf.append(Character.toUpperCase(ch));
						afterHyphen = false;
					} else
						{buf.append(ch);}
				}
			}
			name = buf.toString();
			LOG.debug("converted method name to {}", name);
		}
	}

	/* (non-Javadoc)
	 * @see org.exist.xquery.Function#getName()
	 */
	public QName getName() {
		return qname;
	}

	@Override
	public Cardinality getCardinality() {
		return Cardinality.ZERO_OR_MORE;
	}
	
	/* (non-Javadoc)
	 * @see org.exist.xquery.Function#setArguments(java.util.List)
	 */
	public void setArguments(List arguments) throws XPathException {
		final int argCount = arguments.size();

        steps.addAll(arguments);

		// search for candidate methods matching the given number of arguments
		if ("new".equals(name)) {
			final Constructor[] constructors = myClass.getConstructors();
            for (final Constructor constructor : constructors) {
                if (Modifier.isPublic(constructor.getModifiers())) {
                    final Class paramTypes[] = constructor.getParameterTypes();
                    if (paramTypes.length == argCount) {
						LOG.debug("Found constructor {}", constructor.toString());
                        candidateMethods.add(constructor);
                    }
                }
            }
			if (candidateMethods.size() == 0) {
				final String message = "no constructor found with " + argCount + " arguments"; 
				throw new XPathException(this,
					message,
					new NoSuchMethodException(message));
			}
		} else {
			final Method[] methods = myClass.getMethods();
            for (final Method method : methods) {
                if (Modifier.isPublic(method.getModifiers())
                        && method.getName().equals(name)) {
                    final Class paramTypes[] = method.getParameterTypes();
                    if (Modifier.isStatic(method.getModifiers())) {
                        if (paramTypes.length == argCount) {
							LOG.debug("Found static method {}", method.toString());
                            candidateMethods.add(method);
                        }
                    } else {
                        if (paramTypes.length == argCount - 1) {
							LOG.debug("Found method {}", method.toString());
                            candidateMethods.add(method);
                        }
                    }
                }
            }
			if (candidateMethods.size() == 0) {
				final String message = "no method matches " + name + " with " + argCount + " arguments"; 
				throw new XPathException(this,
					message,
					new NoSuchMethodException(message));
			}
		}
	}

    /* (non-Javadoc)
     * @see org.exist.xquery.Function#analyze(org.exist.xquery.AnalyzeContextInfo)
     */
    public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
    }
    
	/* (non-Javadoc)
	 * @see org.exist.xquery.Expression#eval(org.exist.xquery.value.Sequence, org.exist.xquery.value.Item)
	 */
	public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
		
        if (context.getProfiler().isEnabled()) {
            context.getProfiler().start(this);       
            context.getProfiler().message(this, Profiler.DEPENDENCIES, "DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies()));
            if (contextSequence != null)
                {context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT SEQUENCE", contextSequence);}
            if (contextItem != null)
                {context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT ITEM", contextItem.toSequence());}
        }

		// get the actual arguments
		final Sequence args[] = getArguments(contextSequence, contextItem);

		AccessibleObject bestMethod = candidateMethods.get(0);
		int conversionPrefs[] = getConversionPreferences(bestMethod, args);

		for (AccessibleObject nextMethod : candidateMethods) {
			int prefs[] = getConversionPreferences(nextMethod, args);
			for (int j = 0; j < prefs.length; j++) {
				if (prefs[j] < conversionPrefs[j]) {
					bestMethod = nextMethod;
					conversionPrefs = prefs;
					break;
				}
			}
		}
//		LOG.debug("calling method " + bestMethod.toString());
		Class paramTypes[] = null;
		boolean isStatic = true;
		if (bestMethod instanceof Constructor)
			{paramTypes = ((Constructor) bestMethod).getParameterTypes();}
		else {
			paramTypes = ((Method) bestMethod).getParameterTypes();
			isStatic = Modifier.isStatic(((Method) bestMethod).getModifiers());
		}

		final Object[] params = new Object[isStatic ? args.length : args.length - 1];
		if (isStatic) {
			for (int i = 0; i < args.length; i++) {
				params[i] = args[i].toJavaObject(paramTypes[i]);
			}
		} else {
			for (int i = 1; i < args.length; i++) {
				params[i - 1] = args[i].toJavaObject(paramTypes[i - 1]);
			}
		}
        
        Sequence result;
		if (bestMethod instanceof Constructor) {
			try {
				final Object object = ((Constructor) bestMethod).newInstance(params);
                result = new JavaObjectValue(object);
			} catch (final IllegalArgumentException e) {
				throw new XPathException(this,
					"illegal argument to constructor "
						+ bestMethod.toString()
						+ ": "
						+ e.getMessage(),
					e);
			} catch (final Exception e) {
				if (e instanceof XPathException)
					{throw (XPathException) e;}
				else
					{throw new XPathException(this,
						"exception while calling constructor "
							+ bestMethod.toString()
							+ ": "
							+ e.getMessage(),
						e);}
			}
		} else {
			try {
				Object invocationResult;
				if (isStatic)
                    {invocationResult = ((Method) bestMethod).invoke(null, params);}
				else {
                    invocationResult =
						((Method) bestMethod).invoke(
							args[0].toJavaObject(myClass),
							params);
				}
                result = XPathUtil.javaObjectToXPath(invocationResult, getContext());
			} catch (final IllegalArgumentException e) {
				throw new XPathException(this,
					"illegal argument to method "
						+ bestMethod.toString()
						+ ": "
						+ e.getMessage(),
					e);
			} catch (final Exception e) {
				if (e instanceof XPathException)
					{throw (XPathException) e;}
				else
					{throw new XPathException(this,
						"exception while calling method "
							+ bestMethod.toString()
							+ ": "
							+ e.getMessage(),
						e);}
			}
		}

         if (context.getProfiler().isEnabled())           
                {context.getProfiler().end(this, "", result);} 
        
        if (result==null) {result = Sequence.EMPTY_SEQUENCE;} 
         
        return result;
        
	}

	/* (non-Javadoc)
	 * @see org.exist.xquery.Function#returnsType()
	 */
	public int returnsType() {
		return Type.ITEM;
	}
	
	private int[] getConversionPreferences(AccessibleObject method, Sequence[] args) {
		final int prefs[] = new int[args.length];
		Class paramTypes[] = null;
		boolean isStatic = true;
		if (method instanceof Constructor)
			{paramTypes = ((Constructor) method).getParameterTypes();}
		else {
			paramTypes = ((Method) method).getParameterTypes();
			isStatic = Modifier.isStatic(((Method) method).getModifiers());
			if (!isStatic) {
				Class nonStaticTypes[] = new Class[paramTypes.length + 1];
				nonStaticTypes[0] = myClass;
				System.arraycopy(paramTypes, 0, nonStaticTypes, 1, paramTypes.length);
				paramTypes = nonStaticTypes;
			}
		}
		for (int i = 0; i < args.length; i++) {
			prefs[i] = args[i].conversionPreference(paramTypes[i]);
		}
		return prefs;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy