com.gentlyweb.utils.Accessor Maven / Gradle / Ivy
/*
* Copyright 2006 - Gary Bentley
*
* 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 com.gentlyweb.utils;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.StringTokenizer;
/**
* This class is used to perform access into a Java object using a
* String value with a specific notation.
*
* The Accessor uses a dot notation such as field1.method1.method2 to
* perform the access on an object. Each value in the notation refers to
* a field or method (a no argument method) of the type of the previous
* value.
* For instance if you have the following class structure:
*
*
* public class A
* {
* public B = new B ();
* }
*
* public class B
* {
* public C = new C ();
* }
*
* public class C
* {
* String d = "";
* }
*
*
* You would then use the notation: B.C.d to get access to
* field d in Class C.
*
* The Accessor also supports a [ ] notation for accessing
* into Lists/Maps and Arrays. If the value between the [ ]
* is an integer then we look for the associated type to be either
* an array or a List, we then index into it with the integer. If
* the value is NOT an integer then we use assume the
* type is a Map and use it as a key into the Map.
*
* For instance changing the example above:
*
*
* public class A
* {
* public List vals = new ArrayList ();
* }
*
*
* Now we could use: vals[X] where X is a positive integer.
* Or changing again:
*
*
* public class A
* {
* public Map vals = new HashMap ();
* }
*
*
* We could use: vals[VALUE] where VALUE would then be
* used as a Key into the vals HashMap.
*
* Note: The Accessor is NOT designed to be an all purpose
* method of gaining access to a class. It has specific uses and for
* most will be of no use at all. It should be used for general purpose
* applications where you want to access specific fields of an object
* without having to know the exact type. One such application is in
* the {@link GeneralComparator}, in that case arbitrary Objects can
* be sorted without having to write complex Comparators or implementing
* the Comparable interface AND it gives the flexibility that sorting
* can be changed ad-hoc.
*
* The Accessor looks for in the following order:
*
* - Public fields with the specified name.
* - If no field is found then the name is converted to a "JavaBeans"
* get method, so a field name of value would be converted
* to getValue and that method is looked for. The method must take
* no arguments.
* - If we don't find the get* method then we look for a method with
* the specified name. So a field name of value would mean that
* a method (that again takes no arguments) is looked for.
*
*
* Note: we have had to add the 3rd type to allow for methods that don't follow
* JavaBeans conventions (there are loads in the standard Java APIs which makes
* accessing impossible otherwise).
*/
public class Accessor
{
private Object accessor = null;
private String index = "";
public String getIndex ()
{
return this.index;
}
public void setIndex (String index)
{
this.index = index;
}
public void setAccessor (Field f)
{
this.accessor = f;
}
public void setAccessor (Method m)
{
this.accessor = m;
}
public boolean isIndexCorrectForType (Class c)
{
// See if the index is a number...in which case
// the class should be either an Array or a List.
if (this.index.equals (""))
{
return true;
}
try
{
Integer.parseInt (this.index);
if ((c.isArray ())
||
(c.isAssignableFrom (List.class))
)
{
return true;
}
} catch (NumberFormatException e) {
if (c.isAssignableFrom (Map.class))
{
return true;
}
}
return false;
}
public static Object getValueFromAccessorChain (Object obj,
List chain)
throws IllegalAccessException,
InvocationTargetException
{
// For our accessor chain, use the Field and Methods
// to get the actual value.
Object retdata = obj;
for (int i = 0; i < chain.size (); i++)
{
Accessor a = (Accessor) chain.get (i);
// See what type the accessor is...
if (a.accessor instanceof Method)
{
Method m = (Method) a.accessor;
Object[] parms = {};
// Invoke the method...
retdata = m.invoke (retdata,
parms);
if (retdata == null)
{
return null;
}
}
if (a.accessor instanceof Field)
{
// It's a field...so...
Field f = (Field) a.accessor;
// Now get the value...
retdata = f.get (retdata);
}
// See if we have an index...
if (!a.getIndex ().equals (""))
{
retdata = Accessor.getValueForIndex (retdata,
a.getIndex ());
if (retdata == null)
{
return null;
}
}
}
return retdata;
}
public static Object getValueForIndex (Object data,
String index)
{
try
{
int in = Integer.parseInt (index);
// See if we have an Array or
// List.
if (data.getClass ().isArray ())
{
if (in < Array.getLength (data))
{
// It's an array.
return Array.get (data,
in);
}
} else {
if (data instanceof List)
{
List l = (List) data;
if (in < l.size ())
{
return l.get (in);
}
}
}
} catch (NumberFormatException e) {
// It's not a number so assume that
// we have a Map.
if (data instanceof Map)
{
Map map = (Map) data;
return map.get (index);
}
}
return null;
}
/**
* Get the Java field associated with the named field. Return
* null if there isn't one, or if we can't access it.
*
* @param field The name of the field.
* @param clazz The Class to get the field from.
* @return A List of Accessor objects used for delving into the
* classes to be sorted.
*/
public static List getAccessorChain (String accessorRef,
Class clazz)
throws IllegalArgumentException
{
StringTokenizer t = new StringTokenizer (accessorRef,
".");
Class c = clazz;
List retdata = new ArrayList ();
while (t.hasMoreTokens ())
{
String tok = t.nextToken ();
Accessor a = new Accessor ();
String index = "";
if (tok.endsWith ("]"))
{
// It does, so now find the [
index = tok.substring ((tok.indexOf ("[") + 1),
tok.length () - 1);
a.setIndex (index);
tok = tok.substring (0,
tok.indexOf ("["));
}
// Get the Fields.
Field[] fields = c.getFields ();
Field f = null;
// See if the token matches...
for (int i = 0; i < fields.length; i++)
{
if (fields[i].getName ().equals (tok))
{
// Found it...
f = fields[i];
break;
}
}
if (f != null)
{
c = f.getType ();
if (!a.isIndexCorrectForType (c))
{
throw new IllegalArgumentException ("Field: " +
tok +
" cannot be accessed with index: " +
index +
" since field type is: " +
c.getName ());
}
a.accessor = f;
retdata.add (a);
} else {
// Now convert it to a method name and use the
// JavaBeans convention...
StringBuffer name = new StringBuffer (tok);
name.setCharAt (0,
Character.toUpperCase (tok.charAt (0)));
name.insert (0,
"get");
// Now get the method...
Method m = Accessor.getNoParmJavaMethod (name.toString (),
c);
if (m != null)
{
c = m.getReturnType ();
if (!a.isIndexCorrectForType (c))
{
throw new IllegalArgumentException ("Method: " +
tok +
" cannot be called with index: " +
index +
" since method return type is: " +
c.getName ());
}
if (Void.class.isAssignableFrom (c))
{
throw new IllegalArgumentException ("Method: " +
tok +
" cannot be called on class: " +
c.getName () +
" since return type is void");
}
a.accessor = m;
retdata.add (a);
continue;
} else {
// The field is not either a standard JavaBeans method so now just
// look for the method with the specified name.
m = Accessor.getNoParmJavaMethod (tok,
c);
if (m == null)
{
throw new IllegalArgumentException ("Cannot find method with name: " +
tok +
" in class: " +
c.getName ());
}
c = m.getReturnType ();
if (!a.isIndexCorrectForType (c))
{
throw new IllegalArgumentException ("Method: " +
tok +
" cannot be called with index: " +
index +
" since method return type is: " +
c.getName ());
}
if (Void.class.isAssignableFrom (c))
{
throw new IllegalArgumentException ("Method: " +
tok +
" cannot be called on class: " +
c.getName () +
" since return type is void");
}
a.accessor = m;
retdata.add (a);
continue;
}
}
}
return retdata;
}
private static Method getNoParmJavaMethod (String method,
Class c)
{
Method[] methods = c.getMethods ();
for (int i = 0; i < methods.length; i++)
{
if ((methods[i].getName ().equals (method))
&&
(methods[i].getParameterTypes ().length == 0)
)
{
// This is the one...
return methods[i];
}
}
return null;
}
}