gnu.jel.Library Maven / Gradle / Ivy
/*-*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* $Id: Library.java 490 2006-10-01 16:08:04Z metlov $
*
* This file is part of the Java Expressions Library (JEL).
* For more information about JEL visit :
* http://kinetic.ac.donetsk.ua/JEL/
*
* (c) 1998 -- 2007 by Konstantin Metlov([email protected]);
*
* JEL is Distributed under the terms of GNU General Public License.
* This code comes with ABSOLUTELY NO WARRANTY.
* For license details see COPYING file in this directory.
*/
package gnu.jel;
import gnu.jel.debug.Debug;
import java.lang.reflect.Member;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Iterator;
/**
* A namespace for JEL expressions.
* There are two types of members in the library, those which are stateless
* (i.e. their value depends only on their arguments, if there are any) and
* stateful (also called here dynamic). The result of evaluation of
* stateful members may depend on other factors besides their arguments.
*
*
Examples of possible stateless members are : Math.sin(double),
* Math.PI.
*
Examples of possible stateful members are : Math.random(),
* System.currentTimeMillis().
*
*
Stateless members of this library are always static members of the
* classes, which define them. The inverse is generally not true. However,
* this library goes as far as assuming that static members are stateless,
* if this assumption does not hold for some of Your members it is possible to
* mark them as stateful using the markStateDependent() method of
* this class.
*
*
The most crucial difference between the two kind of members of this
* library is that evaluation of stateless methods is attempted by JEL at
* a compile time during the constants folding phase.
*/
public class Library {
private HashMap> names;
private HashMap dynIDs;
private HashMap stateless;
private HashMap>> dotClasses;
private boolean noDotSecurity=false;
public DVMap resolver; // directly accessed from EC.jj
public HashMap cnmap;
/**
* Creates a library for JEL.
* See the three argument constructor for more info.
* @param staticLib is the array of classes, whose public static
* methods are exported.
* @param dynamicLib is the array of classes, whose public virutal
* methods are exported.
* @deprecated Please us 5 argument constructor with unused arguments
* set to null. This constructor is scheduled for removal in
* JEL 1.0.
*/
public @Deprecated Library(Class[] staticLib,Class[] dynamicLib) {
this(staticLib,dynamicLib,null);
};
/**
* Creates a library for JEL.
*
See the three argument constructor for more info.
* @param staticLib is the array of classes, whose public static
* methods are exported.
* @param dynamicLib is the array of classes, whose public virutal
* methods are exported.
* @param dotClasses Controls access to the "dot" operator on classes.
* @deprecated Please us 5 argument constructor with unused arguments
* set to null. This constructor is scheduled for removal in
* JEL 1.0.
*/
public @Deprecated Library(Class[] staticLib,Class[] dynamicLib,Class[] dotClasses) {
this(staticLib,dynamicLib,dotClasses,null);
};
/**
* Creates a library for JEL.
*
See the three argument constructor for more info.
* @param staticLib is the array of classes, whose public static
* methods are exported.
* @param dynamicLib is the array of classes, whose public virutal
* methods are exported.
* @param dotClasses Controls access to the "dot" operator on classes.
* @param resolver Controls resolution of dynamic variables.
* @deprecated Please us 5 argument constructor with unused arguments
* set to null. This constructor is scheduled for removal in
* JEL 1.0.
*/
public @Deprecated Library(Class[] staticLib,Class[] dynamicLib,Class[] dotClasses,
DVMap resolver) {
this(staticLib,dynamicLib,dotClasses,resolver,null);
};
/**
* Creates a library for JEL.
*
The following should be kept in mind when constructing a library:
*
* - This constructor may throw IllegalArgumentException if it does not
* like something in Your library. The requirements to the method names
* are somewhat more strict, than in Java because members of several
* classes can be merged in root namespace.
*
- When calling the
* CompiledExpression.evaluate(Object[] dynalib) of the
* expression, using dynamic library methods it is needed to pass as
* dynalib parameter the array of objects, of the classes
* _exactly_ in the same order as they are appearing in the
* dynamicLib parameter of this constructor. If You do not
* allow to call dynamic methods (there is no sense, then, to use a compiler)
* it is possible to pass null istead of dynalib.
*
- Generally speaking, this class should not let You to create wrong
* libraries. It's methods will throw exceptions, return false's ,
* ignore Your actions,... ;)
*
* If methods in the library classes conflict with each other, the last
* conflicting method will be skipped. You will not get any messages unless
* debugging is ON (see gnu.jel.debug.Debug.enabled). This is
* done to avoid unnecessary error messages in the production code of the
* compiler.
* The array (dotClasses), which is the third argument of
* this constructor determines how (and whether) to compile the
* dot operators encountered in expressions. These operators are
* of the form <object>.(<method>|<field>),
* which means to call method (access field)
* of an <object>. There can be three types of the behaviour:
*
1) dot operators are prohibited (dotClasses==null),
* this is behaviour of older version of JEL.
*
2) dot operators are allowed on all classes
* (dotClasses==new Class[0], an empty array).
* Depending on the types of objects returned by the static/dynamic library
* classes this may pose a security risk.
*
3) dot operators are allowed only on some classes. This is achieved
* by listing these classes in the dotClasses array.
* @param staticLib is the array of classes, whose public static
* methods are exported.
* @param dynamicLib is the array of classes, whose public virutal
* methods are exported.
* @param dotClasses is the array of classes on which the dot ('.')
* operation is allowed.
* @param resolver is the object used to resolve the names.
* @param cnmap Maps class names into classes for non-primitive type casts.
*/
public Library(Class[] staticLib,Class[] dynamicLib,Class[] dotClasses,
DVMap resolver,HashMap cnmap) {
this.cnmap=cnmap;
this.resolver=resolver;
if (dotClasses==null) {
this.dotClasses=null;
} else {
noDotSecurity=(dotClasses.length==0);
this.dotClasses=
new HashMap>>();
// hash the names
Class[] temp=new Class[1];
for(int i=0;i>();
dynIDs = new HashMap();
stateless=new HashMap();
if (staticLib!=null)
rehash(staticLib,names,null,stateless);
if (dynamicLib!=null)
rehash(dynamicLib,names,dynIDs,null);
};
private void rehash(Class cls) {
HashMap> tempNames=new HashMap>();
Class[] temp=new Class[1];
temp[0]=cls;
// rehash(temp,tempNames,null,new HashMap());
rehash(temp,tempNames,new HashMap(),null);
dotClasses.put(cls,tempNames);
};
private static void rehash(Class[] arr,
HashMap>hashedNames,
HashMap dynIDs,
HashMap stateless) {
for (int i=0; i0) { // static
if ((stateless!=null) && rehash(hashedNames,m))
stateless.put(m,Boolean.TRUE);
} else { // not static
if ((dynIDs!=null) && rehash(hashedNames,m))
dynIDs.put(m,dynID);
};
};
};
};
private static boolean rehash(HashMap> hashedNames, Member m) {
String name=m.getName();
String signature=getSignature(m);
// for the purpose of matching fields behave like methods with no
// arguments
if (isField(m)) signature="()"+signature;
HashMap signatures=hashedNames.get(name);
if (signatures==null) {
// No method with this name was added
HashMap signatures_new=new HashMap();
signatures_new.put(signature,m);
hashedNames.put(name,signatures_new);
return true;
};
// Name exists in the library, check for possible signature conflict.
Object conflicting_method=signatures.get(signature);
if (conflicting_method==null) { // No conflict
signatures.put(signature,m);
return true;
};
// if (Debug.enabled) {
// Debug.println("Conflict was detected during the library "+
// "initialization."+
// " Conflicting "+"\""+name+signature+
// "\", conflicting :"+ conflicting_method+" and "+m+" .");
// };
// If no debug then the method is ignored.
return false;
};
/**
* This method marks a static member as having the internal state.
* If java.lang.Math is included into the library it is
* necessary to mark java.lang.Math.random() as having the
* state. This can be done by calling
* markStateDependent("random",null).
*
Please specify parameters as close as possible, otherwise you can
* accidentally mark another function.
* @param name is the function name.
* @param params are the possible invocation parameters of the function.
* @exception CompilationException if the method can't be resolved
*/
public void markStateDependent(String name, Class[] params)
throws CompilationException {
Object m=getMember(null,name,params);
Object removed=stateless.remove(m);
if (Debug.enabled)
Debug.check(removed!=null,"State dependent methos \""+m+
"\"is made state dependend again.");
};
/**
* Used to check if the given method is stateless.
* @param o is method or field to check.
* @return true if the method is stateless and can be invoked at
* compile time.
*/
public boolean isStateless(Member o) {
return stateless.containsKey(o);
};
/**
* Searches the namespace defined by this library object for method or field.
*
The method with the same name, and closest (convertible) parameter
* types is returned. If there are several methods the most specific one
* is used.
*
Ambiguities are detected. Example of detectable ambiguity:
* you ask for a call someName(int, int), but there are two
* applicable methods someName(int, double) and
* someName(double, int). Requirements to parameter types
* of both can be satisfied by _widening_ conversions. Thus, there
* is no most specific method of these two in terms of Java Language
* Specification (15.11.2.2). It means, that an ambiguity is present,
* and null will be returned.
*
Java compiler normally would not allow to define such ambiguous
* methods in the same class. However, as this library is assembled from
* several Java classes, such ambiguities can happen, and should be
* detected.
* @param container the class to search the method within, if null
* the root namespace is searched.
* @param name is the name of the method to find.
* @param params are the types of formal parameters in the method invocation.
* @return the method/field object of the resolved method/field.
* @exception CompilationException if the method can't be resolved
*/
public Member getMember(Class container,String name,Class[] params)
throws CompilationException {
HashMap> hashedMembers=names;
// dot operator security
if (container!=null) {
if (dotClasses==null) // dot operator is prohibited
throw new CompilationException(11,null);
else if (! (noDotSecurity || dotClasses.containsKey(container))) {
// dot is not allowed in this particular class
Object[] paramsExc={container};
throw new CompilationException(12,paramsExc);
};
if ((hashedMembers=dotClasses.get(container))==null) {
rehash(container);
hashedMembers=dotClasses.get(container);
};
};
HashMap signatures=hashedMembers.get(name);
// System.out.print("RESOLVING: ");
// System.out.println(describe(name,params));
if (signatures==null) { // name is not found
Object[] paramsExc={name,container};
throw new CompilationException(container==null?5:6,paramsExc);
};
// Choose applicable methods
ArrayList applicable_methods=new ArrayList();
for(Member cm: signatures.values()) {
Class[] cp=getParameterTypes(cm);
boolean applicable=false;
if (params!=null) { // if no parameters requested
if (cp.length==params.length) { // If number of arguments matches
applicable=true;
for(int i=0;((i e=applicable_methods.iterator();
Member most_specific=e.next();
Class[] most_specific_params=getParameterTypes(most_specific);
// System.out.println("--- APPLICABLE METHODS ---");
// System.out.println(most_specific.getName()+
// ClassFile.getSignature(most_specific));
while (e.hasNext()) {
Member cm= e.next();
Class[] cp=getParameterTypes(cm);
boolean moreSpecific=true;
boolean lessSpecific=true;
// System.out.println(cm.getName()+ClassFile.getSignature(cm));
for(int i=0; i ID's are used to locate the pointers to the objects, implementing
* dynamic methods, in the array, argument of evaluate(Object[]) function.
* @param m method to get an ID of.
* @return the ID of the method or -1 if the method is static.
* @exception NullPointerException if method is not a dynamic method of
* this library.
*/
public int getDynamicMethodClassID(Member m) {
Integer id=dynIDs.get(m);
if (id==null) return -1;
return id.intValue();
};
/**
* Used to get return type of a class member.
* The type of a method is its return type, the type of a constructor is
* void.
* @param m member whose type is to be determined
* @return type of the member
*/
public static Class getType(Member m) {
if (m instanceof Method) return ((Method)m).getReturnType();
if (m instanceof Field) return ((Field)m).getType();
if (m instanceof LocalField) return ((LocalField)m).getType();
// otherwise it must be java.lang.reflect.Constructor
if (Debug.enabled)
Debug.check(m instanceof java.lang.reflect.Constructor);
return OP.specialTypes[9]; // java.lang.reflect.Void.TYPE
};
/**
* Used to get types of formal parameters of a member.
*
The reference to the class instance "this" is not counted by
* this method.
* @param m member whose formal parameters are to be obtained
* @return array of formal parameter types (empty array if none).
*/
public static Class[] getParameterTypes(Member m) {
if (m instanceof Method) return ((Method)m).getParameterTypes();
if (m instanceof LocalMethod) return ((LocalMethod)m).getParameterTypes();
if (m instanceof Constructor) return ((Constructor)m).getParameterTypes();
if (Debug.enabled)
Debug.check((m instanceof Field)||(m instanceof LocalField));
return new Class[0];
};
/**
* Computes signature of the given member.
* @param m the member to compute the sugnature of.
* @return the signature.
*/
public static String getSignature(Member m) {
StringBuilder signature=new StringBuilder();
if (!isField(m)) {
Class parameters[]=getParameterTypes(m);
signature.append('(');
for(int i=0;i The signature of the class (Field descriptor) is the string and
* it's format is described in the paragraph 4.3.2 of the Java VM
* specification (ISBN 0-201-63451-1).
* The same can be done using java.lang.Class.getName() by
* converting it's result into the "historical form".
*
This utility method can be used outside of the JEL package
* it does not involve any JEL specific assumptions and should follow
* JVM Specification precisely.
* @param cls is the class to compute the signature of. Can be primitive or
* array type.
* @return the class signature.
*/
public static String getSignature(Class cls) {
return appendSignature(new StringBuilder(),cls).toString();
};
private static StringBuilder appendSignature(StringBuilder buff, Class cls) {
if (cls.isPrimitive()) {
int tid;
buff.append((tid=OP.typeID(cls))>9?'L':"ZBCSIJFDLV".charAt(tid));
} else if (cls.isArray()) {
buff.append('[');
appendSignature(buff,cls.getComponentType());
} else { // just a class
buff.append('L');
appendHistoricalForm(buff,cls.getName());
buff.append(';');
};
return buff;
};
public static String toHistoricalForm(String className) {
return appendHistoricalForm(new StringBuilder(),className).toString();
};
private static StringBuilder appendHistoricalForm(StringBuilder buff,
String className) {
int namelen=className.length();
for(int i=0;i