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

com.gentlyweb.utils.Getter 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.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 Getter { private List chain = new ArrayList (); private Class clazz = null; private int cs = 0; private String acc = null; /** * Get the getter associated with the named reference. Return * null if there isn't one, or if we can't access it. * * @param ref The reference for the getter. * @param clazz The Class to get the field from. */ public Getter (String ref, Class clazz) throws IllegalArgumentException { if (clazz == null) { throw new IllegalArgumentException ("Class must be specified"); } this.acc = ref; this.clazz = clazz; StringTokenizer t = new StringTokenizer (ref, "."); Class c = clazz; while (t.hasMoreTokens ()) { String tok = t.nextToken (); String index = ""; // 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 (); this.chain.add (f); } else { Method m = this.getNoParmJavaGetMethod (tok, c); if (m == null) { throw new IllegalArgumentException ("Cannot find method with name: " + tok + " in class: " + c.getName ()); } // Need to set the method as being accessible here to workaround // an annoying Java reflection bug that seems to have been around // since the year dot. See bug: 4071957. m.setAccessible (true); c = m.getReturnType (); if (Void.class.isAssignableFrom (c)) { throw new IllegalArgumentException ("Method: " + m.getName () + " cannot be called on class: " + c.getName () + " since return type is void"); } this.chain.add (m); } } this.cs = this.chain.size (); } public Class getBaseClass () { return this.clazz; } /** * Get the class of the type of object we would return from the {@link #getValue(Object)} * method. * * @return The class. */ public Class getType () { Object o = this.chain.get (this.chain.size () - 1); // See what type the accessor is... if (o instanceof Method) { Method m = (Method) o; return m.getReturnType (); } if (o instanceof Field) { // It's a field...so... Field f = (Field) o; return f.getType (); } return null; } public Object getValue (Object obj) throws IllegalAccessException, InvocationTargetException { // If the object is null then return null. if (obj == null) { return null; } // For our accessor chain, use the Field and Methods // to get the actual value. Object retdata = obj; for (int i = 0; i < this.cs; i++) { Object o = this.chain.get (i); // See what type the accessor is... if (o instanceof Method) { Method m = (Method) o; Object[] parms = {}; // Invoke the method... try { retdata = m.invoke (retdata, parms); } catch (Exception e) { this.throwException (obj, e); } if (retdata == null) { return null; } } if (o instanceof Field) { // It's a field...so... Field f = (Field) o; // Now get the value... try { retdata = f.get (retdata); } catch (Exception e) { this.throwException (obj, e); } } } return retdata; } private void throwException (Object o, Exception e) { throw new RuntimeException ("Unable to get value from instance of: " + o.getClass ().getName () + ", using accessor: " + this.acc + " expected type to be: " + this.clazz.getName (), e); } public static Method getNoParmJavaGetMethod (String method, Class c) { StringBuffer b = new StringBuffer (method); Method m = null; // First look for a "get" method. try { b.setCharAt (0, Character.toUpperCase (method.charAt (0))); b.insert (0, "get"); String getMN = b.toString (); m = c.getMethod (getMN, null); if (m != null) { return m; } } catch (Exception e) { // Painful to have to do it this way... } try { b = new StringBuffer (method); b.setCharAt (0, Character.toUpperCase (method.charAt (0))); b.insert (0, "is"); String isMN = b.toString (); m = c.getMethod (isMN, null); if (m != null) { return m; } } catch (Exception e) { // Sigh... } try { return c.getMethod (method, null); } catch (Exception e) { // Ignore... } return null; } public String getAccessor () { return this.acc; } public String toString () { return "Accessor: " + this.acc + " from class: " + this.clazz.getName (); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy