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.configuration;
039:
040: import java.lang.reflect.Method;
041: import java.util.ArrayList;
042: import java.util.HashMap;
043: import java.util.Iterator;
044: import java.util.List;
045: import java.util.Map;
046: import java.util.Set;
047:
048: /**
049: * A container for all the javascript configuration information.
050: *
051: * @version $Revision: 2139 $
052: * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
053: * @author Chris Erskine
054: * @author Ahmed Ashour
055: */
056: public final class ClassConfiguration {
057: private static final String GETTER_PREFIX = "jsxGet_";
058: private static final String SETTER_PREFIX = "jsxSet_";
059: private static final String FUNCTION_PREFIX = "jsxFunction_";
060:
061: private Map propertyMap_ = new HashMap();
062: private Map functionMap_ = new HashMap();
063: private List constants_ = new ArrayList();
064: private String extendedClass_;
065: /**
066: * The fully qualified name of the class that implements this class.
067: */
068: private final String className_;
069: private final Class linkedClass_;
070: /**
071: * The constructor method in the {@link #linkedClass_}
072: */
073: private final Method jsConstructor_;
074: private final String htmlClassname_;
075: private final boolean jsObject_;
076:
077: /**
078: * Constructor
079: *
080: * @param classname the name of the configuration class this entry is for
081: * @param implementingClass - the fully qualified name of the class implementing this functionality
082: * @param jsConstructor the constructor of method <code>implementingClass</code>
083: * @param extendedClass - The name of the class that this class extends
084: * @param htmlClass The name of the html class that this object supports
085: * @param jsObject boolean flag for if this object is a JavaScript object
086: * @throws ClassNotFoundException - If the implementing class is not found
087: */
088: public ClassConfiguration(final String classname,
089: final String implementingClass, final String jsConstructor,
090: final String extendedClass, final String htmlClass,
091: final boolean jsObject) throws ClassNotFoundException {
092: className_ = classname;
093: extendedClass_ = extendedClass;
094: linkedClass_ = Class.forName(implementingClass);
095: if (jsConstructor != null && jsConstructor.length() != 0) {
096: Method foundCtor = null;
097: final Method[] methods = linkedClass_.getMethods();
098: for (int i = 0; i < methods.length; i++) {
099: if (methods[i].getName().equals(jsConstructor)) {
100: foundCtor = methods[i];
101: break;
102: }
103: }
104: if (foundCtor == null) {
105: throw new IllegalStateException("Constructor method \""
106: + jsConstructor + "\" in class \""
107: + implementingClass + " is not found.");
108: }
109: jsConstructor_ = foundCtor;
110: } else {
111: jsConstructor_ = null;
112: }
113: jsObject_ = jsObject;
114: if (htmlClass != null && htmlClass.length() != 0) {
115: htmlClassname_ = htmlClass;
116: } else {
117: htmlClassname_ = null;
118: }
119: }
120:
121: /**
122: * @return Returns the className.
123: */
124: public String getClassName() {
125: return className_;
126: }
127:
128: /**
129: * Add the property to the configuration
130: * @param name - Name of the property
131: * @param readable - Flag for if the property is readable
132: * @param writeable - Flag for if the property is writeable
133: */
134: public void addProperty(final String name, final boolean readable,
135: final boolean writeable) {
136: final PropertyInfo info = new PropertyInfo();
137: info.setReadable(readable);
138: info.setWriteable(writeable);
139: try {
140: if (readable) {
141: info.setReadMethod(linkedClass_.getMethod(GETTER_PREFIX
142: + name, null));
143: }
144: } catch (final NoSuchMethodException e) {
145: throw new IllegalStateException("Method '" + GETTER_PREFIX
146: + name + "' was not found for " + name
147: + " property in " + linkedClass_.getName());
148: }
149: // For the setters, we have to loop through the methods since we do not know what type of argument
150: // the method takes.
151: if (writeable) {
152: final Method[] methods = linkedClass_.getMethods();
153: final String setMethodName = SETTER_PREFIX + name;
154: for (int i = 0; i < methods.length; i++) {
155: if (methods[i].getName().equals(setMethodName)
156: && methods[i].getParameterTypes().length == 1) {
157: info.setWriteMethod(methods[i]);
158: break;
159: }
160: }
161: if (info.getWriteMethod() == null) {
162: throw new IllegalStateException("Method '"
163: + SETTER_PREFIX + name + "' was not found for "
164: + name + " property in "
165: + linkedClass_.getName());
166: }
167: }
168: propertyMap_.put(name, info);
169: }
170:
171: /**
172: * Add the constant to the configuration.
173: * @param name - Name of the configuration.
174: */
175: public void addConstant(final String name) {
176: constants_.add(name);
177: }
178:
179: /**
180: * Return the set of keys for the defined properties.
181: * @return a set.
182: */
183: public Set propertyKeys() {
184: return propertyMap_.keySet();
185: }
186:
187: /**
188: * Return the set of keys for the defined functions
189: * @return a set.
190: */
191: public Set functionKeys() {
192: return functionMap_.keySet();
193: }
194:
195: /**
196: * Return the constant list.
197: * @return a list.
198: */
199: public List constants() {
200: return constants_;
201: }
202:
203: /**
204: * Add the function to the configuration
205: * @param name - Name of the function
206: */
207: public void addFunction(final String name) {
208: final FunctionInfo info = new FunctionInfo();
209: final Method[] methods = linkedClass_.getMethods();
210: final String setMethodName = FUNCTION_PREFIX + name;
211: for (int i = 0; i < methods.length; i++) {
212: if (methods[i].getName().equals(setMethodName)) {
213: info.setFunctionMethod(methods[i]);
214: break;
215: }
216: }
217: if (info.getFunctionMethod() == null) {
218: throw new IllegalStateException("Method '"
219: + FUNCTION_PREFIX + name + "' was not found for "
220: + name + " function in " + linkedClass_.getName());
221: }
222: functionMap_.put(name, info);
223: }
224:
225: /**
226: * Set the browser information for this named property
227: * @param propertyName - Name of the property to set
228: * @param browserName - Browser name to set
229: * @throws IllegalStateException - Property does not exist
230: */
231: public void setBrowser(final String propertyName,
232: final String browserName) throws IllegalStateException {
233: final PropertyInfo property = getPropertyInfo(propertyName);
234: if (property == null) {
235: throw new IllegalStateException(
236: "Property does not exist to set browser");
237: }
238: property.setBrowser(new BrowserInfo(browserName));
239: }
240:
241: /**
242: * @return Returns the extendedClass.
243: */
244: public String getExtendedClass() {
245: return extendedClass_;
246: }
247:
248: /**
249: * @param extendedClass The extendedClass to set.
250: */
251: public void setExtendedClass(final String extendedClass) {
252: extendedClass_ = extendedClass;
253: }
254:
255: /**
256: * Return the PropertyInfo for the given property name
257: * @param propertyName Name of property
258: * @return ClassConfiguration.PropertyInfo
259: */
260: protected PropertyInfo getPropertyInfo(final String propertyName) {
261: return (PropertyInfo) propertyMap_.get(propertyName);
262: }
263:
264: private FunctionInfo getFunctionInfo(final String functionName) {
265: return (FunctionInfo) functionMap_.get(functionName);
266: }
267:
268: /**
269: * Test for value equality of the 2 objects
270: *
271: * @param obj the reference object with which to compare.
272: * @return <code>true</code> if the value of this object is the same as the obj
273: * argument; <code>false</code> otherwise.
274: */
275: public boolean equals(final Object obj) {
276: if (!(obj instanceof ClassConfiguration)) {
277: return false;
278: }
279: final ClassConfiguration config = (ClassConfiguration) obj;
280: if (propertyMap_.size() != config.propertyMap_.size()) {
281: return false;
282: }
283: if (functionMap_.size() != config.functionMap_.size()) {
284: return false;
285: }
286: final Set keys = config.propertyMap_.keySet();
287: final Iterator it = keys.iterator();
288: while (it.hasNext()) {
289: final String key = (String) it.next();
290: if (!(((PropertyInfo) config.propertyMap_.get(key))
291: .valueEquals(propertyMap_.get(key)))) {
292: return false;
293: }
294: }
295:
296: final Set fkeys = config.functionMap_.keySet();
297: final Iterator fit = fkeys.iterator();
298: while (fit.hasNext()) {
299: final String fkey = (String) fit.next();
300: if (!(((FunctionInfo) config.functionMap_.get(fkey))
301: .valueEquals(functionMap_.get(fkey)))) {
302: return false;
303: }
304: }
305: return true;
306: }
307:
308: /**
309: * Currently, this is the hashcode for the name.
310: * {@inheritDoc}
311: */
312: public int hashCode() {
313: return className_.hashCode();
314: }
315:
316: /**
317: * Gets the method that implements the getter for the named property
318: *
319: * @param propertyName The name of the property
320: * @return Method
321: */
322: public Method getPropertyReadMethod(final String propertyName) {
323: final PropertyInfo info = getPropertyInfo(propertyName);
324: if (info == null) {
325: return null;
326: }
327: return info.getReadMethod();
328: }
329:
330: /**
331: * Gets the method that implements the setter for the named property
332: *
333: * @param propertyName The name of the property
334: * @return Method
335: */
336: public Method getPropertyWriteMethod(final String propertyName) {
337: final PropertyInfo info = getPropertyInfo(propertyName);
338: if (info == null) {
339: return null;
340: }
341: return info.getWriteMethod();
342: }
343:
344: /**
345: * Gets the method that implements the given function
346: *
347: * @param functionName The name of the property
348: * @return Method
349: */
350: public Method getFunctionMethod(final String functionName) {
351: final FunctionInfo info = getFunctionInfo(functionName);
352: if (info == null) {
353: return null;
354: }
355: return info.getFunctionMethod();
356: }
357:
358: /**
359: * Gets the class of the Javascript host object
360: * @return Returns the linkedClass.
361: */
362: public Class getLinkedClass() {
363: return linkedClass_;
364: }
365:
366: /**
367: * Gets the JavaScript constructor method in {@link #getLinkedClass()}
368: * @return Returns the constructor Method.
369: */
370: public Method getJsConstructor() {
371: return jsConstructor_;
372: }
373:
374: /**
375: * @return Returns the htmlClassname.
376: */
377: public String getHtmlClassname() {
378: return htmlClassname_;
379: }
380:
381: /**
382: * @return Returns the jsObject.
383: */
384: public boolean isJsObject() {
385: return jsObject_;
386: }
387:
388: /**
389: * Class used to contain the property information if the property is readable, writeable and the
390: * methods that implement the get and set functions.
391: */
392: protected class PropertyInfo {
393: private boolean readable_ = false;
394: private boolean writeable_ = false;
395: private boolean hasBrowsers_ = false;
396: private Map browserMap_;
397: private Method readMethod_;
398: private Method writeMethod_;
399:
400: /**
401: * @return Returns the readMethod.
402: */
403: public Method getReadMethod() {
404: return readMethod_;
405: }
406:
407: /**
408: * @param readMethod The readMethod to set.
409: */
410: public void setReadMethod(final Method readMethod) {
411: readMethod_ = readMethod;
412: }
413:
414: /**
415: * @return Returns the writeMethod.
416: */
417: public Method getWriteMethod() {
418: return writeMethod_;
419: }
420:
421: /**
422: * @param writeMethod The writeMethod to set.
423: */
424: public void setWriteMethod(final Method writeMethod) {
425: writeMethod_ = writeMethod;
426: }
427:
428: private void setBrowser(final BrowserInfo browserInfo) {
429: if (browserMap_ == null) {
430: hasBrowsers_ = true;
431: browserMap_ = new HashMap();
432: }
433:
434: browserMap_.put(browserInfo.getBrowserName(), browserInfo);
435: }
436:
437: /**
438: * Test for value equality of the 2 objects
439: *
440: * @param obj the reference object with which to compare.
441: * @return <code>true</code> if the value of this object is the same as the obj
442: * argument; <code>false</code> otherwise.
443: */
444: private boolean valueEquals(final Object obj) {
445: if (!(obj instanceof PropertyInfo)) {
446: return false;
447: }
448: final PropertyInfo info = (PropertyInfo) obj;
449: if (hasBrowsers_ != info.hasBrowsers_) {
450: return false;
451: }
452: if (hasBrowsers_) {
453: if (browserMap_.size() != info.browserMap_.size()) {
454: return false;
455: }
456: final Set keys = browserMap_.keySet();
457: final Iterator it = keys.iterator();
458: while (it.hasNext()) {
459: final String key = (String) it.next();
460: if (!(((BrowserInfo) browserMap_.get(key))
461: .valueEquals(info.browserMap_.get(key)))) {
462: return false;
463: }
464: }
465:
466: }
467: return (readable_ == info.readable_)
468: && (writeable_ == info.writeable_);
469: }
470:
471: /**
472: * @param readable The readable to set.
473: */
474: private void setReadable(final boolean readable) {
475: readable_ = readable;
476: }
477:
478: /**
479: * @param writeable The writeable to set.
480: */
481: private void setWriteable(final boolean writeable) {
482: writeable_ = writeable;
483: }
484: }
485:
486: private class FunctionInfo {
487: private boolean hasBrowsers_ = false;
488: private Map browserMap_;
489: private Method functionMethod_;
490:
491: /**
492: * Test for value equality of the 2 objects
493: *
494: * @param obj the reference object with which to compare.
495: * @return <code>true</code> if the value of this object is the same as the obj
496: * argument; <code>false</code> otherwise.
497: */
498: private boolean valueEquals(final Object obj) {
499: if (!(obj instanceof FunctionInfo)) {
500: return false;
501: }
502: final FunctionInfo info = (FunctionInfo) obj;
503: if (hasBrowsers_ != info.hasBrowsers_) {
504: return false;
505: }
506: if (hasBrowsers_) {
507: if (browserMap_.size() != info.browserMap_.size()) {
508: return false;
509: }
510: final Set keys = browserMap_.keySet();
511: final Iterator it = keys.iterator();
512: while (it.hasNext()) {
513: final String key = (String) it.next();
514: if (!(((BrowserInfo) browserMap_.get(key))
515: .valueEquals(info.browserMap_.get(key)))) {
516: return false;
517: }
518: }
519:
520: }
521: return true;
522: }
523:
524: /**
525: * @return Returns the functionMethod.
526: */
527: public Method getFunctionMethod() {
528: return functionMethod_;
529: }
530:
531: /**
532: * @param functionMethod The functionMethod to set.
533: */
534: public void setFunctionMethod(final Method functionMethod) {
535: functionMethod_ = functionMethod;
536: }
537: }
538:
539: private class BrowserInfo {
540: private String browserName_;
541: private String minVersion_;
542: private String maxVersion_;
543: private String lessThanVersion_;
544:
545: /**
546: * Test for value equality of the 2 objects
547: *
548: * @param obj the reference object with which to compare.
549: * @return <code>true</code> if the value of this object is the same as the obj
550: * argument; <code>false</code> otherwise.
551: */
552: private boolean valueEquals(final Object obj) {
553: if (!(obj instanceof BrowserInfo)) {
554: return false;
555: }
556: final BrowserInfo info = (BrowserInfo) obj;
557: if (minVersion_ != null
558: && !minVersion_.equals(info.minVersion_)) {
559: return false;
560: }
561: if (maxVersion_ != null
562: && !maxVersion_.equals(info.maxVersion_)) {
563: return false;
564: }
565: if (lessThanVersion_ != null
566: && !lessThanVersion_.equals(info.lessThanVersion_)) {
567: return false;
568: }
569: return (browserName_ == info.browserName_);
570: }
571:
572: /**
573: * @param browserName - Name of the browser
574: */
575: public BrowserInfo(final String browserName) {
576: browserName_ = browserName;
577: }
578:
579: /**
580: * @return Returns the browserName.
581: */
582: private String getBrowserName() {
583: return browserName_;
584: }
585: }
586:
587: }
|