package com.dbxml.labrador.objects;
/*
* The dbXML Labrador Software License, Version 1.0
*
*
* Copyright (c) 2003 The dbXML Group, L.L.C. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by The
* dbXML Group, L.L.C. (http://www.dbxml.com/)."
* Alternately, this acknowledgment may appear in the software
* itself, if and wherever such third-party acknowledgments normally
* appear.
*
* 4. The names "Labrador" and "dbXML Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* info@dbxml.com
*
* 5. Products derived from this software may not be called "Labrador",
* nor may "Labrador" appear in their name, without prior written
* permission of The dbXML Group, L.L.C..
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE DBXML GROUP, L.L.C. OR ITS
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* $Id: ObjectDiscovery.java,v 1.7 2003/10/29 20:11:16 bradford Exp $
*/
import com.dbxml.labrador.Discovery;
import com.dbxml.labrador.types.ArrayConversions;
import com.dbxml.labrador.types.TypeConversions;
import com.dbxml.labrador.types.Types;
import com.dbxml.labrador.types.Variant;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* ObjectDiscovery is an Object reflection and method invocation utility for
* the standard Labrador object registration system.
*/
public final class ObjectDiscovery implements Discovery {
private static final String[] EmptyStrings = new String[0];
private static final Set ObjectMethods = new HashSet();
static {
Class c = Object.class;
Method[] m = c.getMethods();
for ( int i = 0; i < m.length; i++ ) {
String name = m[i].getName();
if ( !ObjectMethods.contains(name) )
ObjectMethods.add(name);
}
}
private static final String PARAMS_PREFIX = "PARAMS_";
private static final String IGNORE_METHODS = "IGNORE_METHODS";
private static final String INCLUDE_METHODS = "INCLUDE_METHODS";
private Class cl;
private Map methods = new HashMap(); // MethodInfo
public ObjectDiscovery(Class cl) throws Exception {
this.cl = cl;
reflect();
}
/**
* reflect performs an initial reflection against the Class that this
* Discovery will support. This method will not be capable of reflecting
* parameter names because the Java compiler throws them away. In order to
* create parameter name mappings, you must call addMethod.
*/
public void reflect() throws Exception {
// Check For Ignored Methods
Set ignoreMethods = new HashSet();
Object ignoreObj = getStaticField(IGNORE_METHODS);
if ( ignoreObj instanceof String[] ) {
String[] ignore = (String[])ignoreObj;
for ( int i = 0; i < ignore.length; i++ ) {
String name = ignore[i];
if ( !ignoreMethods.contains(name) )
ignoreMethods.add(name);
}
}
// Check For Included Methods
Set includeMethods = new HashSet();
Object includeObj = getStaticField(INCLUDE_METHODS);
if ( includeObj instanceof String[] ) {
String[] include = (String[])includeObj;
for ( int i = 0; i < include.length; i++ ) {
String name = include[i];
if ( !includeMethods.contains(name) ) {
includeMethods.add(name);
ignoreMethods.remove(name); // No Point Being There
}
}
}
Method[] m = cl.getDeclaredMethods();
Set defined = new HashSet();
for ( int i = 0; i < m.length; i++ ) {
String name = m[i].getName();
// Check Modifiers
int mod = m[i].getModifiers();
if ( !Modifier.isPublic(mod) || Modifier.isStatic(mod) )
continue;
// Check Valid Return Types
Class ret = m[i].getReturnType();
int retType = Types.typeOf(ret);
if ( retType == Types.UNKNOWN )
continue;
// If Not Included In INCLUDE_METHODS, Do A Special Check
if ( !includeMethods.contains(name) ) {
// Ignore Special Java Methods
if ( ObjectMethods.contains(name) )
continue;
// Ignore Methods Specified By IGNORE_METHODS
if ( ignoreMethods.contains(name) )
continue;
}
if ( defined.contains(name) )
throw new Exception("Duplicate Exposed Method '" + name + "'");
// Check Valid Parameter Types
Class[] p = m[i].getParameterTypes();
ParamInfo[] params = new ParamInfo[p.length];
String[] names = getFieldVals(cl, p.length, PARAMS_PREFIX + name);
boolean good = true;
for ( int j = 0; j < p.length && good; j++ ) {
String pname;
if ( names != null )
pname = names[j];
else
pname = "arg" + j;
int parmType = Types.typeOf(p[j]);
good = parmType != Types.UNKNOWN;
if ( p[j].isArray() )
params[j] = new ParamInfo(pname, parmType, p[j].getComponentType(), true);
else
params[j] = new ParamInfo(pname, parmType, p[j], false);
}
if ( !good )
continue;
if ( ret.isArray() )
methods.put(name, new MethodInfo(name, retType, ret.getComponentType(), true, m[i], params));
else
methods.put(name, new MethodInfo(name, retType, ret, false, m[i], params));
defined.add(name);
}
}
public Object getStaticField(String name) {
try {
Field f = cl.getDeclaredField(name);
int mod = f.getModifiers();
if ( Modifier.isStatic(mod) && Modifier.isPublic(mod) )
return f.get(null);
}
catch ( Exception e ) {
}
return null;
}
private String[] getFieldVals(Class c, int len, String name) {
try {
Field f = c.getDeclaredField(name);
int mod = f.getModifiers();
if ( Modifier.isStatic(mod) && Modifier.isFinal(mod)
&& f.getType().isArray() && f.getType().getComponentType() == String.class ) {
String[] res = (String[])f.get(null);
if ( res.length == len )
return res;
}
}
catch ( Exception e ) {
}
return null;
}
public String[] listMethods() {
return (String[])methods.keySet().toArray(EmptyStrings);
}
public int getParamCount(String method) {
MethodInfo info = (MethodInfo)methods.get(method);
if ( info != null )
return info.params.length;
else
return 0;
}
public String[] listParams(String name) {
MethodInfo info = (MethodInfo)methods.get(name);
if ( info != null )
return info.paramNames;
else
return null;
}
public int getReturnType(String method) {
MethodInfo info = (MethodInfo)methods.get(method);
if ( info != null )
return info.type;
else
return Types.UNKNOWN;
}
public boolean isReturnArray(String method) {
MethodInfo info = (MethodInfo)methods.get(method);
if ( info != null )
return info.array;
else
return false;
}
public int getParamType(String method, int index) {
MethodInfo info = (MethodInfo)methods.get(method);
if ( info != null && index < info.params.length )
return info.params[index].type;
else
return Types.UNKNOWN;
}
public boolean isParamArray(String method, int index) {
MethodInfo info = (MethodInfo)methods.get(method);
if ( info != null && index < info.params.length )
return info.params[index].array;
else
return false;
}
// Invocation Methods
/**
* invoke invokes the named method against the specified
* Object, passing to the method the set of specified parameters.
*
* @param obj The target object
* @param name The method name
* @param params The parameter values
* @return The result of the method
* @throws Throwable if an invocation error occurs
*/
public Object invoke(Object obj, String name, Object[] params) throws Throwable {
Variant[] v = new Variant[params.length];
for ( int i = 0; i < v.length; i++ )
v[i] = new Variant(params[i]);
return invoke(obj, name, v);
}
/**
* invoke invokes the named method against the specified Object,
* passing to the method the set of specified parameters. This method will
* convert the Variant array in args to the appropriate Object types before
* invoking the method.
*
* @param obj The target object
* @param name The method name
* @param args The parameter values
* @return The result of the method
* @throws Throwable if an invocation error occurs
*/
private Object invoke(Object obj, String name, Variant[] args) throws Throwable {
MethodInfo info = (MethodInfo)methods.get(name);
if ( info != null ) {
Object[] params = new Object[info.params.length];
for ( int i = 0; i < info.params.length; i++ ) {
ParamInfo p = info.params[i];
if ( !p.array )
params[i] = TypeConversions.toTypedValue(p.type, args[i], p.classType);
else {
if ( args[i].getType() == Types.LIST ) {
List l = args[i].getList();
params[i] = ArrayConversions.toTypedArray(p.type, l, p.classType);
}
else if ( p.type == Types.BYTE && args[i].isArray() )
params[i] = (byte[])args[i].getObject();
else if ( args[i].getType() != p.type ) {
// Try to convert the list
List l = ArrayConversions.toList(args[i]);
params[i] = ArrayConversions.toTypedArray(p.type, l, p.classType);
}
else
params[i] = args[i];
}
}
try {
return info.method.invoke(obj, params);
}
catch ( InvocationTargetException e ) {
throw e.getTargetException();
}
}
else
throw new Exception("Method '" + name + "' Not Found");
}
/**
* ParamInfo encapsulates parameter type and name information
*/
private class ParamInfo {
public String name;
public int type;
public Class classType;
public boolean array;
public ParamInfo(String name, int type, Class classType, boolean array) {
this.name = name;
this.type = type;
this.classType = classType;
this.array = array;
}
}
/**
* MethodInfo encapsulates Method result, name, and parameter information
*/
private class MethodInfo extends ParamInfo {
public Method method;
public ParamInfo[] params;
public String[] paramNames;
public MethodInfo(String name, int type, Class component, boolean array, Method method, ParamInfo[] params) {
super(name, type, component, array);
this.method = method;
this.params = params;
calcParamNames();
}
public void calcParamNames() {
paramNames = new String[params.length];
for ( int i = 0; i < paramNames.length; i++ )
paramNames[i] = params[i].name;
}
}
}