1 /*
2 * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package java.beans;
26
27 import java.lang.reflect.AccessibleObject;
28 import java.lang.reflect.Array;
29 import java.lang.reflect.Constructor;
30 import java.lang.reflect.InvocationTargetException;
31 import java.lang.reflect.Method;
32 import java.security.AccessControlContext;
33 import java.security.AccessController;
34 import java.security.PrivilegedActionException;
35 import java.security.PrivilegedExceptionAction;
36
37 import com.sun.beans.finder.ClassFinder;
38 import com.sun.beans.finder.ConstructorFinder;
39 import com.sun.beans.finder.MethodFinder;
40 import sun.reflect.misc.MethodUtil;
41
42 /**
43 * A <code>Statement</code> object represents a primitive statement
44 * in which a single method is applied to a target and
45 * a set of arguments - as in <code>"a.setFoo(b)"</code>.
46 * Note that where this example uses names
47 * to denote the target and its argument, a statement
48 * object does not require a name space and is constructed with
49 * the values themselves.
50 * The statement object associates the named method
51 * with its environment as a simple set of values:
52 * the target and an array of argument values.
53 *
54 * @since 1.4
55 *
56 * @author Philip Milne
57 */
58 public class Statement {
59
60 private static Object[] emptyArray = new Object[]{};
61
62 static ExceptionListener defaultExceptionListener = new ExceptionListener() {
63 public void exceptionThrown(Exception e) {
64 System.err.println(e);
65 // e.printStackTrace();
66 System.err.println("Continuing ...");
67 }
68 };
69
70 private final AccessControlContext acc = AccessController.getContext();
71 private final Object target;
72 private final String methodName;
73 private final Object[] arguments;
74 ClassLoader loader;
75
76 /**
77 * Creates a new {@link Statement} object
78 * for the specified target object to invoke the method
79 * specified by the name and by the array of arguments.
80 * <p>
81 * The {@code target} and the {@code methodName} values should not be {@code null}.
82 * Otherwise an attempt to execute this {@code Expression}
83 * will result in a {@code NullPointerException}.
84 * If the {@code arguments} value is {@code null},
85 * an empty array is used as the value of the {@code arguments} property.
86 *
87 * @param target the target object of this statement
88 * @param methodName the name of the method to invoke on the specified target
89 * @param arguments the array of arguments to invoke the specified method
90 */
91 @ConstructorProperties({"target", "methodName", "arguments"})
92 public Statement(Object target, String methodName, Object[] arguments) {
93 this.target = target;
94 this.methodName = methodName;
95 this.arguments = (arguments == null) ? emptyArray : arguments;
96 }
97
98 /**
99 * Returns the target object of this statement.
100 * If this method returns {@code null},
101 * the {@link #execute} method
102 * throws a {@code NullPointerException}.
103 *
104 * @return the target object of this statement
105 */
106 public Object getTarget() {
107 return target;
108 }
109
110 /**
111 * Returns the name of the method to invoke.
112 * If this method returns {@code null},
113 * the {@link #execute} method
114 * throws a {@code NullPointerException}.
115 *
116 * @return the name of the method
117 */
118 public String getMethodName() {
119 return methodName;
120 }
121
122 /**
123 * Returns the arguments for the method to invoke.
124 * The number of arguments and their types
125 * must match the method being called.
126 * {@code null} can be used as a synonym of an empty array.
127 *
128 * @return the array of arguments
129 */
130 public Object[] getArguments() {
131 return arguments;
132 }
133
134 /**
135 * The {@code execute} method finds a method whose name is the same
136 * as the {@code methodName} property, and invokes the method on
137 * the target.
138 *
139 * When the target's class defines many methods with the given name
140 * the implementation should choose the most specific method using
141 * the algorithm specified in the Java Language Specification
142 * (15.11). The dynamic class of the target and arguments are used
143 * in place of the compile-time type information and, like the
144 * {@link java.lang.reflect.Method} class itself, conversion between
145 * primitive values and their associated wrapper classes is handled
146 * internally.
147 * <p>
148 * The following method types are handled as special cases:
149 * <ul>
150 * <li>
151 * Static methods may be called by using a class object as the target.
152 * <li>
153 * The reserved method name "new" may be used to call a class's constructor
154 * as if all classes defined static "new" methods. Constructor invocations
155 * are typically considered {@code Expression}s rather than {@code Statement}s
156 * as they return a value.
157 * <li>
158 * The method names "get" and "set" defined in the {@link java.util.List}
159 * interface may also be applied to array instances, mapping to
160 * the static methods of the same name in the {@code Array} class.
161 * </ul>
162 *
163 * @throws NullPointerException if the value of the {@code target} or
164 * {@code methodName} property is {@code null}
165 * @throws NoSuchMethodException if a matching method is not found
166 * @throws SecurityException if a security manager exists and
167 * it denies the method invocation
168 * @throws Exception that is thrown by the invoked method
169 *
170 * @see java.lang.reflect.Method
171 */
172 public void execute() throws Exception {
173 invoke();
174 }
175
176 Object invoke() throws Exception {
177 AccessControlContext acc = this.acc;
178 if ((acc == null) && (System.getSecurityManager() != null)) {
179 throw new SecurityException("AccessControlContext is not set");
180 }
181 try {
182 return AccessController.doPrivileged(
183 new PrivilegedExceptionAction<Object>() {
184 public Object run() throws Exception {
185 return invokeInternal();
186 }
187 },
188 acc
189 );
190 }
191 catch (PrivilegedActionException exception) {
192 throw exception.getException();
193 }
194 }
195
196 private Object invokeInternal() throws Exception {
197 Object target = getTarget();
198 String methodName = getMethodName();
199
200 if (target == null || methodName == null) {
201 throw new NullPointerException((target == null ? "target" :
202 "methodName") + " should not be null");
203 }
204
205 Object[] arguments = getArguments();
206 if (arguments == null) {
207 arguments = emptyArray;
208 }
209 // Class.forName() won't load classes outside
210 // of core from a class inside core. Special
211 // case this method.
212 if (target == Class.class && methodName.equals("forName")) {
213 return ClassFinder.resolveClass((String)arguments[0], this.loader);
214 }
215 Class[] argClasses = new Class[arguments.length];
216 for(int i = 0; i < arguments.length; i++) {
217 argClasses[i] = (arguments[i] == null) ? null : arguments[i].getClass();
218 }
219
220 AccessibleObject m = null;
221 if (target instanceof Class) {
222 /*
223 For class methods, simluate the effect of a meta class
224 by taking the union of the static methods of the
225 actual class, with the instance methods of "Class.class"
226 and the overloaded "newInstance" methods defined by the
227 constructors.
228 This way "System.class", for example, will perform both
229 the static method getProperties() and the instance method
230 getSuperclass() defined in "Class.class".
231 */
232 if (methodName.equals("new")) {
233 methodName = "newInstance";
234 }
235 // Provide a short form for array instantiation by faking an nary-constructor.
236 if (methodName.equals("newInstance") && ((Class)target).isArray()) {
237 Object result = Array.newInstance(((Class)target).getComponentType(), arguments.length);
238 for(int i = 0; i < arguments.length; i++) {
239 Array.set(result, i, arguments[i]);
240 }
241 return result;
242 }
243 if (methodName.equals("newInstance") && arguments.length != 0) {
244 // The Character class, as of 1.4, does not have a constructor
245 // which takes a String. All of the other "wrapper" classes
246 // for Java's primitive types have a String constructor so we
247 // fake such a constructor here so that this special case can be
248 // ignored elsewhere.
249 if (target == Character.class && arguments.length == 1 &&
250 argClasses[0] == String.class) {
251 return new Character(((String)arguments[0]).charAt(0));
252 }
253 try {
254 m = ConstructorFinder.findConstructor((Class)target, argClasses);
255 }
256 catch (NoSuchMethodException exception) {
257 m = null;
258 }
259 }
260 if (m == null && target != Class.class) {
261 m = getMethod((Class)target, methodName, argClasses);
262 }
263 if (m == null) {
264 m = getMethod(Class.class, methodName, argClasses);
265 }
266 }
267 else {
268 /*
269 This special casing of arrays is not necessary, but makes files
270 involving arrays much shorter and simplifies the archiving infrastrcure.
271 The Array.set() method introduces an unusual idea - that of a static method
272 changing the state of an instance. Normally statements with side
273 effects on objects are instance methods of the objects themselves
274 and we reinstate this rule (perhaps temporarily) by special-casing arrays.
275 */
276 if (target.getClass().isArray() &&
277 (methodName.equals("set") || methodName.equals("get"))) {
278 int index = ((Integer)arguments[0]).intValue();
279 if (methodName.equals("get")) {
280 return Array.get(target, index);
281 }
282 else {
283 Array.set(target, index, arguments[1]);
284 return null;
285 }
286 }
287 m = getMethod(target.getClass(), methodName, argClasses);
288 }
289 if (m != null) {
290 try {
291 if (m instanceof Method) {
292 return MethodUtil.invoke((Method)m, target, arguments);
293 }
294 else {
295 return ((Constructor)m).newInstance(arguments);
296 }
297 }
298 catch (IllegalAccessException iae) {
299 throw new Exception("Statement cannot invoke: " +
300 methodName + " on " + target.getClass(),
301 iae);
302 }
303 catch (InvocationTargetException ite) {
304 Throwable te = ite.getTargetException();
305 if (te instanceof Exception) {
306 throw (Exception)te;
307 }
308 else {
309 throw ite;
310 }
311 }
312 }
313 throw new NoSuchMethodException(toString());
314 }
315
316 String instanceName(Object instance) {
317 if (instance == null) {
318 return "null";
319 } else if (instance.getClass() == String.class) {
320 return "\""+(String)instance + "\"";
321 } else {
322 // Note: there is a minor problem with using the non-caching
323 // NameGenerator method. The return value will not have
324 // specific information about the inner class name. For example,
325 // In 1.4.2 an inner class would be represented as JList$1 now
326 // would be named Class.
327
328 return NameGenerator.unqualifiedClassName(instance.getClass());
329 }
330 }
331
332 /**
333 * Prints the value of this statement using a Java-style syntax.
334 */
335 public String toString() {
336 // Respect a subclass's implementation here.
337 Object target = getTarget();
338 String methodName = getMethodName();
339 Object[] arguments = getArguments();
340 if (arguments == null) {
341 arguments = emptyArray;
342 }
343 StringBuffer result = new StringBuffer(instanceName(target) + "." + methodName + "(");
344 int n = arguments.length;
345 for(int i = 0; i < n; i++) {
346 result.append(instanceName(arguments[i]));
347 if (i != n -1) {
348 result.append(", ");
349 }
350 }
351 result.append(");");
352 return result.toString();
353 }
354
355 static Method getMethod(Class<?> type, String name, Class<?>... args) {
356 try {
357 return MethodFinder.findMethod(type, name, args);
358 }
359 catch (NoSuchMethodException exception) {
360 return null;
361 }
362 }
363 }