2003/05/12 05:10:30
[org.ibex.core.git] / src / org / mozilla / javascript / NativeJavaClass.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-1999 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 \r
44 /**\r
45  * This class reflects Java classes into the JavaScript environment, mainly\r
46  * for constructors and static members.  We lazily reflect properties,\r
47  * and currently do not guarantee that a single j.l.Class is only\r
48  * reflected once into the JS environment, although we should.\r
49  * The only known case where multiple reflections\r
50  * are possible occurs when a j.l.Class is wrapped as part of a\r
51  * method return or property access, rather than by walking the\r
52  * Packages/java tree.\r
53  *\r
54  * @author Mike Shaver\r
55  * @see NativeJavaArray\r
56  * @see NativeJavaObject\r
57  * @see NativeJavaPackage\r
58  */\r
59 \r
60 public class NativeJavaClass extends NativeJavaObject implements Function {\r
61 \r
62     public NativeJavaClass(Scriptable scope, Class cl) {\r
63         super(scope, cl, JavaMembers.lookupClass(scope, cl, cl));\r
64         fieldAndMethods = members.getFieldAndMethodsObjects(this, javaObject, \r
65                                                             true);\r
66     }\r
67 \r
68     public String getClassName() {\r
69         return "JavaClass";\r
70     }\r
71     \r
72     public boolean has(String name, Scriptable start) {\r
73         return members.has(name, true);\r
74     }\r
75         \r
76     public Object get(String name, Scriptable start) {\r
77         // When used as a constructor, ScriptRuntime.newObject() asks\r
78         // for our prototype to create an object of the correct type.\r
79         // We don't really care what the object is, since we're returning\r
80         // one constructed out of whole cloth, so we return null.\r
81 \r
82         if (name.equals("prototype"))\r
83             return null;\r
84         \r
85         Object result = Scriptable.NOT_FOUND;\r
86         \r
87         if (fieldAndMethods != null) {\r
88             result = fieldAndMethods.get(name);\r
89             if (result != null)\r
90                 return result;\r
91         }\r
92         \r
93         if (members.has(name, true)) {\r
94             result = members.get(this, name, javaObject, true);\r
95         } else {\r
96             // experimental:  look for nested classes by appending $name to current class' name.\r
97             try {\r
98                 String nestedName = getClassObject().getName() + '$' + name;\r
99                 Class nestedClass = ScriptRuntime.loadClassName(nestedName);\r
100                 Scriptable nestedValue = wrap(ScriptableObject.getTopLevelScope(this), nestedClass);\r
101                 nestedValue.setParentScope(this);\r
102                 result = nestedValue;\r
103             } catch (ClassNotFoundException ex) {\r
104                 throw members.reportMemberNotFound(name);\r
105             } catch (IllegalArgumentException e) {\r
106                 throw members.reportMemberNotFound(name);\r
107             }\r
108         }\r
109                 \r
110         return result;\r
111     }\r
112 \r
113     public void put(String name, Scriptable start, Object value) {\r
114         members.put(this, name, javaObject, value, true);\r
115     }\r
116 \r
117     public Object[] getIds() {\r
118         return members.getIds(true);\r
119     }\r
120     \r
121     public Class getClassObject() { \r
122         return (Class) super.unwrap();\r
123     }\r
124 \r
125     // XXX ??\r
126     public static NativeJavaClass wrap(Scriptable scope, Class cls) {\r
127         return new NativeJavaClass(scope, cls);\r
128     }\r
129 \r
130     public Object getDefaultValue(Class hint) {\r
131         if (hint == null || hint == ScriptRuntime.StringClass)\r
132             return this.toString();\r
133         if (hint == ScriptRuntime.BooleanClass)\r
134             return Boolean.TRUE;\r
135         if (hint == ScriptRuntime.NumberClass)\r
136             return ScriptRuntime.NaNobj;\r
137         return this;\r
138     }\r
139 \r
140     public Object call(Context cx, Scriptable scope, Scriptable thisObj,\r
141                        Object[] args)\r
142         throws JavaScriptException\r
143     {\r
144         // If it looks like a "cast" of an object to this class type,\r
145         // walk the prototype chain to see if there's a wrapper of a \r
146         // object that's an instanceof this class.\r
147         if (args.length == 1 && args[0] instanceof Scriptable) {\r
148             Class c = getClassObject();\r
149             Scriptable p = (Scriptable) args[0];\r
150             do {\r
151                 if (p instanceof Wrapper) {\r
152                     Object o = ((Wrapper) p).unwrap();\r
153                     if (c.isInstance(o))\r
154                         return p;\r
155                 }\r
156                 p = p.getPrototype();\r
157             } while (p != null);\r
158         }\r
159         return construct(cx, scope, args);\r
160     }\r
161 \r
162     public Scriptable construct(Context cx, Scriptable scope, Object[] args)\r
163         throws JavaScriptException\r
164     {\r
165         Class classObject = getClassObject();\r
166         int modifiers = classObject.getModifiers();\r
167         if (! (Modifier.isInterface(modifiers) || \r
168                Modifier.isAbstract(modifiers))) \r
169         {\r
170                 Constructor[] ctors = members.getConstructors();\r
171                 Member member = NativeJavaMethod.findFunction(ctors, args);\r
172                 Constructor ctor = (Constructor) member;\r
173                 if (ctor == null) {\r
174                     String sig = NativeJavaMethod.scriptSignature(args);\r
175                 throw Context.reportRuntimeError2(\r
176                     "msg.no.java.ctor", classObject.getName(), sig);\r
177                 }\r
178 \r
179                 // Found the constructor, so try invoking it.\r
180                 return NativeJavaClass.constructSpecific(cx, scope, \r
181                                                          this, ctor, args);\r
182         } else {\r
183                 Scriptable topLevel = ScriptableObject.getTopLevelScope(this);\r
184                 String msg = "";\r
185                 try {\r
186                     // trying to construct an interface; use JavaAdapter to \r
187                     // construct a new class on the fly that implements this \r
188                     // interface.\r
189                     Object v = topLevel.get("JavaAdapter", topLevel);\r
190                     if (v != NOT_FOUND) {\r
191                         Function f = (Function) v;\r
192                         Object[] adapterArgs = { this, args[0] };\r
193                         return (Scriptable) f.construct(cx, topLevel, \r
194                                                         adapterArgs);\r
195                     }\r
196                 } catch (Exception ex) {\r
197                     // fall through to error\r
198                     String m = ex.getMessage();\r
199                     if (m != null)\r
200                         msg = m;\r
201                 }\r
202                   throw Context.reportRuntimeError2(\r
203                       "msg.cant.instantiate", msg, classObject.getName());\r
204         }\r
205     }\r
206 \r
207     public static Scriptable constructSpecific(Context cx, \r
208                                                Scriptable scope, \r
209                                                Scriptable thisObj, \r
210                                                Constructor ctor,\r
211                                                Object[] args)\r
212         throws JavaScriptException\r
213     {\r
214         Scriptable topLevel = ScriptableObject.getTopLevelScope(thisObj);\r
215         Class classObject = ctor.getDeclaringClass();\r
216 \r
217         Class[] paramTypes = ctor.getParameterTypes();\r
218         for (int i = 0; i < args.length; i++) {\r
219             args[i] = NativeJavaObject.coerceType(paramTypes[i], args[i]);\r
220         }\r
221         try {\r
222             // we need to force this to be wrapped, because construct _has_\r
223             // to return a scriptable \r
224             return \r
225                 (Scriptable) NativeJavaObject.wrap(topLevel, \r
226                                                    ctor.newInstance(args),\r
227                                                    classObject);\r
228 \r
229         } catch (InstantiationException instEx) {\r
230             throw Context.reportRuntimeError2(\r
231                 "msg.cant.instantiate", \r
232                 instEx.getMessage(), classObject.getName());\r
233         } catch (IllegalArgumentException argEx) {\r
234             String signature = NativeJavaMethod.scriptSignature(args);\r
235             String ctorString = ctor.toString();\r
236             throw Context.reportRuntimeError3(\r
237                 "msg.bad.ctor.sig", argEx.getMessage(), ctorString, signature);\r
238         } catch (InvocationTargetException e) {\r
239             throw JavaScriptException.wrapException(scope, e);\r
240         } catch (IllegalAccessException accessEx) {\r
241             throw Context.reportRuntimeError1(\r
242                 "msg.java.internal.private", accessEx.getMessage());\r
243         }\r
244     }\r
245 \r
246     public String toString() {\r
247         return "[JavaClass " + getClassObject().getName() + "]";\r
248     }\r
249 \r
250     /**\r
251      * Determines if prototype is a wrapped Java object and performs\r
252      * a Java "instanceof".\r
253      * Exception: if value is an instance of NativeJavaClass, it isn't \r
254      * considered an instance of the Java class; this forestalls any \r
255      * name conflicts between java.lang.Class's methods and the \r
256      * static methods exposed by a JavaNativeClass.\r
257      */\r
258     public boolean hasInstance(Scriptable value) {\r
259 \r
260         if (value instanceof Wrapper && \r
261             !(value instanceof NativeJavaClass)) {\r
262             Object instance = ((Wrapper)value).unwrap();\r
263 \r
264             return getClassObject().isInstance(instance);\r
265         }\r
266 \r
267         // value wasn't something we understand\r
268         return false;\r
269     }\r
270 \r
271     private Hashtable fieldAndMethods;\r
272 \r
273     // beard: need a scope for finding top-level prototypes.\r
274     private Scriptable parent;\r
275 }\r