View Javadoc

1   /*
2    * This file is part of Pease Plate Template Engine.
3    * 
4    * Pease Plate Template Engine is free software: you can redistribute
5    * it and/or modify it under the terms of the GNU Lesser General 
6    * Public License as published by the Free Software Foundation, 
7    * either version 3 of the License, or any later version.
8    * 
9    * Pease Plate Template Engine is distributed in the hope that it 
10   * will be useful, but WITHOUT ANY WARRANTY; without even the implied
11   * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12   * See the GNU Lesser General Public License for more details.
13   * 
14   * You should have received a copy of the GNU Lesser General Public 
15   * License along with Pease Plate Template Engine. If not, see 
16   * <http://www.gnu.org/licenses/>.
17   * 
18   * Copyright (c) 2008 Manfred HANTSCHEL
19   */
20  package org.peaseplate.internal.lang.command;
21  
22  import java.lang.reflect.Field;
23  import java.lang.reflect.Method;
24  import java.util.Locale;
25  import java.util.Map;
26  
27  import org.peaseplate.TemplateRuntimeException;
28  import org.peaseplate.internal.BuildContext;
29  import org.peaseplate.internal.util.ReflectionException;
30  import org.peaseplate.internal.util.ReflectionUtils;
31  import org.peaseplate.locator.TemplateLocator;
32  
33  /**
34   * This command does the whole magic. It invokes the method
35   * that matches the identifier and the parameterCommands. Since the 
36   * language is not strictly type, this may be complicated
37   * some times.
38   * 
39   * Any method that has a name similar to the identifier,
40   * or the identifier prefixed with "get", "is" or "set"
41   * (the case is ignored) will be taken into consideration.
42   * 
43   * Next it checks the number (not the type) of the parameterCommands.
44   * 
45   * Hopefully this results in just one method. If there are still 
46   * multiple methods with name and the same parameter count,
47   * it searches for the method that most exactly fits the types
48   * of the parameterCommands (this may vary each time the command is
49   * called, because the language is not strictly typed).
50   * 
51   * To get this whole stuff acceptable fast, it stores all possible
52   * methods in a list the first time the command is invoked.
53   * 
54   * Due to the fact, that the language is not strictly typed, it
55   * even is possible that the class may change on which
56   * this command is invoked. Thus the list with the commands is
57   * stored in a hash map with the class as key.
58   */
59  public class InvocationCommand extends AbstractObjectCallCommand {
60  
61  	private final String identifier;
62  	
63  	public InvocationCommand(TemplateLocator locator, int line, int column, ICommand command, String identifier, ICommand[] parameterCommands) {
64  		super(locator, line, column, command, parameterCommands);
65  		
66  		this.identifier = identifier;
67  	}
68  	
69  	/**
70  	 * @see org.peaseplate.internal.lang.command.ICommand#call(BuildContext)
71  	 */
72  	public Object call(BuildContext context) throws TemplateRuntimeException {
73  		Object workingObject = callCommand(context);
74  
75  		if (workingObject == null)
76  			return null;
77  		
78  		return callObject(context, workingObject, identifier);
79  	}
80  
81  	/**
82  	 * Usually an invocation command calls a method, but if there is no method
83  	 * matching the identifier and the object is a map, and the identifier is
84  	 * a string, and there are no parameters, then the map is accessed
85  	 * 
86  	 * @see org.peaseplate.internal.lang.command.AbstractObjectCallCommand#callObject(org.peaseplate.internal.BuildContext, java.lang.Object, java.lang.Object)
87  	 */
88  	@Override
89      protected Object callObject(BuildContext context, Object onObject, Object identifier) throws TemplateRuntimeException {
90  		if (onObject == null)
91  			return null;
92  
93  		if (!hasParameters()) {
94  			Field field = getField(onObject.getClass(), String.valueOf(identifier).toLowerCase(Locale.getDefault()));
95  			
96  			if (field != null)
97  				return callField(onObject, field);
98  		}
99  
100 		Method method = getMethod(
101 			onObject.getClass(), String.valueOf(identifier).toLowerCase(Locale.getDefault()), getNumberOfParameters()
102 		);
103 
104 		if (method == null) {
105 			if ((onObject instanceof Map) && (identifier instanceof String) && (!hasParameters()))
106 				return callMap(context, (Map<?, ?>)onObject, identifier);
107 			
108 			throw new TemplateRuntimeException(
109 				getLocator(), getLine(), getColumn(), 
110 				"No method matches " + ReflectionUtils.formatMethodName(onObject.getClass(), identifier, getNumberOfParameters())
111 			);
112 		}
113 		
114 		return callMethod(context, onObject, method);
115 	}
116 
117 	/**
118 	 * @see org.peaseplate.internal.lang.command.AbstractNativeCallCommand#getField(java.lang.Class, java.lang.String)
119 	 */
120 	@Override
121     protected Field getField(Class<?> clazz, String identifier) throws TemplateRuntimeException {
122 	    return findField(clazz, identifier);
123     }
124 
125 	/**
126 	 * @see org.peaseplate.internal.lang.command.AbstractObjectCallCommand#getMethod(java.lang.Class, java.lang.String, int)
127 	 */
128 	@Override
129     protected Method getMethod(Class<?> clazz, String identifier, int numberOfParameters) throws TemplateRuntimeException {
130 		// TODO implement caching here!
131 		try {
132 			return ReflectionUtils.findMethod(clazz, identifier, numberOfParameters);
133 		}
134 		catch (ReflectionException e) {
135 			throw new TemplateRuntimeException(
136 				getLocator(), getLine(), getColumn(),
137 				"Could not find method \"" + identifier + "\" in " + clazz, e
138 			);
139 		}
140     }
141 
142 	/**
143 	 * @see java.lang.Object#toString()
144 	 */
145 	@Override
146 	public String toString() {
147 		StringBuilder result = new StringBuilder();
148 		
149 		if (getCommand() != null)
150 			result.append(getCommand()).append(".");
151 			
152 		result.append(identifier);
153 		result.append(super.toString());
154 		
155 		return result.toString();
156 	}
157 	
158 }