2003/05/12 05:10:30
[org.ibex.core.git] / src / org / mozilla / javascript / JavaMembers.java
1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-\r
2  *\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
7  *\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
12  *\r
13  * The Original Code is Rhino code, released\r
14  * May 6, 1999.\r
15  *\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
19  * Rights Reserved.\r
20  *\r
21  * Contributor(s): \r
22  * Norris Boyd\r
23  * Frank Mitchell\r
24  * Mike Shaver\r
25  * Kurt Westerfeld\r
26  *\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
37  */\r
38 \r
39 package org.mozilla.javascript;\r
40 \r
41 import java.lang.reflect.*;\r
42 import java.util.Hashtable;\r
43 import java.util.Enumeration;\r
44 \r
45 /**\r
46  *\r
47  * @author Mike Shaver\r
48  * @author Norris Boyd\r
49  * @see NativeJavaObject\r
50  * @see NativeJavaClass\r
51  */\r
52 class JavaMembers {\r
53 \r
54     JavaMembers(Scriptable scope, Class cl) {\r
55         this.members = new Hashtable(23);\r
56         this.staticMembers = new Hashtable(7);\r
57         this.cl = cl;\r
58         reflect(scope, cl);\r
59     }\r
60 \r
61     boolean has(String name, boolean isStatic) {\r
62         Hashtable ht = isStatic ? staticMembers : members;\r
63         Object obj = ht.get(name);\r
64         if (obj != null) {\r
65             return true;\r
66         } else {\r
67             Member member = this.findExplicitFunction(name, isStatic);\r
68             return member != null;\r
69         }\r
70     }\r
71 \r
72     Object get(Scriptable scope, String name, Object javaObject,\r
73                boolean isStatic)\r
74     {\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
80         }\r
81         if (member == null) {\r
82             member = this.getExplicitFunction(scope, name, \r
83                                               javaObject, isStatic);\r
84             if (member == null)\r
85                 return Scriptable.NOT_FOUND;\r
86         }\r
87         if (member instanceof Scriptable)\r
88             return member;      // why is this here?\r
89         Object rval;\r
90         Class type;\r
91         try {\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
96             } else {\r
97                 Field field = (Field) member;\r
98                 rval = field.get(isStatic ? null : javaObject);\r
99                 type = field.getType();\r
100             }\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
107         }\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
111     }\r
112 \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
119 \r
120         if (isCtor) {\r
121             // Explicit request for an overloaded constructor\r
122             methodsOrCtors = ctors;\r
123         }\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
131             }\r
132             if (obj != null && obj instanceof NativeJavaMethod) {\r
133                 method = (NativeJavaMethod)obj;\r
134                 methodsOrCtors = method.getMethods();\r
135             }\r
136         }\r
137 \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
144                 }\r
145             }\r
146         }\r
147 \r
148         return null;\r
149     }\r
150 \r
151     Object getExplicitFunction(Scriptable scope, String name, \r
152                                Object javaObject, boolean isStatic) \r
153     {\r
154         Hashtable ht = isStatic ? staticMembers : members;\r
155         Object member = null;\r
156         Member methodOrCtor = this.findExplicitFunction(name, isStatic);\r
157 \r
158         if (methodOrCtor != null) {\r
159             Scriptable prototype = \r
160                 ScriptableObject.getFunctionPrototype(scope);\r
161 \r
162             if (methodOrCtor instanceof Constructor) {\r
163                 NativeJavaConstructor fun = \r
164                     new NativeJavaConstructor((Constructor)methodOrCtor);\r
165                 fun.setPrototype(prototype);\r
166                 member = fun;\r
167                 ht.put(name, fun);\r
168             } else {\r
169                 String trueName = methodOrCtor.getName();\r
170                 member = ht.get(trueName);\r
171 \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
177                     ht.put(name, fun);\r
178                     member = fun;\r
179                 }\r
180             }\r
181         }\r
182 \r
183         return member;\r
184     }\r
185 \r
186 \r
187     public void put(Scriptable scope, String name, Object javaObject, \r
188                     Object value, boolean isStatic)\r
189     {\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
195         }\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
201         }\r
202         \r
203         // Is this a bean property "set"?\r
204         if (member instanceof BeanProperty) { \r
205             try {\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
218             }\r
219         }\r
220         else {\r
221             Field field = null;\r
222             try {\r
223                 field = (Field) member;\r
224                 if (field == null) {\r
225                     throw Context.reportRuntimeError1(\r
226                         "msg.java.internal.private", name);\r
227                 }\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
241             }\r
242         }\r
243     }\r
244 \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
252         return result;\r
253     }\r
254     \r
255     Class getReflectedClass() {\r
256         return cl;\r
257     }\r
258     \r
259     void reflectField(Scriptable scope, Field field) {\r
260         int mods = field.getModifiers();\r
261         if (!Modifier.isPublic(mods))\r
262             return;\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
271                                                           field,\r
272                                                           null);\r
273                 fam.setPrototype(ScriptableObject.getFunctionPrototype(scope));\r
274                 getFieldAndMethodsTable(isStatic).put(name, fam);\r
275                 ht.put(name, fam);\r
276                 return;\r
277             }\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
287                 return;\r
288             }\r
289             throw new RuntimeException("unknown member type");\r
290         }\r
291         ht.put(name, field);\r
292     }\r
293 \r
294     void reflectMethod(Scriptable scope, Method method) {\r
295         int mods = method.getModifiers();\r
296         if (!Modifier.isPublic(mods))\r
297             return;\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
302         if (fun == null) {\r
303             fun = new NativeJavaMethod();\r
304             if (scope != null)\r
305                 fun.setPrototype(ScriptableObject.getFunctionPrototype(scope));\r
306             ht.put(name, fun);\r
307             fun.add(method);\r
308         } else {\r
309             fun.add(method);\r
310         }\r
311     }\r
312 \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
320         \r
321         Field[] fields = cl.getFields();\r
322         for (int i = 0; i < fields.length; i++)\r
323             reflectField(scope, fields[i]);\r
324 \r
325         makeBeanProperties(scope, false);\r
326         makeBeanProperties(scope, true);\r
327         \r
328         ctors = cl.getConstructors();\r
329     }\r
330     \r
331     Hashtable getFieldAndMethodsTable(boolean isStatic) {\r
332         Hashtable fmht = isStatic ? staticFieldAndMethods\r
333                                   : fieldAndMethods;\r
334         if (fmht == null) {\r
335             fmht = new Hashtable(11);\r
336             if (isStatic)\r
337                 staticFieldAndMethods = fmht;\r
338             else\r
339                 fieldAndMethods = fmht;\r
340         }\r
341         \r
342         return fmht;\r
343     }\r
344 \r
345     void makeBeanProperties(Scriptable scope, boolean isStatic) {\r
346         Hashtable ht = isStatic ? staticMembers : members;\r
347         Hashtable toAdd = new Hashtable();\r
348         \r
349         // Now, For each member, make "bean" properties.\r
350         for (Enumeration e = ht.keys(); e.hasMoreElements(); ) {\r
351             \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
360                     continue;\r
361                 \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
370                     }\r
371                 }\r
372                 \r
373                 // If we already have a member by this name, don't do this\r
374                 // property.\r
375                 if (ht.containsKey(beanPropertyName))\r
376                     continue;\r
377                 \r
378                 // Get the method by this name.\r
379                 Object method = ht.get(name);\r
380                 if (!(method instanceof NativeJavaMethod))\r
381                     continue;\r
382                 NativeJavaMethod getJavaMethod = (NativeJavaMethod) method;\r
383                 \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
386                 Class[] params;\r
387                 Method[] getMethods = getJavaMethod.getMethods();\r
388                 Class type;\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
394                 { \r
395                     \r
396                     // Make sure the method static-ness is preserved for this property.\r
397                     if (isStatic && !Modifier.isStatic(getMethods[0].getModifiers()))\r
398                         continue;\r
399                         \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
404 \r
405                         // Is this value a method?\r
406                         method = ht.get(setter);\r
407                         if (method instanceof NativeJavaMethod) {\r
408                         \r
409                             //\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
413                             //\r
414 \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
425                                         \r
426                                         if ((pass == 1 && params[0] == type) ||\r
427                                             (pass == 2 && params[0].isAssignableFrom(type))) { \r
428                                             setMethod = setMethods[i];\r
429                                             break;\r
430                                         }\r
431                                     }\r
432                                 }\r
433                             }\r
434                         }\r
435                     }\r
436                             \r
437                     // Make the property.\r
438                     BeanProperty bp = new BeanProperty(getMethods[0], setMethod);\r
439                     toAdd.put(beanPropertyName, bp);\r
440                 }\r
441             }\r
442         }           \r
443         \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
449         }\r
450     }\r
451 \r
452     Hashtable getFieldAndMethodsObjects(Scriptable scope, Object javaObject,\r
453                                         boolean isStatic) \r
454     {\r
455         Hashtable ht = isStatic ? staticFieldAndMethods : fieldAndMethods;\r
456         if (ht == null)\r
457             return null;\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
466         }\r
467         return result;\r
468     }\r
469 \r
470     Constructor[] getConstructors() {\r
471         return ctors;\r
472     }\r
473 \r
474     static JavaMembers lookupClass(Scriptable scope, Class dynamicType,\r
475                                    Class staticType)\r
476     {\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
481             return members;\r
482         if (staticType != null && staticType != dynamicType &&\r
483             !Modifier.isPublic(dynamicType.getModifiers()) &&\r
484             Modifier.isPublic(staticType.getModifiers()))\r
485         {\r
486             cl = staticType;\r
487             \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
493             {\r
494                 if (Modifier.isPublic(parentType.getModifiers())) {\r
495                    cl = parentType;\r
496                    break;\r
497                 }\r
498             }\r
499         }\r
500         try {\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
509             else\r
510                 throw e;\r
511         }\r
512         if (Context.isCachingEnabled) \r
513             ct.put(cl, members);\r
514         return members;\r
515     }\r
516 \r
517     RuntimeException reportMemberNotFound(String memberName) {\r
518         return Context.reportRuntimeError2(\r
519             "msg.java.member.not.found", cl.getName(), memberName);\r
520     }\r
521 \r
522     static Hashtable classTable = new Hashtable();\r
523 \r
524     private Class cl;\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
530 }\r
531 \r
532 class BeanProperty {\r
533     BeanProperty(Method getter, Method setter) {\r
534         this.getter = getter;\r
535         this.setter = setter;\r
536     }\r
537     Method getter;\r
538     Method setter;\r
539 }\r
540 \r
541 class FieldAndMethods extends NativeJavaMethod {\r
542 \r
543     FieldAndMethods(Method[] methods, Field field, String name) {\r
544         super(methods);\r
545         this.field = field;\r
546         this.name = name;\r
547     }\r
548 \r
549     void setJavaObject(Object javaObject) {\r
550         this.javaObject = javaObject;\r
551     }\r
552 \r
553     String getName() {\r
554         if (field == null)\r
555             return name;\r
556         return field.getName();\r
557     }\r
558 \r
559     Field getField() {\r
560         return field;\r
561     }\r
562     \r
563     public Object getDefaultValue(Class hint) {\r
564         if (hint == ScriptRuntime.FunctionClass)\r
565             return this;\r
566         Object rval;\r
567         Class type;\r
568         try {\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
574         }\r
575         rval = NativeJavaObject.wrap(this, rval, type);\r
576         if (rval instanceof Scriptable) {\r
577             rval = ((Scriptable) rval).getDefaultValue(hint);\r
578         }\r
579         return rval;\r
580     }\r
581 \r
582     public Object clone() {\r
583         FieldAndMethods result = new FieldAndMethods(methods, field, name);\r
584         result.javaObject = javaObject;\r
585         return result;\r
586     }\r
587     \r
588     private Field field;\r
589     private Object javaObject;\r
590     private String name;\r
591 }\r