001: /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
002: *
003: * ***** BEGIN LICENSE BLOCK *****
004: * Version: MPL 1.1/GPL 2.0
005: *
006: * The contents of this file are subject to the Mozilla Public License Version
007: * 1.1 (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: * http://www.mozilla.org/MPL/
010: *
011: * Software distributed under the License is distributed on an "AS IS" basis,
012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
013: * for the specific language governing rights and limitations under the
014: * License.
015: *
016: * The Original Code is Rhino code, released
017: * May 6, 1999.
018: *
019: * The Initial Developer of the Original Code is
020: * Netscape Communications Corporation.
021: * Portions created by the Initial Developer are Copyright (C) 1997-1999
022: * the Initial Developer. All Rights Reserved.
023: *
024: * Contributor(s):
025: * Norris Boyd
026: * Frank Mitchell
027: * Mike Shaver
028: * Kurt Westerfeld
029: * Kemal Bayram
030: * Ulrike Mueller <umueller@demandware.com>
031: *
032: * Alternatively, the contents of this file may be used under the terms of
033: * the GNU General Public License Version 2 or later (the "GPL"), in which
034: * case the provisions of the GPL are applicable instead of those above. If
035: * you wish to allow use of your version of this file only under the terms of
036: * the GPL and not to allow others to use your version of this file under the
037: * MPL, indicate your decision by deleting the provisions above and replacing
038: * them with the notice and other provisions required by the GPL. If you do
039: * not delete the provisions above, a recipient may use your version of this
040: * file under either the MPL or the GPL.
041: *
042: * ***** END LICENSE BLOCK ***** */
043:
044: package org.mozilla.javascript;
045:
046: import java.lang.reflect.*;
047: import java.util.Hashtable;
048:
049: /**
050: * This class reflects Java classes into the JavaScript environment, mainly
051: * for constructors and static members. We lazily reflect properties,
052: * and currently do not guarantee that a single j.l.Class is only
053: * reflected once into the JS environment, although we should.
054: * The only known case where multiple reflections
055: * are possible occurs when a j.l.Class is wrapped as part of a
056: * method return or property access, rather than by walking the
057: * Packages/java tree.
058: *
059: * @author Mike Shaver
060: * @see NativeJavaArray
061: * @see NativeJavaObject
062: * @see NativeJavaPackage
063: */
064:
065: public class NativeJavaClass extends NativeJavaObject implements
066: Function {
067: static final long serialVersionUID = -6460763940409461664L;
068:
069: // Special property for getting the underlying Java class object.
070: static final String javaClassPropertyName = "__javaObject__";
071:
072: public NativeJavaClass() {
073: }
074:
075: public NativeJavaClass(Scriptable scope, Class cl) {
076: this .parent = scope;
077: this .javaObject = cl;
078: initMembers();
079: }
080:
081: protected void initMembers() {
082: Class cl = (Class) javaObject;
083: members = JavaMembers.lookupClass(parent, cl, cl, false);
084: staticFieldAndMethods = members.getFieldAndMethodsObjects(this ,
085: cl, true);
086: }
087:
088: public String getClassName() {
089: return "JavaClass";
090: }
091:
092: public boolean has(String name, Scriptable start) {
093: return members.has(name, true)
094: || javaClassPropertyName.equals(name);
095: }
096:
097: public Object get(String name, Scriptable start) {
098: // When used as a constructor, ScriptRuntime.newObject() asks
099: // for our prototype to create an object of the correct type.
100: // We don't really care what the object is, since we're returning
101: // one constructed out of whole cloth, so we return null.
102: if (name.equals("prototype"))
103: return null;
104:
105: if (staticFieldAndMethods != null) {
106: Object result = staticFieldAndMethods.get(name);
107: if (result != null)
108: return result;
109: }
110:
111: if (members.has(name, true)) {
112: return members.get(this , name, javaObject, true);
113: }
114:
115: if (javaClassPropertyName.equals(name)) {
116: Context cx = Context.getContext();
117: Scriptable scope = ScriptableObject.getTopLevelScope(start);
118: return cx.getWrapFactory().wrap(cx, scope, javaObject,
119: ScriptRuntime.ClassClass);
120: }
121:
122: // experimental: look for nested classes by appending $name to
123: // current class' name.
124: Class nestedClass = findNestedClass(getClassObject(), name);
125: if (nestedClass != null) {
126: NativeJavaClass nestedValue = new NativeJavaClass(
127: ScriptableObject.getTopLevelScope(this ),
128: nestedClass);
129: nestedValue.setParentScope(this );
130: return nestedValue;
131: }
132:
133: throw members.reportMemberNotFound(name);
134: }
135:
136: public void put(String name, Scriptable start, Object value) {
137: members.put(this , name, javaObject, value, true);
138: }
139:
140: public Object[] getIds() {
141: return members.getIds(true);
142: }
143:
144: public Class getClassObject() {
145: return (Class) super .unwrap();
146: }
147:
148: public Object getDefaultValue(Class hint) {
149: if (hint == null || hint == ScriptRuntime.StringClass)
150: return this .toString();
151: if (hint == ScriptRuntime.BooleanClass)
152: return Boolean.TRUE;
153: if (hint == ScriptRuntime.NumberClass)
154: return ScriptRuntime.NaNobj;
155: return this ;
156: }
157:
158: public Object call(Context cx, Scriptable scope,
159: Scriptable this Obj, Object[] args) {
160: // If it looks like a "cast" of an object to this class type,
161: // walk the prototype chain to see if there's a wrapper of a
162: // object that's an instanceof this class.
163: if (args.length == 1 && args[0] instanceof Scriptable) {
164: Class c = getClassObject();
165: Scriptable p = (Scriptable) args[0];
166: do {
167: if (p instanceof Wrapper) {
168: Object o = ((Wrapper) p).unwrap();
169: if (c.isInstance(o))
170: return p;
171: }
172: p = p.getPrototype();
173: } while (p != null);
174: }
175: return construct(cx, scope, args);
176: }
177:
178: public Scriptable construct(Context cx, Scriptable scope,
179: Object[] args) {
180: Class classObject = getClassObject();
181: int modifiers = classObject.getModifiers();
182: if (!(Modifier.isInterface(modifiers) || Modifier
183: .isAbstract(modifiers))) {
184: MemberBox[] ctors = members.ctors;
185: int index = NativeJavaMethod.findFunction(cx, ctors, args);
186: if (index < 0) {
187: String sig = NativeJavaMethod.scriptSignature(args);
188: throw Context.reportRuntimeError2("msg.no.java.ctor",
189: classObject.getName(), sig);
190: }
191:
192: // Found the constructor, so try invoking it.
193: return constructSpecific(cx, scope, args, ctors[index]);
194: } else {
195: Scriptable topLevel = ScriptableObject
196: .getTopLevelScope(this );
197: String msg = "";
198: try {
199: // trying to construct an interface; use JavaAdapter to
200: // construct a new class on the fly that implements this
201: // interface.
202: Object v = topLevel.get("JavaAdapter", topLevel);
203: if (v != NOT_FOUND) {
204: Function f = (Function) v;
205: Object[] adapterArgs = { this , args[0] };
206: return f.construct(cx, topLevel, adapterArgs);
207: }
208: } catch (Exception ex) {
209: // fall through to error
210: String m = ex.getMessage();
211: if (m != null)
212: msg = m;
213: }
214: throw Context.reportRuntimeError2("msg.cant.instantiate",
215: msg, classObject.getName());
216: }
217: }
218:
219: static Scriptable constructSpecific(Context cx, Scriptable scope,
220: Object[] args, MemberBox ctor) {
221: Scriptable topLevel = ScriptableObject.getTopLevelScope(scope);
222: Class[] argTypes = ctor.argTypes;
223:
224: if (ctor.vararg) {
225: // marshall the explicit parameter
226: Object[] newArgs = new Object[argTypes.length];
227: for (int i = 0; i < argTypes.length - 1; i++) {
228: newArgs[i] = Context.jsToJava(args[i], argTypes[i]);
229: }
230:
231: Object varArgs;
232:
233: // Handle special situation where a single variable parameter
234: // is given and it is a Java or ECMA array.
235: if (args.length == argTypes.length
236: && (args[args.length - 1] == null
237: || args[args.length - 1] instanceof NativeArray || args[args.length - 1] instanceof NativeJavaArray)) {
238: // convert the ECMA array into a native array
239: varArgs = Context.jsToJava(args[args.length - 1],
240: argTypes[argTypes.length - 1]);
241: } else {
242: // marshall the variable parameter
243: Class componentType = argTypes[argTypes.length - 1]
244: .getComponentType();
245: varArgs = Array.newInstance(componentType, args.length
246: - argTypes.length + 1);
247: for (int i = 0; i < Array.getLength(varArgs); i++) {
248: Object value = Context.jsToJava(
249: args[argTypes.length - 1 + i],
250: componentType);
251: Array.set(varArgs, i, value);
252: }
253: }
254:
255: // add varargs
256: newArgs[argTypes.length - 1] = varArgs;
257: // replace the original args with the new one
258: args = newArgs;
259: } else {
260: Object[] origArgs = args;
261: for (int i = 0; i < args.length; i++) {
262: Object arg = args[i];
263: Object x = Context.jsToJava(arg, argTypes[i]);
264: if (x != arg) {
265: if (args == origArgs) {
266: args = origArgs.clone();
267: }
268: args[i] = x;
269: }
270: }
271: }
272:
273: Object instance = ctor.newInstance(args);
274: // we need to force this to be wrapped, because construct _has_
275: // to return a scriptable
276: return cx.getWrapFactory()
277: .wrapNewObject(cx, topLevel, instance);
278: }
279:
280: public String toString() {
281: return "[JavaClass " + getClassObject().getName() + "]";
282: }
283:
284: /**
285: * Determines if prototype is a wrapped Java object and performs
286: * a Java "instanceof".
287: * Exception: if value is an instance of NativeJavaClass, it isn't
288: * considered an instance of the Java class; this forestalls any
289: * name conflicts between java.lang.Class's methods and the
290: * static methods exposed by a JavaNativeClass.
291: */
292: public boolean hasInstance(Scriptable value) {
293:
294: if (value instanceof Wrapper
295: && !(value instanceof NativeJavaClass)) {
296: Object instance = ((Wrapper) value).unwrap();
297:
298: return getClassObject().isInstance(instance);
299: }
300:
301: // value wasn't something we understand
302: return false;
303: }
304:
305: private static Class findNestedClass(Class parentClass, String name) {
306: String nestedClassName = parentClass.getName() + '$' + name;
307: ClassLoader loader = parentClass.getClassLoader();
308: if (loader == null) {
309: // ALERT: if loader is null, nested class should be loaded
310: // via system class loader which can be different from the
311: // loader that brought Rhino classes that Class.forName() would
312: // use, but ClassLoader.getSystemClassLoader() is Java 2 only
313: return Kit.classOrNull(nestedClassName);
314: } else {
315: return Kit.classOrNull(loader, nestedClassName);
316: }
317: }
318:
319: private Hashtable staticFieldAndMethods;
320: }
|