001: /*
002: * Copyright (c) 2002-2008 Gargoyle Software Inc. All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * 1. Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: * 2. Redistributions in binary form must reproduce the above copyright notice,
010: * this list of conditions and the following disclaimer in the documentation
011: * and/or other materials provided with the distribution.
012: * 3. The end-user documentation included with the redistribution, if any, must
013: * include the following acknowledgment:
014: *
015: * "This product includes software developed by Gargoyle Software Inc.
016: * (http://www.GargoyleSoftware.com/)."
017: *
018: * Alternately, this acknowledgment may appear in the software itself, if
019: * and wherever such third-party acknowledgments normally appear.
020: * 4. The name "Gargoyle Software" must not be used to endorse or promote
021: * products derived from this software without prior written permission.
022: * For written permission, please contact info@GargoyleSoftware.com.
023: * 5. Products derived from this software may not be called "HtmlUnit", nor may
024: * "HtmlUnit" appear in their name, without prior written permission of
025: * Gargoyle Software Inc.
026: *
027: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
028: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
029: * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
030: * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
031: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
032: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
033: * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
034: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
035: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
036: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
037: */
038: package com.gargoylesoftware.htmlunit.javascript;
039:
040: import java.lang.reflect.Method;
041: import java.util.HashMap;
042: import java.util.Map;
043:
044: import org.apache.commons.lang.ArrayUtils;
045: import org.apache.commons.lang.ClassUtils;
046: import org.mozilla.javascript.Context;
047: import org.mozilla.javascript.FunctionObject;
048: import org.mozilla.javascript.Scriptable;
049: import org.mozilla.javascript.ScriptableObject;
050: import org.w3c.dom.NamedNodeMap;
051: import org.w3c.dom.NodeList;
052:
053: /**
054: * Simple wrapper to make "normal" object scriptable according to specific configuration
055: * and allowing use of index properties.
056: * TODO: Configuration of the
057: * properties and functions should occur from the xml configuration according to
058: * the browser to simulate.
059: *
060: * @version $Revision: 2132 $
061: * @author Marc Guillemot
062: */
063: public class ScriptableWrapper extends ScriptableObject {
064: private static final long serialVersionUID = 1736378450382368760L;
065:
066: private final Map properties_ = new HashMap();
067:
068: private Method getByIndexMethod_;
069: private final Object javaObject_;
070: private final String jsClassName_;
071: private Method getByNameFallback_;
072:
073: /**
074: * Constructs a wrapper for the java object.
075: * @param scope the scope of the executing script
076: * @param javaObject the javaObject to wrap
077: * @param staticType the static type of the object
078: */
079: public ScriptableWrapper(final Scriptable scope,
080: final Object javaObject, final Class staticType) {
081: javaObject_ = javaObject;
082: setParentScope(scope);
083:
084: // all these information should come from the xml js configuration file
085: // just for a first time...
086: if (NodeList.class.equals(staticType)
087: || NamedNodeMap.class.equals(staticType)) {
088: try {
089: jsClassName_ = ClassUtils.getShortClassName(staticType);
090:
091: // is there a better way that would avoid to keep local
092: // information?
093: // it seems that defineProperty with only accepts delegate if
094: // its method takes a ScriptableObject
095: // as parameter.
096: final Method length = javaObject.getClass().getMethod(
097: "getLength", ArrayUtils.EMPTY_CLASS_ARRAY);
098: properties_.put("length", length);
099:
100: final Method item = javaObject.getClass().getMethod(
101: "item", new Class[] { Integer.TYPE });
102: defineProperty("item", new MethodWrapper("item",
103: staticType, new Class[] { Integer.TYPE }), 0);
104:
105: final Method toString = getClass().getMethod(
106: "jsToString", ArrayUtils.EMPTY_CLASS_ARRAY);
107: defineProperty("toString", new FunctionObject(
108: "toString", toString, this ), 0);
109:
110: getByIndexMethod_ = item;
111:
112: if (NamedNodeMap.class.equals(staticType)) {
113: final Method getNamedItem = javaObject.getClass()
114: .getMethod("getNamedItem",
115: new Class[] { String.class });
116: defineProperty("getNamedItem", new MethodWrapper(
117: "getNamedItem", staticType,
118: new Class[] { String.class }), 0);
119:
120: getByNameFallback_ = getNamedItem;
121: }
122: } catch (final Exception e) {
123: throw new RuntimeException("Method not found", e);
124: }
125: } else {
126: throw new RuntimeException("Unknown type: "
127: + staticType.getName());
128: }
129: }
130:
131: /**
132: * {@inheritDoc}
133: * @see org.mozilla.javascript.ScriptableObject#get(java.lang.String, org.mozilla.javascript.Scriptable)
134: */
135: public Object get(final String name, final Scriptable start) {
136: final Method propertyGetter = (Method) properties_.get(name);
137: final Object response;
138: if (propertyGetter != null) {
139: response = invoke(propertyGetter);
140: } else {
141: final Object fromSuper = super .get(name, start);
142: if (fromSuper != Scriptable.NOT_FOUND) {
143: response = fromSuper;
144: } else {
145: final Object byName = invoke(getByNameFallback_,
146: new Object[] { name });
147: if (byName != null) {
148: response = byName;
149: } else {
150: response = Scriptable.NOT_FOUND;
151: }
152: }
153: }
154:
155: return Context.javaToJS(response, ScriptableObject
156: .getTopLevelScope(start));
157: }
158:
159: /**
160: * {@inheritDoc}
161: * @see org.mozilla.javascript.ScriptableObject#has(java.lang.String, org.mozilla.javascript.Scriptable)
162: */
163: public boolean has(final String name, final Scriptable start) {
164: return properties_.containsKey(name) || super .has(name, start);
165: }
166:
167: /**
168: * Invokes the method on the wrapped object
169: * @param method the method to invoke
170: * @return the invocation result
171: */
172: protected Object invoke(final Method method) {
173: return invoke(method, ArrayUtils.EMPTY_OBJECT_ARRAY);
174: }
175:
176: /**
177: * Invokes the method on the wrapped object
178: * @param method the method to invoke
179: * @param args the argument to pass to the method
180: * @return the invocation result
181: */
182: protected Object invoke(final Method method, final Object[] args) {
183: try {
184: return method.invoke(javaObject_, args);
185: } catch (final Exception e) {
186: throw new RuntimeException(
187: "Invocation of method on java object failed", e);
188: }
189: }
190:
191: /**
192: * {@inheritDoc}
193: * @see org.mozilla.javascript.ScriptableObject#get(int, org.mozilla.javascript.Scriptable)
194: */
195: public Object get(final int index, final Scriptable start) {
196: if (getByIndexMethod_ != null) {
197: final Object byIndex = invoke(getByIndexMethod_,
198: new Object[] { new Integer(index) });
199: return Context.javaToJS(byIndex, ScriptableObject
200: .getTopLevelScope(start));
201: } else {
202: return super .get(index, start);
203: }
204: }
205:
206: /**
207: * {@inheritDoc}
208: * @see org.mozilla.javascript.ScriptableObject#getDefaultValue(java.lang.Class)
209: */
210: public Object getDefaultValue(final Class hint) {
211: if (hint == null || String.class == null) {
212: return jsToString();
213: } else {
214: return super .getDefaultValue(hint);
215: }
216: }
217:
218: /**
219: * To use as "toString" function in javascript
220: * @return the string representation
221: */
222: public String jsToString() {
223: return "[object " + getClassName() + "]";
224: }
225:
226: /**
227: * {@inheritDoc}
228: * @see org.mozilla.javascript.ScriptableObject#getClassName()
229: */
230: public String getClassName() {
231: return jsClassName_;
232: }
233:
234: /**
235: * Gets the java object made availabe to javascript through this wrapper
236: * @return the wrapped object
237: */
238: public Object getWrappedObject() {
239: return javaObject_;
240: }
241: }
|