001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.util.functions;
011:
012: import org.mmbase.cache.Cache;
013:
014: import java.lang.reflect.*;
015: import java.util.*;
016: import org.mmbase.util.logging.*;
017:
018: /**
019: * One or more functions based on a Java-bean. Every setter method of the bean corresponds with one
020: * parameter. The default value of the parameter can be defined with the getter method (which will
021: * be called immediately after instantiation of such a Class).
022: *
023: * All other methods (with no arguments) of the class correspond to the functions. So, you can
024: * implement more bean-functions in the same class, as long as they have the same parameters.
025: *
026: * A BeanFunction can be aquired via {@link FunctionFactory#getFunction(Class, String)} (which
027: * delegates to a static method in this class).
028: *
029: * @author Michiel Meeuwissen
030: * @version $Id: BeanFunction.java,v 1.25 2008/02/03 17:33:57 nklasens Exp $
031: * @see org.mmbase.util.functions.MethodFunction
032: * @see org.mmbase.util.functions.FunctionFactory
033: * @since MMBase-1.8
034: */
035: public class BeanFunction extends AbstractFunction<Object> {
036:
037: /**
038: * @since MMBase-1.8.5
039: */
040: public static abstract class Producer {
041: public abstract Object getInstance();
042:
043: public String toString() {
044: return getClass().getName();
045: }
046: }
047:
048: private static final Logger log = Logging
049: .getLoggerInstance(BeanFunction.class);
050:
051: /**
052: * Utility function, searches an inner class of a given class. This inner class can perhaps be used as a
053: * bean. Used in JSP/taglib.
054: * @param claz The class to be considered
055: * @param name The name of the inner class
056: * @throws IllegalArgumentException if claz has no inner class with that name
057: */
058: public static Class getClass(Class claz, String name) {
059: Class[] classes = claz.getDeclaredClasses();
060: for (Class c : classes) {
061: if (c.getName().endsWith("$" + name)) {
062: return c;
063: }
064: }
065: throw new IllegalArgumentException(
066: "There is no inner class with name '" + name + "' in "
067: + claz);
068: }
069:
070: /**
071: * A cache for bean classes. Used to avoid some reflection.
072: */
073: private static Cache<String, BeanFunction> beanFunctionCache = new Cache<String, BeanFunction>(
074: 50) {
075: public String getName() {
076: return "BeanFunctionCache";
077: }
078:
079: public String getDescription() {
080: return "ClassName.FunctionName -> BeanFunction object";
081: }
082: };
083:
084: /**
085: * Gives back a Function object based on the 'bean' concept.
086: * @param claz The class which must be considered a 'bean' function
087: * @param name The name of the function (the name of a Method in the given class)
088: * @param producer An object that can produce in instance of the class
089: * <code>claz</code>. Defaults to a producer that simply calls <code>claz.newInstance()</code>
090: * @since MMBase-1.8.5
091: */
092: public static BeanFunction getFunction(final Class claz,
093: String name, Producer producer)
094: throws IllegalAccessException, InstantiationException,
095: InvocationTargetException {
096: String key = claz.getName() + '.' + name + '.' + producer;
097: BeanFunction result = beanFunctionCache.get(key);
098: if (result == null) {
099: result = new BeanFunction(claz, name, producer);
100: beanFunctionCache.put(key, result);
101: }
102: return result;
103: }
104:
105: /**
106: * Called from {@link FunctionFactory}
107: */
108: public static BeanFunction getFunction(final Class claz, String name)
109: throws IllegalAccessException, InstantiationException,
110: InvocationTargetException {
111: return getFunction(claz, name, new Producer() {
112: public Object getInstance() {
113: try {
114: return claz.newInstance();
115: } catch (Exception e) {
116: throw new RuntimeException(e);
117: }
118: }
119:
120: public String toString() {
121: return "";
122: }
123: });
124: }
125:
126: /**
127: * Utitily function to create an instance of a certain class. Two constructors are tried, a one
128: * argument one, and if that fails, simply newInstance is used.
129: * @since MMBase-1.8.5
130: */
131: public static Object getInstance(final Class claz,
132: Object constructorArgument) throws IllegalAccessException,
133: InstantiationException, InvocationTargetException {
134: Class c = constructorArgument.getClass();
135: while (c != null) {
136: try {
137: Constructor con = claz
138: .getConstructor(new Class[] { c });
139: return con
140: .newInstance(new Object[] { constructorArgument });
141: } catch (NoSuchMethodException e) {
142: c = c.getSuperclass();
143: }
144: }
145: Class[] interfaces = constructorArgument.getClass()
146: .getInterfaces();
147: for (Class element : interfaces) {
148: try {
149: Constructor con = claz
150: .getConstructor(new Class[] { element });
151: return con
152: .newInstance(new Object[] { constructorArgument });
153: } catch (NoSuchMethodException e) {
154: }
155:
156: }
157: return claz.newInstance();
158: }
159:
160: /* ================================================================================
161: Instance methods
162: ================================================================================
163: */
164:
165: /**
166: * The method corresponding to the function called in getFunctionValue.
167: */
168: private final Method method;
169:
170: /**
171: * A list of all found setter methods. This list 1-1 corresponds with getParameterDefinition. Every Parameter belongs to a setter method.
172: */
173: private List<Method> setMethods = new ArrayList<Method>();
174:
175: private final Producer producer;
176:
177: /**
178: * The constructor! Performs reflection to fill 'method' and 'setMethods' members.
179: */
180: private BeanFunction(Class claz, String name, Producer producer)
181: throws IllegalAccessException, InstantiationException,
182: InvocationTargetException {
183: super (name, null, null);
184: this .producer = producer;
185:
186: Method candMethod = null;
187: // Finding the methods to be used.
188: for (Method m : claz.getMethods()) {
189: String methodName = m.getName();
190: if (methodName.equals(name)
191: && m.getParameterTypes().length == 0) {
192: candMethod = m;
193: break;
194: }
195: }
196:
197: if (candMethod == null) {
198: throw new IllegalArgumentException("The class " + claz
199: + " does not have method " + name
200: + " (with no argument)");
201: }
202:
203: method = candMethod;
204:
205: // Now finding the parameters.
206:
207: // need a sample instance to get the default values from.
208: Object sampleInstance = producer.getInstance();
209:
210: List<Parameter> parameters = new ArrayList<Parameter>();
211: Method nodeParameter = null;
212: for (Method m : claz.getMethods()) {
213: String methodName = m.getName();
214: Class[] parameterTypes = m.getParameterTypes();
215: if (parameterTypes.length == 1
216: && methodName.startsWith("set")) {
217: String parameterName = methodName.substring(3);
218: boolean required = false;
219: Required requiredAnnotation = m
220: .getAnnotation(Required.class);
221: required = requiredAnnotation != null;
222:
223: // find a corresponding getter method, which can be used for a default value;
224: Object defaultValue;
225: try {
226: Method getter = claz.getMethod("get"
227: + parameterName);
228: defaultValue = getter.invoke(sampleInstance);
229: } catch (NoSuchMethodException nsme) {
230: defaultValue = null;
231: }
232: if (Character.isUpperCase(parameterName.charAt(0))) {
233: if (parameterName.length() > 1) {
234: if (!Character.isUpperCase(parameterName
235: .charAt(1))) {
236: parameterName = ""
237: + Character
238: .toLowerCase(parameterName
239: .charAt(0))
240: + parameterName.substring(1);
241: }
242: } else {
243: parameterName = parameterName.toLowerCase();
244: }
245: }
246: if (parameterName.equals("node")
247: && org.mmbase.bridge.Node.class
248: .isAssignableFrom(parameterTypes[0])) {
249: nodeParameter = method;
250: } else {
251: if (defaultValue != null) {
252: if (required) {
253: log
254: .warn("Required annotation ignored, because a default value is present");
255: }
256: parameters.add(new Parameter(parameterName,
257: parameterTypes[0], defaultValue));
258: } else {
259: parameters.add(new Parameter(parameterName,
260: parameterTypes[0], required));
261: }
262: setMethods.add(m);
263: }
264:
265: }
266: }
267: if (nodeParameter != null) {
268: parameters.add(Parameter.NODE);
269: setMethods.add(nodeParameter);
270: }
271: setParameterDefinition(parameters.toArray(Parameter
272: .emptyArray()));
273: ReturnType returnType = new ReturnType(method.getReturnType(),
274: "");
275: setReturnType(returnType);
276:
277: }
278:
279: /**
280: * @since MMBase-1.8.5
281: */
282: public BeanFunction(final Object bean, String name)
283: throws IllegalAccessException, InstantiationException,
284: InvocationTargetException {
285: this (bean.getClass(), name, new Producer() {
286: public Object getInstance() {
287: return bean;
288: }
289: });
290: }
291:
292: /**
293: * @since MMBase-1.8.5
294: */
295: public Producer getProducer() {
296: return producer;
297: }
298:
299: /**
300: * {@inheritDoc}
301: * Instantiates the bean, calls all setters using the parameters, and executes the method associated with this function.
302: */
303: public Object getFunctionValue(Parameters parameters) {
304: try {
305: Object b = getProducer().getInstance();
306: int count = 0;
307: Iterator<?> i = parameters.iterator();
308: Iterator<Method> j = setMethods.iterator();
309: while (i.hasNext() && j.hasNext()) {
310: Object value = i.next();
311: Method setter = j.next();
312: if (value == null) {
313: if (setter.getParameterTypes()[0].isPrimitive()) {
314: log
315: .debug("Tried to sed null in in primitive setter method");
316: //primitive types cannot be null, never mind.
317: continue;
318: }
319: }
320: Object defaultValue = parameters.getDefinition()[count]
321: .getDefaultValue();
322: if ((defaultValue == null && value != null)
323: || (defaultValue != null && (!defaultValue
324: .equals(value)))) {
325: setter.invoke(b, value);
326: }
327: count++;
328:
329: }
330: Object ret = method.invoke(b);
331: return ret;
332: } catch (Exception e) {
333: throw new RuntimeException(e);
334: }
335: }
336:
337: public static void main(String[] argv) throws Exception {
338: Function fun = getFunction(Class.forName(argv[0]), argv[1]);
339: System.out.println("" + fun);
340: System.out.println("" + fun.createParameters());
341: }
342: }
|