KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > gargoylesoftware > htmlunit > javascript > SimpleScriptable


1 /*
2  * Copyright (c) 2002, 2005 Gargoyle Software Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * 1. Redistributions of source code must retain the above copyright notice,
8  * this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright notice,
10  * this list of conditions and the following disclaimer in the documentation
11  * and/or other materials provided with the distribution.
12  * 3. The end-user documentation included with the redistribution, if any, must
13  * include the following acknowledgment:
14  *
15  * "This product includes software developed by Gargoyle Software Inc.
16  * (http://www.GargoyleSoftware.com/)."
17  *
18  * Alternately, this acknowledgment may appear in the software itself, if
19  * and wherever such third-party acknowledgments normally appear.
20  * 4. The name "Gargoyle Software" must not be used to endorse or promote
21  * products derived from this software without prior written permission.
22  * For written permission, please contact info@GargoyleSoftware.com.
23  * 5. Products derived from this software may not be called "HtmlUnit", nor may
24  * "HtmlUnit" appear in their name, without prior written permission of
25  * Gargoyle Software Inc.
26  *
27  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
28  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
29  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
30  * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
33  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37  */

38 package com.gargoylesoftware.htmlunit.javascript;
39
40 import java.lang.reflect.InvocationTargetException JavaDoc;
41 import java.lang.reflect.Method JavaDoc;
42 import java.util.Map JavaDoc;
43
44 import org.apache.commons.collections.Transformer;
45 import org.apache.commons.logging.Log;
46 import org.apache.commons.logging.LogFactory;
47 import org.mozilla.javascript.Context;
48 import org.mozilla.javascript.FunctionObject;
49 import org.mozilla.javascript.Scriptable;
50 import org.mozilla.javascript.ScriptableObject;
51
52 import com.gargoylesoftware.htmlunit.Assert;
53 import com.gargoylesoftware.htmlunit.BrowserVersion;
54 import com.gargoylesoftware.htmlunit.ScriptException;
55 import com.gargoylesoftware.htmlunit.WebWindow;
56 import com.gargoylesoftware.htmlunit.html.DomNode;
57 import com.gargoylesoftware.htmlunit.html.HtmlElement;
58 import com.gargoylesoftware.htmlunit.javascript.configuration.JavaScriptConfiguration;
59 import com.gargoylesoftware.htmlunit.javascript.host.Window;
60
61 /**
62  * A javascript object for a Location
63  *
64  * @version $Revision: 100 $
65  * @author <a HREF="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
66  * @author David K. Taylor
67  * @author Marc Guillemot
68  * @author Chris Erskine
69  */

70 public class SimpleScriptable extends ScriptableObject {
71     private static final long serialVersionUID = 3120000176890886780L;
72     
73     private JavaScriptEngine.PageInfo pageInfo_;
74     private DomNode domNode_;
75
76
77     /**
78      * Create an instance. Javascript objects must have a default constructor.
79      */

80     public SimpleScriptable() {
81     }
82
83
84     /**
85      * Return an immutable map containing the html to javascript mappings. Keys are
86      * java classes for the various html classes (ie HtmlInput.class) and the values
87      * are the javascript class names (ie "Anchor").
88      * @return the mappings
89      */

90     public static synchronized Map JavaDoc getHtmlJavaScriptMapping() {
91         return JavaScriptConfiguration.getHtmlJavaScriptMapping();
92     }
93
94
95
96     private JavaScriptConfiguration getJavaScriptConfiguration() {
97         final BrowserVersion browserVersion
98             = getDomNodeOrDie().getPage().getWebClient().getBrowserVersion();
99         return JavaScriptConfiguration.getInstance(browserVersion);
100     }
101
102
103     /**
104      * Return the javascript class name
105      * @return The javascript class name
106      */

107     public String JavaDoc getClassName() {
108         final String JavaDoc javaClassName = getClass().getName();
109         final int index = javaClassName.lastIndexOf(".");
110         if( index == -1 ) {
111             throw new IllegalStateException JavaDoc("No dot in classname: "+javaClassName);
112         }
113
114         return javaClassName.substring(index+1);
115     }
116
117
118     /**
119      * Set the page info. This contains information specific to the rhino engine.
120      * @param pageInfo The new pageInfo.
121      */

122     public final void setPageInfo( final JavaScriptEngine.PageInfo pageInfo ) {
123         Assert.notNull("pageInfo", pageInfo);
124         pageInfo_ = pageInfo;
125     }
126
127     /**
128      * Create a new javascript object
129      * @param className The class name of the new object
130      * @return The new object
131      */

132     public SimpleScriptable makeJavaScriptObject( final String JavaDoc className ) {
133         final JavaScriptEngine.PageInfo pageInfo = getPageInfo();
134
135         final SimpleScriptable newObject;
136         try {
137             newObject = (SimpleScriptable) Context.getCurrentContext().newObject(
138                 pageInfo.getScope(), className);
139             initJavaScriptObject( newObject );
140             return newObject;
141         }
142         catch( final Exception JavaDoc e ) {
143             throw new ScriptException(e);
144         }
145     }
146
147
148     /**
149      * Initialize a new javascript object
150      * @param newObject The JavaScript object to initialize.
151      */

152     protected void initJavaScriptObject( final SimpleScriptable newObject ) {
153         final JavaScriptEngine.PageInfo pageInfo = getPageInfo();
154
155         newObject.setPageInfo( pageInfo );
156     }
157
158
159     /**
160      * Return the DOM node that corresponds to this javascript object or throw
161      * an exception if one cannot be found.
162      * @return The DOM node
163      * @exception IllegalStateException If the DOM node could not be found.
164      */

165     public final DomNode getDomNodeOrDie() throws IllegalStateException JavaDoc {
166         if( domNode_ == null ) {
167             final String JavaDoc clazz = getClass().getName();
168             throw new IllegalStateException JavaDoc("DomNode has not been set for this SimpleScriptable: " + clazz);
169         }
170         else {
171             return domNode_;
172         }
173     }
174
175
176     /**
177      * Return the html element that corresponds to this javascript object or throw an exception
178      * if one cannot be found.
179      * @return The html element
180      * @exception IllegalStateException If the html element could not be found.
181      */

182     public final HtmlElement getHtmlElementOrDie() throws IllegalStateException JavaDoc {
183         return (HtmlElement) getDomNodeOrDie();
184     }
185
186
187     /**
188      * Return the DOM node that corresponds to this javascript object
189      * or null if a node hasn't been set.
190      * @return The DOM node or null
191      */

192     public final DomNode getDomNodeOrNull() {
193         return domNode_;
194     }
195
196
197     /**
198      * Return the html element that corresponds to this javascript object
199      * or null if an element hasn't been set.
200      * @return The html element or null
201      */

202     public final HtmlElement getHtmlElementOrNull() {
203         return (HtmlElement) getDomNodeOrNull();
204     }
205
206
207     /**
208      * Set the DOM node that corresponds to this javascript object
209      * @param domNode The DOM node
210      */

211     public void setDomNode( final DomNode domNode ) {
212         setDomNode( domNode, true );
213     }
214
215     /**
216      * Set the DOM node that corresponds to this javascript object
217      * @param domNode The DOM node
218      * @param assignScriptObject If true, call <code>setScriptObject</code> on domNode
219      */

220     protected void setDomNode( final DomNode domNode, final boolean assignScriptObject ) {
221         Assert.notNull("domNode", domNode);
222         domNode_ = domNode;
223         if (assignScriptObject) {
224             domNode_.setScriptObject(this);
225         }
226     }
227
228     /**
229      * Set the html element that corresponds to this javascript object
230      * @param htmlElement The html element
231      */

232     public void setHtmlElement( final HtmlElement htmlElement ) {
233         setDomNode(htmlElement);
234     }
235
236
237     /**
238      * Return the specified property or NOT_FOUND if it could not be found. This could also be
239      * a call to a function so check it out as a function also.
240      * @param name The name of the property
241      * @param start The scriptable object that was originally queried for this property
242      * @return The property.
243      */

244     public Object JavaDoc get( final String JavaDoc name, final Scriptable start ) {
245         // Some calls to get will happen during the initialization of the superclass.
246
// At this point, we don't have enough information to do our own initialization
247
// so we have to just pass this call through to the superclass.
248
if( domNode_ == null ) {
249             final Object JavaDoc result = super.get(name, start);
250             // this may help to find which properties htmlunit should impement
251
if (result == NOT_FOUND) {
252                 getLog().debug("Property \"" + name + "\" of " + start + " not defined as pure js property");
253             }
254             return result;
255         }
256
257         final JavaScriptConfiguration configuration = getJavaScriptConfiguration();
258         final Class JavaDoc clazz = getClass();
259         final Object JavaDoc result;
260
261         final Method JavaDoc propertyMethod = configuration.getPropertyReadMethod(clazz, name);
262         final Method JavaDoc functionMethod = configuration.getFunctionMethod(clazz, name);
263         if (propertyMethod != null && functionMethod != null) {
264             throw new IllegalStateException JavaDoc("Name is both a property and a function: name=["
265                 + name + "] class=[" + clazz.getName() + "]");
266         }
267         if (propertyMethod == null) {
268             if (functionMethod == null) {
269                 result = super.get(name, start);
270             }
271             else {
272                 result = new FunctionObject(name, functionMethod, this);
273             }
274         }
275         else {
276             try {
277                 result = propertyMethod.invoke(this, new Object JavaDoc[0]);
278             }
279             catch (final Exception JavaDoc e) {
280                 throw new ScriptException(e);
281             }
282         }
283         
284         // this may help to find which properties htmlunit should impement
285
if (result == NOT_FOUND) {
286             getLog().debug("Property \"" + name + "\" of " + start + " not defined as fixed property");
287         }
288
289         return result;
290     }
291
292
293     /**
294      * Set the specified property
295      * @param name The name of the property
296      * @param start The scriptable object that was originally invoked for this property
297      * @param newValue The new value
298      */

299     public void put( final String JavaDoc name, final Scriptable start, Object JavaDoc newValue ) {
300         // Some calls to put will happen during the initialization of the superclass.
301
// At this point, we don't have enough information to do our own initialization
302
// so we have to just pass this call through to the superclass.
303
final SimpleScriptable simpleScriptable = (SimpleScriptable) start;
304         if (simpleScriptable.domNode_ == null ) {
305             super.put(name, start, newValue);
306             return;
307         }
308
309         final JavaScriptConfiguration configuration = simpleScriptable.getJavaScriptConfiguration();
310         final Method JavaDoc setterMethod = configuration.getPropertyWriteMethod(getClass(), name);
311
312         if (setterMethod == null) {
313             if (configuration.propertyExists(getClass(), name)) {
314                 throw Context.reportRuntimeError("Property \"" + name + "\" is not writable for " + start + ". "
315                     + "Cant set it to: " + newValue);
316             }
317             else {
318                 getLog().debug("No configured setter \"" + name + "\" found for "
319                     + start + ". Setting it as pure javascript property.");
320                
321                 super.put(name, start, newValue);
322             }
323         }
324         else {
325             final Class JavaDoc parameterClass = setterMethod.getParameterTypes()[0];
326             if( parameterClass == String JavaDoc.class) {
327                 newValue = Context.toString(newValue);
328             }
329             else if (Integer.TYPE.equals(parameterClass)) {
330                 newValue = new Integer JavaDoc((new Double JavaDoc(Context.toNumber(newValue))).intValue());
331             }
332             else if (Boolean.TYPE.equals(parameterClass)) {
333                 newValue = Boolean.valueOf(Context.toBoolean(newValue));
334             }
335             try {
336                 setterMethod.invoke(
337                     simpleScriptable.findMatchingScriptable(start, setterMethod),
338                     new Object JavaDoc[]{ newValue } );
339             }
340             catch( final InvocationTargetException JavaDoc e ) {
341                 throw new ScriptException(e.getTargetException());
342             }
343             catch( final Exception JavaDoc e ) {
344                 throw new ScriptException(e);
345             }
346
347         }
348     }
349
350
351     /**
352      * Walk up the prototype chain and return the first scriptable that this method can
353      * be invoked on.
354      * @param start The object on which we are starting the search
355      * @param method The method
356      * @return The first scriptable
357      * @throws IllegalStateException If a matching scriptable could not be found.
358      */

359     private Scriptable findMatchingScriptable( final Scriptable start, final Method JavaDoc method ) {
360         final Class JavaDoc declaringClass = method.getDeclaringClass();
361         Scriptable scriptable = start;
362         while( declaringClass.isInstance(start) == false ) {
363             scriptable = scriptable.getPrototype();
364             if( scriptable == null ) {
365                 throw new IllegalStateException JavaDoc("Couldn't find a matching scriptable");
366             }
367         }
368
369         return scriptable;
370     }
371
372
373     /**
374      * Return the log that is being used for all scripting objects
375      * @return The log.
376      */

377     protected final Log getLog() {
378         return LogFactory.getLog(getClass());
379     }
380
381
382     /**
383      * Gets the associated page info
384      * @return the info
385      */

386     protected JavaScriptEngine.PageInfo getPageInfo() {
387         if( pageInfo_ == null ) {
388             throw new IllegalStateException JavaDoc("pageInfo_ has not been initialized!");
389         }
390         return pageInfo_;
391     }
392
393
394     /**
395      * Return the javascript object that corresponds to the specified object.
396      * New javascript objects will be created as needed. If a javascript object
397      * cannot be created for a domNode then NOT_FOUND will be returned.
398      *
399      * @param object a {@link DomNode} or a {@link WebWindow}
400      * @return The javascript object or NOT_FOUND
401      */

402     protected SimpleScriptable getScriptableFor(final Object JavaDoc object) {
403         if (object instanceof WebWindow) {
404             return (SimpleScriptable) ((WebWindow) object).getScriptObject();
405         }
406         
407         final DomNode domNode = (DomNode) object;
408         
409         final Object JavaDoc scriptObject = domNode.getScriptObject();
410         if( scriptObject != null ) {
411             return (SimpleScriptable)scriptObject;
412         }
413         else {
414             return makeScriptableFor(domNode);
415         }
416     }
417
418     /**
419      * Builds a new the javascript object that corresponds to the specified object.<br>
420      * @param domNode the dom node for which a JS object should be created
421      * @return The javascript object
422      */

423     public SimpleScriptable makeScriptableFor(final DomNode domNode) {
424
425         final String JavaDoc javaScriptClassName = (String JavaDoc)getHtmlJavaScriptMapping().get(domNode.getClass());
426         final SimpleScriptable scriptable;
427         if (javaScriptClassName == null) {
428             // We don't have a specific subclass for this element so create something generic.
429
scriptable = makeJavaScriptObject("HTMLElement");
430             getLog().debug("No javascript class found for element <"+domNode.getNodeName()+">. Using HTMLElement");
431         }
432         else {
433             scriptable = makeJavaScriptObject(javaScriptClassName);
434         }
435         scriptable.setDomNode(domNode);
436         // parent scope needs to be set to "window" (no simple unit test found to illustrate the necessity)
437
scriptable.setParentScope((Scriptable) domNode.getPage().getEnclosingWindow().getScriptObject());
438         return scriptable;
439     }
440
441
442     /**
443      * Gets a transformer getting the scriptable element for an HtmlElement
444      * @return the transformer.
445      */

446     protected Transformer getTransformerScriptableFor() {
447         return new Transformer() {
448             public Object JavaDoc transform(final Object JavaDoc obj) {
449                 return getScriptableFor(obj);
450             }
451         };
452     }
453
454     /**
455      * Return the value at the specified location in the argument list. If the index is larger
456      * than the argument array then return the default value.
457      *
458      * @param index The index into the argument list.
459      * @param args The argument list.
460      * @param defaultValue The default value to return if the arg wasn't specified.
461      * @return The specified object or null
462      */

463     public static Object JavaDoc getObjectArg( final int index, final Object JavaDoc[] args, final Object JavaDoc defaultValue ) {
464         if( index >= args.length ) {
465             return defaultValue;
466         }
467         else {
468             return args[index];
469         }
470     }
471
472
473     /**
474      * Return the string value at the specified location in the argument list. If the index is larger
475      * than the argument array then return the default value.
476      *
477      * @param index The index into the argument list.
478      * @param args The argument list.
479      * @param defaultValue The default value to return if the arg wasn't specified.
480      * @return The specified string or null
481      */

482     public static String JavaDoc getStringArg( final int index, final Object JavaDoc[] args, final String JavaDoc defaultValue ) {
483         return Context.toString(getObjectArg(index, args, defaultValue));
484     }
485
486
487     /**
488      * Return the boolean value at the specified location in the argument list. If the index is larger
489      * than the argument array then return the default value.
490      *
491      * @param index The index into the argument list.
492      * @param args The argument list.
493      * @param defaultValue The default value to be used.
494      * @return The specified boolean or the default value.
495      */

496     public static boolean getBooleanArg( final int index, final Object JavaDoc[] args, final boolean defaultValue ) {
497         final Boolean JavaDoc defaultBoolean = Boolean.valueOf(defaultValue);
498
499         return Context.toBoolean(getObjectArg(index, args, defaultBoolean));
500     }
501
502
503     /**
504      * Return the int value at the specified location in the argument list. If the index is larger
505      * than the argument array then return the default value.
506      *
507      * @param index The index into the argument list.
508      * @param args The argument list.
509      * @param defaultValue The default value to be used.
510      * @return The specified int or the default value.
511      */

512     public static int getIntArg( final int index, final Object JavaDoc[] args, final int defaultValue ) {
513         return (int) Context.toNumber(getObjectArg(index, args, new Integer JavaDoc(defaultValue)));
514     }
515
516
517     /**
518      * Return the javascript default value of this object. This is the javascript equivilent
519      * of a toString() in java.
520      *
521      * @param hint A hint as to the format of the default value. Ignored in this case.
522      * @return The default value.
523      */

524     public Object JavaDoc getDefaultValue( final Class JavaDoc hint ) {
525         return toString();
526     }
527     
528     /**
529      * Gets the window that is (or should be) the scope for this object
530      * @return the window
531      * @throws RuntimeException if the window can't be found, what should never occurs
532      */

533     protected Window getWindow() throws RuntimeException JavaDoc {
534         Scriptable current = this;
535         while (current != null) {
536             if (current instanceof Window) {
537                 return (Window) current;
538             }
539             current = current.getParentScope();
540         }
541         throw new RuntimeException JavaDoc("Unable to find associated window to " + this);
542     }
543 }
544
545
Popular Tags