/** * Copyright (C) 2002-2012 The FreeCol Team * * This file is part of FreeCol. * * FreeCol is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * FreeCol 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with FreeCol. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.freecol.common.util; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; /** * A class to allow access to the methods "fooType getFoo()" and * "void setFoo(fooType)" conventionally seen in objects. * Useful when Foo arrives as a run-time String, such as is the * case in serialization to/from XML representations. */ public class Introspector { /** * The class whose field we are to operate on. */ private Class<?> theClass; /** * The field whose get/set methods we wish to invoke. */ private String field; /** * Build a new Introspector for the specified class and field name. * * @param theClass The <code>Class</code> of interest. * @param field The field name within the class of interest. * @throws IllegalArgumentException */ public Introspector(Class<?> theClass, String field) throws IllegalArgumentException { if (field == null || field.length() == 0) { throw new IllegalArgumentException("Field may not be empty"); } this.theClass = theClass; this.field = field; } /** * Get a get-method for this Introspector. * * @return A <code>Method</code> representing getField(). * @throws IllegalArgumentException */ private Method getGetMethod() throws IllegalArgumentException { String methodName = "get" + field.substring(0, 1).toUpperCase() + field.substring(1); try { return theClass.getMethod(methodName); } catch (Exception e) { throw new IllegalArgumentException(theClass.getName() + "." + methodName + ": " + e.toString()); } } /** * Get a set-method for this Introspector. * * @param argType A <code>Class</code> that is the argument to * the set-method * @return A <code>Method</code> representing setField(). * @throws IllegalArgumentException */ private Method getSetMethod(Class<?> argType) throws IllegalArgumentException { String methodName = "set" + field.substring(0, 1).toUpperCase() + field.substring(1); try { return theClass.getMethod(methodName, argType); } catch (Exception e) { throw new IllegalArgumentException(theClass.getName() + "." + methodName + ": " + e.toString()); } } /** * Get the return type from a <code>Method</code>. * * @param method The <code>Method</code> to examine. * @return The method return type, or null on error. * @throws IllegalArgumentException */ private Class<?> getMethodReturnType(Method method) throws IllegalArgumentException { Class<?> ret; try { ret = method.getReturnType(); } catch (Exception e) { throw new IllegalArgumentException(theClass.getName() + "." + method.getName() + " return type: " + e.toString()); } return ret; } /** * Get a function that converts to String from a given class. * We use Enum.name() for enums, and String.valueOf(argType) for the rest. * * @param argType A <code>Class</code> to find a converter for. * @return A conversion function, or null on error. * @throws IllegalArgumentException */ private Method getToStringConverter(Class<?> argType) throws IllegalArgumentException { Method method; if (argType.isEnum()) { try { method = argType.getMethod("name"); } catch (Exception e) { throw new IllegalArgumentException(argType.getName() + ".getMethod(name()): " + e.toString()); } } else { try { method = String.class.getMethod("valueOf", argType); } catch (Exception e) { throw new IllegalArgumentException("String.getMethod(valueOf(" + argType.getName() + ")): " + e.toString()); } } return method; } /** * Get a function that converts from String to a given class. * We use Enum.valueOf(Class, String) for enums, and * argType.valueOf(String) for the rest, having first dodged * the primitive types. * * @param argType A <code>Class</code> to find a converter for. * @return A conversion function, or null on error. * @throws IllegalArgumentExcpetion */ private Method getFromStringConverter(Class<?> argType) throws IllegalArgumentException { Method method; if (argType.isEnum()) { try { method = Enum.class.getMethod("valueOf", Class.class, String.class); } catch (Exception e) { throw new IllegalArgumentException("Enum.getMethod(valueOf(Class, String)): " + e.toString()); } } else { if (argType.isPrimitive()) { if (argType == Integer.TYPE) { argType = Integer.class; } else if (argType == Boolean.TYPE) { argType = Boolean.class; } else if (argType == Float.TYPE) { argType = Float.class; } else if (argType == Double.TYPE) { argType = Double.class; } else if (argType == Character.TYPE) { argType = Character.class; } else { throw new IllegalArgumentException("Need compatible class for primitive " + argType.getName()); } } try { method = argType.getMethod("valueOf", String.class); } catch (Exception e) { throw new IllegalArgumentException(argType.getName() + ".getMethod(valueOf(String)): " + e.toString()); } } return method; } /** * Invoke the get-method for this Introspector. * * @param obj An <code>Object</code> (really of type theClass) * whose get-method is to be invoked. * @return A <code>String</code> containing the result of invoking * the get-method. * @throws IllegalArgumentException */ public String getter(Object obj) throws IllegalArgumentException { Method getMethod = getGetMethod(); Class<?> fieldType = getMethodReturnType(getMethod); if (fieldType == String.class) { try { return (String) getMethod.invoke(obj); } catch (Exception e) { throw new IllegalArgumentException(getMethod.getName() + "(obj)): " + e.toString()); } } else { Object result = null; try { result = getMethod.invoke(obj); } catch (Exception e) { throw new IllegalArgumentException(getMethod.getName() + "(obj): " + e.toString()); } Method convertMethod = getToStringConverter(fieldType); if (Modifier.isStatic(convertMethod.getModifiers())) { try { return (String) convertMethod.invoke(null, result); } catch (Exception e) { throw new IllegalArgumentException(convertMethod.getName() + "(null, result): " + e.toString()); } } else { try { return (String) convertMethod.invoke(result); } catch (Exception e) { throw new IllegalArgumentException(convertMethod.getName() + "(result): " + e.toString()); } } } } /** * Invoke the set-method provided by this Introspector. * * @param obj An <code>Object</code> (really of type theClass) * whose set-method is to be invoked. * @param value A <code>String</code> containing the value to be set. * @throws IllegalArgumentException */ public void setter(Object obj, String value) throws IllegalArgumentException { Method getMethod = getGetMethod(); Class<?> fieldType = getMethodReturnType(getMethod); Method setMethod = getSetMethod(fieldType); if (fieldType == String.class) { try { setMethod.invoke(obj, value); } catch (Exception e) { throw new IllegalArgumentException(setMethod.getName() + "(obj, " + value + ")): " + e.toString()); } } else { Method convertMethod = getFromStringConverter(fieldType); Object result = null; if (fieldType.isEnum()) { try { result = convertMethod.invoke(null, fieldType, value); } catch (Exception e) { throw new IllegalArgumentException(convertMethod.getName() + "(null, " + fieldType.getName() + ", " + value + "):" + e.toString()); } } else { try { result = convertMethod.invoke(null, value); } catch (Exception e) { throw new IllegalArgumentException(convertMethod.getName() + "(null, " + value + "):" + e.toString()); } } try { setMethod.invoke(obj, result); } catch (Exception e) { throw new IllegalArgumentException(setMethod.getName() + "(result): " + e.toString()); } } } /** * Constructs a new instance of an object of a class specified by name, * with supplied parameters. * * @param tag The name of the class to instantiate. * @param types The argument types of the constructor to call. * @param params The parameters to call the constructor with. * @return The new object instance. * @exception IllegalArgumentException wraps all exceptional conditions. */ public static Object instantiate(String tag, Class[] types, Object[] params) throws IllegalArgumentException { Class<?> messageClass; try { messageClass = Class.forName(tag); } catch (Exception e) { throw new IllegalArgumentException("Unable to find class " + tag + ": " + e.toString()); } Constructor<?> constructor; try { constructor = messageClass.getDeclaredConstructor(types); } catch (Exception e) { String p = "Unable to find constructor " + tag + "("; for (int i = 0; i < types.length; i++) { p += " " + types[i].toString(); } p += " ): "; throw new IllegalArgumentException(p + e.toString()); } Object instance; try { instance = constructor.newInstance(params); } catch (Exception e) { throw new IllegalArgumentException("Failed to construct " + tag + ": " + e.toString()); } return instance; } }