1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
\r
3 * The contents of this file are subject to the Netscape Public
\r
4 * License Version 1.1 (the "License"); you may not use this file
\r
5 * except in compliance with the License. You may obtain a copy of
\r
6 * the License at http://www.mozilla.org/NPL/
\r
8 * Software distributed under the License is distributed on an "AS
\r
9 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
\r
10 * implied. See the License for the specific language governing
\r
11 * rights and limitations under the License.
\r
13 * The Original Code is Rhino code, released
\r
16 * The Initial Developer of the Original Code is Netscape
\r
17 * Communications Corporation. Portions created by Netscape are
\r
18 * Copyright (C) 1997-2000 Netscape Communications Corporation. All
\r
27 * Alternatively, the contents of this file may be used under the
\r
28 * terms of the GNU Public License (the "GPL"), in which case the
\r
29 * provisions of the GPL are applicable instead of those above.
\r
30 * If you wish to allow use of your version of this file only
\r
31 * under the terms of the GPL and not to allow others to use your
\r
32 * version of this file under the NPL, indicate your decision by
\r
33 * deleting the provisions above and replace them with the notice
\r
34 * and other provisions required by the GPL. If you do not delete
\r
35 * the provisions above, a recipient may use your version of this
\r
36 * file under either the NPL or the GPL.
\r
39 package org.mozilla.javascript;
\r
41 import java.lang.reflect.*;
\r
42 import java.util.Hashtable;
\r
43 import java.util.Enumeration;
\r
47 * @author Mike Shaver
\r
48 * @author Norris Boyd
\r
49 * @see NativeJavaObject
\r
50 * @see NativeJavaClass
\r
54 JavaMembers(Scriptable scope, Class cl) {
\r
55 this.members = new Hashtable(23);
\r
56 this.staticMembers = new Hashtable(7);
\r
61 boolean has(String name, boolean isStatic) {
\r
62 Hashtable ht = isStatic ? staticMembers : members;
\r
63 Object obj = ht.get(name);
\r
67 Member member = this.findExplicitFunction(name, isStatic);
\r
68 return member != null;
\r
72 Object get(Scriptable scope, String name, Object javaObject,
\r
75 Hashtable ht = isStatic ? staticMembers : members;
\r
76 Object member = ht.get(name);
\r
77 if (!isStatic && member == null) {
\r
78 // Try to get static member from instance (LC3)
\r
79 member = staticMembers.get(name);
\r
81 if (member == null) {
\r
82 member = this.getExplicitFunction(scope, name,
\r
83 javaObject, isStatic);
\r
85 return Scriptable.NOT_FOUND;
\r
87 if (member instanceof Scriptable)
\r
88 return member; // why is this here?
\r
92 if (member instanceof BeanProperty) {
\r
93 BeanProperty bp = (BeanProperty) member;
\r
94 rval = bp.getter.invoke(javaObject, ScriptRuntime.emptyArgs);
\r
95 type = bp.getter.getReturnType();
\r
97 Field field = (Field) member;
\r
98 rval = field.get(isStatic ? null : javaObject);
\r
99 type = field.getType();
\r
101 } catch (IllegalAccessException accEx) {
\r
102 throw new RuntimeException("unexpected IllegalAccessException "+
\r
103 "accessing Java field");
\r
104 } catch (InvocationTargetException e) {
\r
105 throw WrappedException.wrapException(
\r
106 JavaScriptException.wrapException(scope, e));
\r
108 // Need to wrap the object before we return it.
\r
109 scope = ScriptableObject.getTopLevelScope(scope);
\r
110 return NativeJavaObject.wrap(scope, rval, type);
\r
113 Member findExplicitFunction(String name, boolean isStatic) {
\r
114 Hashtable ht = isStatic ? staticMembers : members;
\r
115 int sigStart = name.indexOf('(');
\r
116 Member[] methodsOrCtors = null;
\r
117 NativeJavaMethod method = null;
\r
118 boolean isCtor = (isStatic && sigStart == 0);
\r
121 // Explicit request for an overloaded constructor
\r
122 methodsOrCtors = ctors;
\r
124 else if (sigStart > 0) {
\r
125 // Explicit request for an overloaded method
\r
126 String trueName = name.substring(0,sigStart);
\r
127 Object obj = ht.get(trueName);
\r
128 if (!isStatic && obj == null) {
\r
129 // Try to get static member from instance (LC3)
\r
130 obj = staticMembers.get(trueName);
\r
132 if (obj != null && obj instanceof NativeJavaMethod) {
\r
133 method = (NativeJavaMethod)obj;
\r
134 methodsOrCtors = method.getMethods();
\r
138 if (methodsOrCtors != null) {
\r
139 for (int i = 0; i < methodsOrCtors.length; i++) {
\r
140 String nameWithSig =
\r
141 NativeJavaMethod.signature(methodsOrCtors[i]);
\r
142 if (name.equals(nameWithSig)) {
\r
143 return methodsOrCtors[i];
\r
151 Object getExplicitFunction(Scriptable scope, String name,
\r
152 Object javaObject, boolean isStatic)
\r
154 Hashtable ht = isStatic ? staticMembers : members;
\r
155 Object member = null;
\r
156 Member methodOrCtor = this.findExplicitFunction(name, isStatic);
\r
158 if (methodOrCtor != null) {
\r
159 Scriptable prototype =
\r
160 ScriptableObject.getFunctionPrototype(scope);
\r
162 if (methodOrCtor instanceof Constructor) {
\r
163 NativeJavaConstructor fun =
\r
164 new NativeJavaConstructor((Constructor)methodOrCtor);
\r
165 fun.setPrototype(prototype);
\r
169 String trueName = methodOrCtor.getName();
\r
170 member = ht.get(trueName);
\r
172 if (member instanceof NativeJavaMethod &&
\r
173 ((NativeJavaMethod)member).getMethods().length > 1 ) {
\r
174 NativeJavaMethod fun =
\r
175 new NativeJavaMethod((Method)methodOrCtor, name);
\r
176 fun.setPrototype(prototype);
\r
187 public void put(Scriptable scope, String name, Object javaObject,
\r
188 Object value, boolean isStatic)
\r
190 Hashtable ht = isStatic ? staticMembers : members;
\r
191 Object member = ht.get(name);
\r
192 if (!isStatic && member == null) {
\r
193 // Try to get static member from instance (LC3)
\r
194 member = staticMembers.get(name);
\r
196 if (member == null)
\r
197 throw reportMemberNotFound(name);
\r
198 if (member instanceof FieldAndMethods) {
\r
199 FieldAndMethods fam = (FieldAndMethods) ht.get(name);
\r
200 member = fam.getField();
\r
203 // Is this a bean property "set"?
\r
204 if (member instanceof BeanProperty) {
\r
206 Method method = ((BeanProperty) member).setter;
\r
207 if (method == null)
\r
208 throw reportMemberNotFound(name);
\r
209 Class[] types = method.getParameterTypes();
\r
210 Object[] params = { NativeJavaObject.coerceType(types[0], value) };
\r
211 method.invoke(javaObject, params);
\r
212 } catch (IllegalAccessException accessEx) {
\r
213 throw new RuntimeException("unexpected IllegalAccessException " +
\r
214 "accessing Java field");
\r
215 } catch (InvocationTargetException e) {
\r
216 throw WrappedException.wrapException(
\r
217 JavaScriptException.wrapException(scope, e));
\r
221 Field field = null;
\r
223 field = (Field) member;
\r
224 if (field == null) {
\r
225 throw Context.reportRuntimeError1(
\r
226 "msg.java.internal.private", name);
\r
228 field.set(javaObject,
\r
229 NativeJavaObject.coerceType(field.getType(), value));
\r
230 } catch (ClassCastException e) {
\r
231 throw Context.reportRuntimeError1(
\r
232 "msg.java.method.assign", name);
\r
233 } catch (IllegalAccessException accessEx) {
\r
234 throw new RuntimeException("unexpected IllegalAccessException "+
\r
235 "accessing Java field");
\r
236 } catch (IllegalArgumentException argEx) {
\r
237 throw Context.reportRuntimeError3(
\r
238 "msg.java.internal.field.type",
\r
239 value.getClass().getName(), field,
\r
240 javaObject.getClass().getName());
\r
245 Object[] getIds(boolean isStatic) {
\r
246 Hashtable ht = isStatic ? staticMembers : members;
\r
247 int len = ht.size();
\r
248 Object[] result = new Object[len];
\r
249 Enumeration keys = ht.keys();
\r
250 for (int i=0; i < len; i++)
\r
251 result[i] = keys.nextElement();
\r
255 Class getReflectedClass() {
\r
259 void reflectField(Scriptable scope, Field field) {
\r
260 int mods = field.getModifiers();
\r
261 if (!Modifier.isPublic(mods))
\r
263 boolean isStatic = Modifier.isStatic(mods);
\r
264 Hashtable ht = isStatic ? staticMembers : members;
\r
265 String name = field.getName();
\r
266 Object member = ht.get(name);
\r
267 if (member != null) {
\r
268 if (member instanceof NativeJavaMethod) {
\r
269 NativeJavaMethod method = (NativeJavaMethod) member;
\r
270 FieldAndMethods fam = new FieldAndMethods(method.getMethods(),
\r
273 fam.setPrototype(ScriptableObject.getFunctionPrototype(scope));
\r
274 getFieldAndMethodsTable(isStatic).put(name, fam);
\r
278 if (member instanceof Field) {
\r
279 Field oldField = (Field) member;
\r
280 // If this newly reflected field shadows an inherited field,
\r
281 // then replace it. Otherwise, since access to the field
\r
282 // would be ambiguous from Java, no field should be reflected.
\r
283 // For now, the first field found wins, unless another field
\r
284 // explicitly shadows it.
\r
285 if (oldField.getDeclaringClass().isAssignableFrom(field.getDeclaringClass()))
\r
286 ht.put(name, field);
\r
289 throw new RuntimeException("unknown member type");
\r
291 ht.put(name, field);
\r
294 void reflectMethod(Scriptable scope, Method method) {
\r
295 int mods = method.getModifiers();
\r
296 if (!Modifier.isPublic(mods))
\r
298 boolean isStatic = Modifier.isStatic(mods);
\r
299 Hashtable ht = isStatic ? staticMembers : members;
\r
300 String name = method.getName();
\r
301 NativeJavaMethod fun = (NativeJavaMethod) ht.get(name);
\r
303 fun = new NativeJavaMethod();
\r
305 fun.setPrototype(ScriptableObject.getFunctionPrototype(scope));
\r
313 void reflect(Scriptable scope, Class cl) {
\r
314 // We reflect methods first, because we want overloaded field/method
\r
315 // names to be allocated to the NativeJavaMethod before the field
\r
316 // gets in the way.
\r
317 Method[] methods = cl.getMethods();
\r
318 for (int i = 0; i < methods.length; i++)
\r
319 reflectMethod(scope, methods[i]);
\r
321 Field[] fields = cl.getFields();
\r
322 for (int i = 0; i < fields.length; i++)
\r
323 reflectField(scope, fields[i]);
\r
325 makeBeanProperties(scope, false);
\r
326 makeBeanProperties(scope, true);
\r
328 ctors = cl.getConstructors();
\r
331 Hashtable getFieldAndMethodsTable(boolean isStatic) {
\r
332 Hashtable fmht = isStatic ? staticFieldAndMethods
\r
334 if (fmht == null) {
\r
335 fmht = new Hashtable(11);
\r
337 staticFieldAndMethods = fmht;
\r
339 fieldAndMethods = fmht;
\r
345 void makeBeanProperties(Scriptable scope, boolean isStatic) {
\r
346 Hashtable ht = isStatic ? staticMembers : members;
\r
347 Hashtable toAdd = new Hashtable();
\r
349 // Now, For each member, make "bean" properties.
\r
350 for (Enumeration e = ht.keys(); e.hasMoreElements(); ) {
\r
352 // Is this a getter?
\r
353 String name = (String) e.nextElement();
\r
354 boolean memberIsGetMethod = name.startsWith("get");
\r
355 boolean memberIsIsMethod = name.startsWith("is");
\r
356 if (memberIsGetMethod || memberIsIsMethod) {
\r
357 // Double check name component.
\r
358 String nameComponent = name.substring(memberIsGetMethod ? 3 : 2);
\r
359 if (nameComponent.length() == 0)
\r
362 // Make the bean property name.
\r
363 String beanPropertyName = nameComponent;
\r
364 if (Character.isUpperCase(nameComponent.charAt(0))) {
\r
365 if (nameComponent.length() == 1) {
\r
366 beanPropertyName = nameComponent.substring(0, 1).toLowerCase();
\r
367 } else if (!Character.isUpperCase(nameComponent.charAt(1))) {
\r
368 beanPropertyName = Character.toLowerCase(nameComponent.charAt(0)) +
\r
369 nameComponent.substring(1);
\r
373 // If we already have a member by this name, don't do this
\r
375 if (ht.containsKey(beanPropertyName))
\r
378 // Get the method by this name.
\r
379 Object method = ht.get(name);
\r
380 if (!(method instanceof NativeJavaMethod))
\r
382 NativeJavaMethod getJavaMethod = (NativeJavaMethod) method;
\r
384 // Grab and inspect the getter method; does it have an empty parameter list,
\r
385 // with a return value (eg. a getSomething() or isSomething())?
\r
387 Method[] getMethods = getJavaMethod.getMethods();
\r
389 if (getMethods != null &&
\r
390 getMethods.length == 1 &&
\r
391 (type = getMethods[0].getReturnType()) != null &&
\r
392 (params = getMethods[0].getParameterTypes()) != null &&
\r
393 params.length == 0)
\r
396 // Make sure the method static-ness is preserved for this property.
\r
397 if (isStatic && !Modifier.isStatic(getMethods[0].getModifiers()))
\r
400 // We have a getter. Now, do we have a setter?
\r
401 Method setMethod = null;
\r
402 String setter = "set" + nameComponent;
\r
403 if (ht.containsKey(setter)) {
\r
405 // Is this value a method?
\r
406 method = ht.get(setter);
\r
407 if (method instanceof NativeJavaMethod) {
\r
410 // Note: it may be preferable to allow NativeJavaMethod.findFunction()
\r
411 // to find the appropriate setter; unfortunately, it requires an
\r
412 // instance of the target arg to determine that.
\r
415 // Make two passes: one to find a method with direct type assignment,
\r
416 // and one to find a widening conversion.
\r
417 NativeJavaMethod setJavaMethod = (NativeJavaMethod) method;
\r
418 Method[] setMethods = setJavaMethod.getMethods();
\r
419 for (int pass = 1; pass <= 2 && setMethod == null; ++pass) {
\r
420 for (int i = 0; i < setMethods.length; ++i) {
\r
421 if (setMethods[i].getReturnType() == void.class &&
\r
422 (!isStatic || Modifier.isStatic(setMethods[i].getModifiers())) &&
\r
423 (params = setMethods[i].getParameterTypes()) != null &&
\r
424 params.length == 1 ) {
\r
426 if ((pass == 1 && params[0] == type) ||
\r
427 (pass == 2 && params[0].isAssignableFrom(type))) {
\r
428 setMethod = setMethods[i];
\r
437 // Make the property.
\r
438 BeanProperty bp = new BeanProperty(getMethods[0], setMethod);
\r
439 toAdd.put(beanPropertyName, bp);
\r
444 // Add the new bean properties.
\r
445 for (Enumeration e = toAdd.keys(); e.hasMoreElements();) {
\r
446 String key = (String) e.nextElement();
\r
447 Object value = toAdd.get(key);
\r
448 ht.put(key, value);
\r
452 Hashtable getFieldAndMethodsObjects(Scriptable scope, Object javaObject,
\r
455 Hashtable ht = isStatic ? staticFieldAndMethods : fieldAndMethods;
\r
458 int len = ht.size();
\r
459 Hashtable result = new Hashtable(Math.max(len,1));
\r
460 Enumeration e = ht.elements();
\r
461 while (len-- > 0) {
\r
462 FieldAndMethods fam = (FieldAndMethods) e.nextElement();
\r
463 fam = (FieldAndMethods) fam.clone();
\r
464 fam.setJavaObject(javaObject);
\r
465 result.put(fam.getName(), fam);
\r
470 Constructor[] getConstructors() {
\r
474 static JavaMembers lookupClass(Scriptable scope, Class dynamicType,
\r
477 Class cl = dynamicType;
\r
478 Hashtable ct = classTable; // use local reference to avoid synchronize
\r
479 JavaMembers members = (JavaMembers) ct.get(cl);
\r
480 if (members != null)
\r
482 if (staticType != null && staticType != dynamicType &&
\r
483 !Modifier.isPublic(dynamicType.getModifiers()) &&
\r
484 Modifier.isPublic(staticType.getModifiers()))
\r
488 // We can use the static type, and that is OK, but we'll trace
\r
489 // back the java class chain here to look for something more suitable.
\r
490 for (Class parentType = dynamicType;
\r
491 parentType != null && parentType != ScriptRuntime.ObjectClass;
\r
492 parentType = parentType.getSuperclass())
\r
494 if (Modifier.isPublic(parentType.getModifiers())) {
\r
501 members = new JavaMembers(scope, cl);
\r
502 } catch (SecurityException e) {
\r
503 // Reflection may fail for objects that are in a restricted
\r
504 // access package (e.g. sun.*). If we get a security
\r
505 // exception, try again with the static type. Otherwise,
\r
506 // rethrow the exception.
\r
507 if (cl != staticType)
\r
508 members = new JavaMembers(scope, staticType);
\r
512 if (Context.isCachingEnabled)
\r
513 ct.put(cl, members);
\r
517 RuntimeException reportMemberNotFound(String memberName) {
\r
518 return Context.reportRuntimeError2(
\r
519 "msg.java.member.not.found", cl.getName(), memberName);
\r
522 static Hashtable classTable = new Hashtable();
\r
525 private Hashtable members;
\r
526 private Hashtable fieldAndMethods;
\r
527 private Hashtable staticMembers;
\r
528 private Hashtable staticFieldAndMethods;
\r
529 private Constructor[] ctors;
\r
532 class BeanProperty {
\r
533 BeanProperty(Method getter, Method setter) {
\r
534 this.getter = getter;
\r
535 this.setter = setter;
\r
541 class FieldAndMethods extends NativeJavaMethod {
\r
543 FieldAndMethods(Method[] methods, Field field, String name) {
\r
545 this.field = field;
\r
549 void setJavaObject(Object javaObject) {
\r
550 this.javaObject = javaObject;
\r
556 return field.getName();
\r
563 public Object getDefaultValue(Class hint) {
\r
564 if (hint == ScriptRuntime.FunctionClass)
\r
569 rval = field.get(javaObject);
\r
570 type = field.getType();
\r
571 } catch (IllegalAccessException accEx) {
\r
572 throw Context.reportRuntimeError1(
\r
573 "msg.java.internal.private", getName());
\r
575 rval = NativeJavaObject.wrap(this, rval, type);
\r
576 if (rval instanceof Scriptable) {
\r
577 rval = ((Scriptable) rval).getDefaultValue(hint);
\r
582 public Object clone() {
\r
583 FieldAndMethods result = new FieldAndMethods(methods, field, name);
\r
584 result.javaObject = javaObject;
\r
588 private Field field;
\r
589 private Object javaObject;
\r
590 private String name;
\r