001: /* ****************************************************************************
002: * ViewSchema.java
003: * ****************************************************************************/
004:
005: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
006: * Copyright 2001-2007 Laszlo Systems, Inc. All Rights Reserved. *
007: * Use is subject to license terms. *
008: * J_LZ_COPYRIGHT_END *********************************************************/
009:
010: package org.openlaszlo.compiler;
011:
012: import java.io.*;
013: import java.util.*;
014: import org.apache.oro.text.regex.*;
015: import org.jdom.Document;
016: import org.jdom.Attribute;
017: import org.jdom.Element;
018: import org.jdom.Namespace;
019: import org.jdom.output.XMLOutputter;
020: import org.jdom.input.SAXBuilder;
021: import org.jdom.JDOMException;
022: import org.openlaszlo.xml.internal.Schema;
023: import org.openlaszlo.xml.internal.XMLUtils;
024: import org.openlaszlo.utils.ChainedException;
025: import org.openlaszlo.server.*;
026:
027: /** A schema that describes a Laszlo XML file. */
028: public class ViewSchema extends Schema {
029: private static final Set sInputTextElements = new HashSet();
030: private static final Set sHTMLContentElements = new HashSet();
031:
032: /** The location of the Laszlo LFC bootstrap interface declarations file */
033: private final String SCHEMA_PATH = LPS.HOME() + File.separator
034: + "WEB-INF" + File.separator + "lps" + File.separator
035: + "schema" + File.separator + "lfc.lzx";
036:
037: private Document schemaDOM = null;
038:
039: private static Document sCachedSchemaDOM;
040: private static long sCachedSchemaLastModified;
041:
042: /** Default table of attribute name -> typecode */
043: private static final Map sAttributeTypes = new HashMap();
044:
045: /** Mapping of RNG type names -> LPS Types */
046: private static final Map sRNGtoLPSTypeMap = new HashMap();
047:
048: /** {String} */
049: private static final Set sMouseEventAttributes;
050:
051: /** Maps a class (name) to its ClassModel. Holds info about
052: * attribute/types for each class, as well as pointer to the
053: * superclass if any.
054: */
055: private final Map mClassMap = new HashMap();
056:
057: /**
058: * If true, requires class names to be valid javascript identifiers.
059: * We disable this when defining LZX builtin tags such as "import"
060: * which are reserved javascript tokens.
061: */
062: public boolean enforceValidIdentifier = false;
063:
064: /** Type of script expressions. */
065: public static final Type EXPRESSION_TYPE = newType("expression");
066:
067: /** 'boolean' is compiled the same as an expression type */
068: public static final Type BOOLEAN_TYPE = newType("boolean");
069:
070: public static final Type REFERENCE_TYPE = newType("reference");
071: /** Type of event handler bodies. */
072: public static final Type EVENT_HANDLER_TYPE = newType("script");
073:
074: /** Type of attribute setter function */
075: public static final Type SETTER_TYPE = newType("setter");
076:
077: /** Type of tokens. */
078: public static final Type TOKEN_TYPE = newType("token");
079: public static final Type COLOR_TYPE = newType("color");
080: public static final Type NUMBER_EXPRESSION_TYPE = newType("numberExpression");
081: public static final Type SIZE_EXPRESSION_TYPE = newType("size");
082: public static final Type CSS_TYPE = newType("css");
083: public static final Type INHERITABLE_BOOLEAN_TYPE = newType("inheritableBoolean");
084: public static final Type XML_LITERAL = newType("xmlLiteral");
085: public static final Type METHOD_TYPE = newType("method");
086:
087: static {
088:
089: sHTMLContentElements.add("text");
090: sInputTextElements.add("inputtext");
091:
092: // from http://www.w3.org/TR/REC-html40/interact/scripts.html
093: String[] mouseEventAttributes = { "onclick", "ondblclick",
094: "onmousedown", "onmouseup", "onmouseover",
095: "onmousemove", "onmouseout" };
096: String[] eventAttributes = { "onkeypress", "onstart", "onstop",
097: "onfocus", "onblur", "onkeydown", "onkeyup",
098: "onsubmit", "onreset", "onselect", "onchange",
099: "oninit", "onerror", "ondata", "ontimeout",
100: "oncommand", "onapply", "onremove" };
101: setAttributeTypes(mouseEventAttributes, EVENT_HANDLER_TYPE);
102: setAttributeTypes(eventAttributes, EVENT_HANDLER_TYPE);
103: sMouseEventAttributes = new HashSet(Arrays
104: .asList(mouseEventAttributes));
105: }
106:
107: private static final String AUTOINCLUDES_PROPERTY_FILE = LPS
108: .getMiscDirectory()
109: + File.separator + "lzx-autoincludes.properties";
110: public static final Properties sAutoincludes = new Properties();
111:
112: static {
113: try {
114: InputStream is = new FileInputStream(
115: AUTOINCLUDES_PROPERTY_FILE);
116: try {
117: sAutoincludes.load(is);
118: } finally {
119: is.close();
120: }
121: } catch (java.io.IOException e) {
122: throw new ChainedException(e);
123: }
124: }
125:
126: /** Set the attributes to the type.
127: * @param attributes a list of attributes
128: * @param type a type
129: */
130: private static void setAttributeTypes(String[] attributes, Type type) {
131: for (int i = 0; i < attributes.length; i++) {
132: sAttributeTypes.put(attributes[i].intern(), type);
133: }
134: }
135:
136: public AttributeSpec findSimilarAttribute(String className,
137: String attributeName) {
138: ClassModel info = getClassModel(className);
139:
140: if (info != null) {
141: return info.findSimilarAttribute(attributeName);
142: } else {
143: // Check other classes....
144: return null;
145: }
146: }
147:
148: /** Set the attribute to the given type, for a specific element */
149: public void setAttributeType(Element elt, String classname,
150: String attrName, AttributeSpec attrspec) {
151: ClassModel classModel = getClassModel(classname);
152: if (classModel == null) {
153: throw new RuntimeException(
154: /* (non-Javadoc)
155: * @i18n.test
156: * @org-mes="undefined class: " + p[0]
157: */
158: org.openlaszlo.i18n.LaszloMessages.getMessage(
159: ViewSchema.class.getName(), "051018-168",
160: new Object[] { classname }));
161: }
162: if (classModel.attributeSpecs.get(attrName) != null) {
163: throw new CompilationError(
164: /* (non-Javadoc)
165: * @i18n.test
166: * @org-mes="duplicate definition of attribute " + p[0] + "." + p[1]
167: */
168: org.openlaszlo.i18n.LaszloMessages.getMessage(
169: ViewSchema.class.getName(), "051018-178",
170: new Object[] { classname, attrName }), elt);
171: }
172: classModel.attributeSpecs.put(attrName, attrspec);
173:
174: if (attrName.equals("text")) {
175: classModel.supportsTextAttribute = true;
176: }
177: }
178:
179: /** Checks to do when declaring a method on a class;
180: * Does the class exist?
181: * Is this a duplicate of another method declaration on this class?
182: * Does the superclass allow overriding of this method?
183: */
184: public void checkMethodDeclaration(Element elt, String classname,
185: String methodName, CompilationEnvironment env) {
186: ClassModel classModel = getClassModel(classname);
187: if (classModel == null) {
188: throw new RuntimeException(
189: /* (non-Javadoc)
190: * @i18n.test
191: * @org-mes="undefined class: " + p[0]
192: */
193: org.openlaszlo.i18n.LaszloMessages.getMessage(
194: ViewSchema.class.getName(), "051018-168",
195: new Object[] { classname }));
196: }
197: AttributeSpec localAttr = classModel
198: .getLocalAttribute(methodName);
199: if (localAttr != null) {
200: if (localAttr.type == METHOD_TYPE) {
201: env.warn(
202: /* (non-Javadoc)
203: * @i18n.test
204: * @org-mes="duplicate definition of method " + p[0] + "." + p[1]
205: */
206: org.openlaszlo.i18n.LaszloMessages.getMessage(
207: ViewSchema.class.getName(), "051018-207",
208: new Object[] { classname, methodName }), elt);
209: } else {
210: env.warn("Method named " + methodName + " on class "
211: + classname
212: + " conflicts with attribute with named "
213: + methodName + " and type " + localAttr.type,
214: elt);
215: }
216: }
217:
218: if (!methodOverrideAllowed(classname, methodName)) {
219: env
220: .warn(
221: "Method "
222: + classname
223: + "."
224: + methodName
225: + " is overriding a superclass method"
226: + " of the same name which has been declared non-overridable",
227: elt);
228: }
229: }
230:
231: /** Checks to do when declaring a method on an instance;
232: * Does the class exist?
233: * Does the superclass allow overriding of this method?
234: */
235: public void checkInstanceMethodDeclaration(Element elt,
236: String classname, String methodName,
237: CompilationEnvironment env) {
238: ClassModel classModel = getClassModel(classname);
239: if (classModel == null) {
240: throw new RuntimeException(
241: /* (non-Javadoc)
242: * @i18n.test
243: * @org-mes="undefined class: " + p[0]
244: */
245: org.openlaszlo.i18n.LaszloMessages.getMessage(
246: ViewSchema.class.getName(), "051018-168",
247: new Object[] { classname }));
248: }
249: AttributeSpec attrspec = classModel.getAttribute(methodName);
250: if (attrspec != null) {
251: if (attrspec.type != METHOD_TYPE) {
252: env.warn("Method named " + methodName + " on class "
253: + classname
254: + " conflicts with attribute with named "
255: + methodName + " and type " + attrspec.type,
256: elt);
257: }
258: }
259:
260: if (!methodOverrideAllowed(classname, methodName)) {
261: env
262: .warn(
263: "Method "
264: + classname
265: + "."
266: + methodName
267: + " is overriding a superclass method"
268: + " of the same name which has been declared non-overridable",
269: elt);
270: }
271: }
272:
273: public String getSuperclassName(String className) {
274: ClassModel model = getClassModel(className);
275: if (model == null)
276: return null;
277: return model.getSuperclassName();
278: }
279:
280: /**
281: * @return the base class of a user defined class
282: */
283: public String getBaseClassname(String className) {
284: String ancestor = getSuperclassName(className);
285: String super class = ancestor;
286:
287: while (ancestor != null) {
288: if (ancestor.equals(className)) {
289: throw new CompilationError(
290: /* (non-Javadoc)
291: * @i18n.test
292: * @org-mes="recursive class definition on " + p[0]
293: */
294: org.openlaszlo.i18n.LaszloMessages.getMessage(
295: ViewSchema.class.getName(), "051018-235",
296: new Object[] { className }));
297: }
298: super class = ancestor;
299: ancestor = getSuperclassName(ancestor);
300: }
301: return super class;
302: }
303:
304: /** Does this class or its ancestors have this attribute declared for it? */
305: AttributeSpec getClassAttribute(String classname, String attrName) {
306: // OK, walk up the superclasses, checking for existence of this attribute
307: ClassModel info = getClassModel(classname);
308: if (info == null) {
309: return null;
310: } else {
311: return info.getAttribute(attrName);
312: }
313: }
314:
315: /**
316: * Add a new element to the attribute type map.
317: *
318: * @param elt the element to add to the map
319: * @param superclassName an element to inherit attribute to type info from. May be null.
320: * @param attributeDefs list of attribute name/type defs
321: */
322: public void addElement(Element elt, String className,
323: String super className, List attributeDefs,
324: CompilationEnvironment env) {
325: ClassModel super class = getClassModel(super className);
326:
327: if (super class == null) {
328: throw new CompilationError(
329: /* (non-Javadoc)
330: * @i18n.test
331: * @org-mes="undefined superclass " + p[0] + " for class " + p[1]
332: */
333: org.openlaszlo.i18n.LaszloMessages.getMessage(
334: ViewSchema.class.getName(), "051018-417",
335: new Object[] { super className, className }));
336: }
337:
338: if (mClassMap.get(className) != null) {
339: String builtin = "builtin ";
340: String also = "";
341: Element other = getClassModel(className).definition;
342: if (other != null) {
343: builtin = "";
344: also = "; also defined at "
345: + Parser.getSourceMessagePathname(other)
346: + ":"
347: + Parser
348: .getSourceLocation(other, Parser.LINENO);
349: }
350: throw new CompilationError(
351: /* (non-Javadoc)
352: * @i18n.test
353: * @org-mes="duplicate class definitions for " + p[0] + p[1] + p[2]
354: */
355: org.openlaszlo.i18n.LaszloMessages.getMessage(
356: ViewSchema.class.getName(), "051018-435",
357: new Object[] { builtin, className, also }), elt);
358: }
359: ClassModel info = new ClassModel(className, super class, this ,
360: elt);
361: mClassMap.put(className, info);
362:
363: if (sInputTextElements.contains(super className)) {
364: info.isInputText = true;
365: info.hasInputText = true;
366: } else {
367: info.isInputText = super class.isInputText;
368: info.hasInputText = super class.hasInputText;
369: }
370:
371: info.supportsTextAttribute = super class.supportsTextAttribute;
372:
373: // Loop over containsElements tags, adding to containment table in classmodel
374: Iterator iterator = elt.getChildren().iterator();
375: while (iterator.hasNext()) {
376: Element child = (Element) iterator.next();
377: if (child.getName().equals("containsElements")) {
378: // look for <element>tagname</element>
379: Iterator iter1 = child.getChildren().iterator();
380: while (iter1.hasNext()) {
381: Element etag = (Element) iter1.next();
382: if (etag.getName().equals("element")) {
383: String tagname = etag.getText();
384: info.addContainsElement(tagname);
385: } else {
386: throw new CompilationError(
387: "containsElement block must only contain <element> tags",
388: etag);
389: }
390: }
391: }
392: }
393:
394: // Add in the attribute declarations
395: addAttributeDefs(elt, className, attributeDefs, env);
396: }
397:
398: /**
399: * Add this list of attribute name/type info to the in-core model of the class definitions.
400: *
401: * @param sourceElement the user's LZX source file element that holds class LZX definition
402: * @param classname the class we are defining
403: * @param attributeDefs list of AttributeSpec attribute info to add to the Schema
404: *
405: */
406: void addAttributeDefs(Element sourceElement, String classname,
407: List attributeDefs, CompilationEnvironment env) {
408: if (!attributeDefs.isEmpty()) {
409: for (Iterator iter = attributeDefs.iterator(); iter
410: .hasNext();) {
411: AttributeSpec attr = (AttributeSpec) iter.next();
412:
413: // If this attribute does not already occur someplace
414: // in an ancestor, then let's add it to the schema.
415: //
416: // While we're here, we need to check that we aren't
417: // redefining an attribute of a parent class with a
418: // different type.
419:
420: Type parentType = null;
421: if (getClassAttribute(classname, attr.name) != null) {
422: // Check that the overriding type is the same as the superclass' type
423: parentType = getAttributeType(classname, attr.name);
424:
425: // Does the parent attribute definition assert override=true?
426: // If so, we're not going to warn if the types mismatch.
427: AttributeSpec parentAttrSpec = getAttributeSpec(
428: classname, attr.name);
429: boolean forceOverride = parentAttrSpec != null
430: && "true".equals(parentAttrSpec.override);
431:
432: if (!forceOverride && (parentType != attr.type)) {
433: // get the parent attribute, so we can see if it says override is allowed
434:
435: env.warn(/* (non-Javadoc)
436: * @i18n.test
437: * @org-mes="In class '" + p[0] + "' attribute '" + p[1] + "' with type '" + p[2] + "' is overriding superclass attribute with same name but different type: " + p[3]
438: */
439: org.openlaszlo.i18n.LaszloMessages.getMessage(
440: ViewSchema.class.getName(),
441: "051018-364", new Object[] { classname,
442: attr.name,
443: attr.type.toString(),
444: parentType.toString() }),
445: sourceElement);
446: }
447: }
448:
449: if (attr.type == ViewSchema.METHOD_TYPE
450: && !("true".equals(attr.override))) {
451: checkMethodDeclaration(sourceElement, classname,
452: attr.name, env);
453: }
454:
455: // Update the in-memory attribute type table
456: setAttributeType(sourceElement, classname, attr.name,
457: attr);
458: }
459: }
460: }
461:
462: public Type getTypeForName(String name) {
463: if (name.equals("text") || name.equals("html"))
464: name = "string";
465: return super .getTypeForName(name);
466: }
467:
468: /** Adds a ClassModel entry into the class table for CLASSNAME. */
469: private void makeNewStaticClass(String classname) {
470: ClassModel info = new ClassModel(classname, this );
471: if (sInputTextElements.contains(classname)) {
472: info.isInputText = true;
473: info.hasInputText = true;
474: }
475: if (mClassMap.get(classname) == null) {
476: mClassMap.put(classname, info);
477: } else {
478: throw new CompilationError(
479: "makeNewStaticClass: `duplicate definition for static class "
480: + classname);
481: }
482: }
483:
484: static Type getAttributeType(String attrName) {
485: return (Type) sAttributeTypes.get(attrName);
486: }
487:
488: /**
489: * Returns a value representing the type of an attribute within an
490: * XML element. Unknown attributes have Expression type.
491: *
492: * @param e an Element
493: * @param attrName an attribute name
494: * @return a value represting the type of the attribute's
495: */
496: public Type getAttributeType(Element e, String attrName) {
497: return getAttributeType(e.getName(), attrName);
498: }
499:
500: /**
501: * Returns a value representing the type of an attribute within an
502: * XML element. Unknown attributes have Expression type.
503: *
504: * @param elt an Element name
505: * @param attrName an attribute name
506: * @return a value represting the type of the attribute's
507: */
508: public Type getAttributeType(String elt, String attrName)
509: throws UnknownAttributeException {
510: String elementName = elt.intern();
511: // +++ This special-case stuff will go away when the RELAX schema
512: // is automatically translated to ViewSchema initialization
513: // code.
514:
515: if (elementName.equals("canvas")) {
516: // override NUMBER_EXPRESSION_TYPE, on view
517: if (attrName.equals("width") || attrName.equals("height")) {
518: return NUMBER_TYPE;
519: }
520: }
521:
522: Type type = null;
523:
524: // Look up attribute in type map for this element
525: ClassModel classModel = getClassModel(elementName);
526:
527: if (classModel != null) {
528: try {
529: type = classModel.getAttributeTypeOrException(attrName);
530: } catch (UnknownAttributeException e) {
531: e.setName(attrName);
532: e.setElementName(elt);
533: throw e;
534: }
535: } else {
536: type = getAttributeType(attrName);
537: if (type == null) {
538: throw new UnknownAttributeException(elt, attrName);
539: //type = EXPRESSION_TYPE;
540: }
541: }
542: return type;
543: }
544:
545: /**
546: * Finds the AttributeSpec definition of an attribute, on a class
547: * or by searching up it's parent class chain.
548: *
549: * @param elt an Element name
550: * @param attrName an attribute name
551: * @return the AttributeSpec or null
552: */
553: public AttributeSpec getAttributeSpec(String elt, String attrName) {
554: String elementName = elt.intern();
555:
556: // Look up attribute in type map for this element
557: ClassModel classModel = getClassModel(elementName);
558:
559: if (classModel != null) {
560: return classModel.getAttribute(attrName);
561: } else {
562: return null;
563: }
564: }
565:
566: /**
567: * checks whether a method with a given method is allowed to be overridden
568: * @param elt an Element name
569: * @param methodName a method name
570: * @return boolean if the method exists on the class or superclass
571: */
572: public boolean methodOverrideAllowed(String classname,
573: String methodName) {
574: AttributeSpec methodspec = getClassAttribute(classname,
575: methodName);
576: if (methodspec == null) {
577: return true;
578: } else {
579: return !("false".equals(methodspec.override));
580: }
581: }
582:
583: boolean isMouseEventAttribute(String name) {
584: return sMouseEventAttributes.contains(name);
585: }
586:
587: ClassModel getClassModel(String elementName) {
588: return (ClassModel) mClassMap.get(elementName);
589: }
590:
591: public String toLZX() {
592: return toLZX("");
593: }
594:
595: public String toLZX(String indent) {
596: String lzx = "";
597: for (Iterator i = (new TreeSet(mClassMap.values())).iterator(); i
598: .hasNext();) {
599: ClassModel model = (ClassModel) i.next();
600: if (model.hasNodeModel()) {
601: lzx += model.toLZX(indent);
602: lzx += "\n";
603: }
604: }
605: return lzx;
606: }
607:
608: public void loadSchema(CompilationEnvironment env)
609: throws JDOMException, IOException {
610: String schemaPath = SCHEMA_PATH;
611: // Load the schema if it hasn't been.
612: // Reload it if it's been touched, to make it easier for developers.
613: while (sCachedSchemaDOM == null
614: || new File(schemaPath).lastModified() != sCachedSchemaLastModified) {
615: // using 'while' and reading the timestamp *first* avoids a
616: // race condition --- although since this doesn't happen in
617: // production code, this isn't critical
618: sCachedSchemaLastModified = new File(schemaPath)
619: .lastModified();
620: sCachedSchemaDOM = new Parser().read(new File(schemaPath));
621: }
622:
623: // This is the base class from which all classes derive unless otherwise
624: // specified. It has no attributes.
625: makeNewStaticClass("Object");
626:
627: schemaDOM = (Document) sCachedSchemaDOM.clone();
628: Element docroot = schemaDOM.getRootElement();
629: ToplevelCompiler ec = (ToplevelCompiler) Compiler
630: .getElementCompiler(docroot, env);
631: Set visited = new HashSet();
632: ec.updateSchema(docroot, this , visited);
633: /** From here on, user-defined classes must not use reserved javascript identifiers */
634: this .enforceValidIdentifier = true;
635: }
636:
637: /** Check if a child element can legally be contained in a parent element.
638: This works with the class hierarchy as follows:
639:
640: + Look up the ClassModel corresponding to the parentTag
641:
642: + Check the containsElements table to see if child tag is in there
643:
644: + If not, look up the ClassModel of the child tag, and follow up the chain
645: checking if that name is present in the table
646:
647:
648: + If not, ascend up the parent classmodel, and call canContainElement recursively
649:
650: */
651: public boolean canContainElement(String parentTag, String childTag) {
652: // Get list of legally nestable tags
653: ClassModel parent = getClassModel(parentTag);
654:
655: // TODO [hqm 2007-09]: CHECK FOR NULL HERE
656:
657: Set tagset = parent.getContainsSet();
658: if (tagset.contains(childTag)) {
659: return true;
660: }
661: // check all superclasses of the childTag
662: ClassModel childclass = getClassModel(childTag);
663:
664: // TODO [hqm 2007-09]: CHECK FOR NULL HERE
665:
666: while (childclass != null) {
667: String super classname = childclass.getSuperclassName();
668: if (tagset.contains(super classname)) {
669: return true;
670: }
671: childclass = childclass.getSuperclassModel();
672: }
673:
674: String parentSuperclassname = parent.getSuperclassName();
675: if (parentSuperclassname != null) {
676: return canContainElement(parentSuperclassname, childTag);
677: }
678:
679: return false;
680: }
681:
682: /** @return true if this element is an input text field */
683: boolean isInputTextElement(Element e) {
684: String classname = e.getName();
685: ClassModel info = getClassModel(classname);
686: if (info != null) {
687: return info.isInputText;
688: }
689: return sInputTextElements.contains(classname);
690: }
691:
692: boolean supportsTextAttribute(Element e) {
693: String classname = e.getName();
694: ClassModel info = getClassModel(classname);
695: if (info != null) {
696: return info.supportsTextAttribute;
697: } else {
698: return false;
699: }
700: }
701:
702: /** @return true if this element content is interpreted as text */
703: boolean hasTextContent(Element e) {
704: // input text elements can have text
705: return isInputTextElement(e) || supportsTextAttribute(e);
706: }
707:
708: /** @return true if this element's content is HTML. */
709: boolean hasHTMLContent(Element e) {
710: String name = e.getName().intern();
711: // TBD: return sHTMLContentElements.contains(name). Currently
712: // uses a blacklist instead of a whitelist because the parser
713: // doesn't tell the schema about user-defined classes.
714: // XXX Since any view can have a text body, this implementation
715: // is actually correct. That is, blacklist rather than whitelist
716: // is the way to go here.
717: return name != "class" && name != "method"
718: && name != "property" && name != "script"
719: && name != "attribute" && !isHTMLElement(e)
720: && !isInputTextElement(e);
721: }
722:
723: /** @return true if this element is an HTML element, that should
724: * be included in the text content of an element that tests true
725: * with hasHTMLContent. */
726: static boolean isHTMLElement(Element e) {
727: String name = e.getName();
728: return name.equals("a") || name.equals("b")
729: || name.equals("img") || name.equals("br")
730: || name.equals("font") || name.equals("i")
731: || name.equals("p") || name.equals("pre")
732: || name.equals("u");
733: }
734:
735: static boolean isDocElement(Element e) {
736: String name = e.getName();
737: return name.equals("doc");
738: }
739:
740: /* Constants for parsing CSS colors. */
741: static final PatternMatcher sMatcher = new Perl5Matcher();
742: static final Pattern sRGBPattern;
743: static final Pattern sHex3Pattern;
744: static final Pattern sHex6Pattern;
745: static final HashMap sColorValues = new HashMap();
746: static {
747: try {
748: Perl5Compiler compiler = new Perl5Compiler();
749: String s = "\\s*(-?\\d+(?:(?:.\\d*)%)?)\\s*"; // component
750: String hexDigit = "[0-9a-fA-F]";
751: String hexByte = hexDigit + hexDigit;
752: sRGBPattern = compiler.compile("\\s*rgb\\(" + s + "," + s
753: + "," + s + "\\)\\s*");
754: sHex3Pattern = compiler.compile("\\s*#\\s*(" + hexDigit
755: + hexDigit + hexDigit + ")\\s*");
756: sHex6Pattern = compiler.compile("\\s*#\\s*(" + hexByte
757: + hexByte + hexByte + ")\\s*");
758: } catch (MalformedPatternException e) {
759: throw new ChainedException(e);
760: }
761:
762: String[] colorNameValues = { "black", "000000", "green",
763: "008000", "silver", "C0C0C0", "lime", "00FF00", "gray",
764: "808080", "olive", "808000", "white", "FFFFFF",
765: "yellow", "FFFF00", "maroon", "800000", "navy",
766: "000080", "red", "FF0000", "blue", "0000FF", "purple",
767: "800080", "teal", "008080", "fuchsia", "FF00FF",
768: "aqua", "00FFFF" };
769: for (int i = 0; i < colorNameValues.length;) {
770: String name = colorNameValues[i++];
771: String value = colorNameValues[i++];
772: sColorValues.put(name, new Integer(Integer.parseInt(value,
773: 16)));
774: }
775: }
776:
777: static class ColorFormatException extends RuntimeException {
778: ColorFormatException(String message) {
779: super (message);
780: }
781: }
782:
783: /** Parse according to http://www.w3.org/TR/2001/WD-css3-color-20010305,
784: * but also allow 0xXXXXXX */
785: public static int parseColor(String str) {
786: {
787: Object value = sColorValues.get(str);
788: if (value != null) {
789: return ((Integer) value).intValue();
790: }
791: }
792: if (str.startsWith("0x")) {
793: try {
794: return Integer.parseInt(str.substring(2), 16);
795: } catch (java.lang.NumberFormatException e) {
796: // fall through
797: }
798: }
799: if (sMatcher.matches(str, sHex3Pattern)) {
800: int r1g1b1 = Integer.parseInt(sMatcher.getMatch().group(1),
801: 16);
802: int r = (r1g1b1 >> 8) * 17;
803: int g = ((r1g1b1 >> 4) & 0xf) * 17;
804: int b = (r1g1b1 & 0xf) * 17;
805: return (r << 16) + (g << 8) + b;
806: }
807: if (sMatcher.matches(str, sHex6Pattern)) {
808: return Integer.parseInt(sMatcher.getMatch().group(1), 16);
809: }
810: if (sMatcher.matches(str, sRGBPattern)) {
811: int v = 0;
812: for (int i = 0; i < 3; i++) {
813: String s = sMatcher.getMatch().group(i + 1);
814: int c;
815: if (s.charAt(s.length() - 1) == '%') {
816: s = s.substring(0, s.length() - 1);
817: float f = Float.parseFloat(s);
818: c = (int) f * 255 / 100;
819: } else {
820: c = Integer.parseInt(s);
821: }
822: if (c < 0)
823: c = 0;
824: if (c > 255)
825: c = 255;
826: v = (v << 8) | c;
827: }
828: return v;
829: }
830: throw new ColorFormatException(str);
831: }
832: }
|