Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: echopoint/stylesheet/CssStyleSheet.java


1   package echopoint.stylesheet;
2   
3   /* 
4    * This file is part of the Echo Point Project.  This project is a collection
5    * of Components that have extended the Echo Web Application Framework.
6    *
7    * EchoPoint is free software; you can redistribute it and/or modify
8    * it under the terms of the GNU Lesser General Public License as published by
9    * the Free Software Foundation; either version 2 of the License, or
10   * (at your option) any later version.
11   *
12   * EchoPoint is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU Lesser General Public License for more details.
16   *
17   * You should have received a copy of the GNU Lesser General Public License
18   * along with Echo Point; if not, write to the Free Software
19   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20   */
21  
22  import java.beans.IntrospectionException;
23  import java.beans.PropertyChangeEvent;
24  import java.beans.PropertyChangeListener;
25  import java.io.File;
26  import java.io.Reader;
27  import java.net.URL;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.Iterator;
31  import java.util.List;
32  
33  import nextapp.echo.Component;
34  import nextapp.echo.EchoInstance;
35  import nextapp.echo.Style;
36  import nextapp.echo.Window;
37  
38  import echopoint.util.IdKit;
39  
40  
41  /**
42   * The <code>CssStyleSheet</code> class implements a StyleSheet for 
43   * Echo derived Components.
44   * <p>
45   * A StyleSheet is a collection of style attributes that can be applied 
46   * to a Component and any of its children.  A StyleSheet object can 
47   * listen for when child Components are added, and apply the StyleSheet
48   * to those child components
49   * <p>
50   * CssStyleSheet style data can be loaded from external files or other
51   * reader data sources.  The format of the CssStyleSheet data is based
52   * on w3c Cascading Style Sheets, and has been made to look 'CSS like'.
53   * (although pure CSS files cannot be used.)
54   * <p>
55   * A CssStyleSheet file contain style instructions for a Component class.  Lower
56   * level class names can be used.  For example the 'font" attribute may be
57   * set for the class nextapp.echo.Component to Verdana.  This means that 
58   * all derived Components will have the Verdana font style applied to them 
59   * unless they have an entry for a more specific class.
60   * <p>
61   * A map of all styles and classes is kept and when a given component is to
62   * have a CssStyleSheet applied to it, the application is done from least 
63   * specific Class to most specific Class.
64   * <p>
65   * For example if a CssStyleSheet is applied to a PasswordField object, then a style 
66   * will be searched first for the Component class, then the TextComponent 
67   * class, the TextField class and then finally the PasswordField class.
68   * <p>
69   * This application of styles allows "cascading" style values to be 
70   * applied to a given Component object.  The more specific StyleSheet entries will
71   * overrride any previous entries.
72   * <p>
73   * CssStyleSheet data has a 'CSS like' format of :<br>
74   * <blockquote><pre>
75   * className1 [,classNameN]  {         <br>
76   *    attributeName1 : attributeValue2;    <br>
77   *    ...                  <br>    
78   *    [attributeNameN : attributeValueN;]  <br>
79   * }                    <br>
80   * </pre></blockquote>
81   * <p>
82   * Multiple class names can be specified for the one entry.  The class names 
83   * are case senstive however the style attribute names and values are not.  
84   * Quotes can be used at any time within the entries. 
85   * <p>
86   * For example :<br>
87   * <blockquote><pre>
88   * // comments allowed             
89   * echopoint.DatePicker  {           
90   *    foreground : #FF00CC;          
91   *    background : color(AA,99,CC);      
92   *    rolloverEnabled : true;        
93   *    font : 'sans serif',bold,12;        
94   *    calendarFont : font(verdana,italic,16)   
95   * }
96   * </pre></blockquote>
97   * <p>
98   * C and C++ style comments (slash slash or slash asterisk) can be used 
99   * anywhere within the CssStyleSheet data
100  * 
101  * <p>
102  * The className can be in three forms : 
103  * <ul>
104  * <li>className</li>
105  * <li>className!groupName</li>
106  * <li>className#id</li>
107  * </ul>
108  * <p>
109  * Using groupName allows you to "logically" group a number of components under 
110  * the one name.  You do this by setting using an attributed identifier of
111  * "stylegroup=xxx", where xxx equals groupName. 
112  * <p>
113  * Using id, will cause the styles to be applied to Components of a certain class
114  * that have the specified identifier or an attributed identifier of
115  * "style=xxx", where xxx equals id.
116  * <p>
117  * For more information on attributed identifiers, see {@link echopoint.util.IdKit echopoint.util.IdKit}
118  * <p>
119  * For example :<br>
120  * <blockquote><pre>
121  * echopoint.PushButton  {          
122  *    foreground : #FF00CC;          
123  *    background : color(AA,99,CC);      
124  * }                    
125  * <br>
126  * echopoint.PushButton!large  {      
127  *    font : 'sans serif',bold,24;        
128  * }
129  * <br>
130  * echopoint.PushButton!small {        
131  *    font : 'verdana',plain,8;          
132  * }
133  * echopoint.PushButton#pbId  {        
134  *    font : 'serif',bold,12;          
135  * }
136  * <br>
137  * </pre></blockquote>
138  * <p>
139  * The ordering of application of styles is : className, className and groupName, 
140  * className and finally id.
141  * <p>
142  * Use the CssStyleSheet.getInstance() to create new StyleSheet objects.  This method 
143  * will return a CssStyleSheet with handlers for both Echo and EchoPoint components.
144  * <p>
145  * The CssStyleSheet.getEmptyInstance() method returns a CssStyleSheet object with no
146  * CssStyleSheetHandlers within it.  You would rarely use this method however.
147  * <p>
148  * If a Component class has a StyleInfo support class associated with it
149  * then the CssStyleSheet will invoke it to find out the class for values of a given
150  * style attribute name.  This ensures that Style objects are created safely, with little chance
151  * of a ClassCastException when applied.  This also allows case insensitive CCS attribute
152  * names to be turned into case sensitive Style attribute names.
153  * <p>
154  * Internally a SmartStyle object is built with all of the information in the style sheet.
155  * This is then applied to the Component via the Component.applyStyle() method.
156  * If any of the style attributes are not set during the applyStyle() method, then
157  * relection is used to find "setter" methods in the Component and this 
158  * is used to set values.
159  * <p> 
160  * NOTE : that this StyleInfo is optional, and if not found then the CssStyleSheet will make an
161  * intelligent guess about what class of object is required for a given attribute name.
162  * <p>
163  * @see echopoint.stylesheet.StyleSheetIntrospector
164  * @see echopoint.stylesheet.StyleInfo
165  * @see echopoint.stylesheet.SmartStyle
166  * @see echopoint.util.IdKit
167  * 
168  */
169 public class CssStyleSheet implements StyleSheet, PropertyChangeListener, java.io.Serializable {
170 
171   /* a class to hold style information */
172   private class StyleEntry implements java.io.Serializable, Comparable {
173     private Class clazz;
174     private int lineNo;
175     private String name;
176     private SmartStyle style;
177     
178     
179     /**
180      * @see java.lang.Comparable#compareTo(java.lang.Object)
181      */
182     public int compareTo(Object o) {
183       Class c1 = (Class) this.clazz;
184       Class c2 = ((StyleEntry)o).clazz;
185       
186       if (c1.equals(c2)) {
187         return 0;
188       } else if (c1.isAssignableFrom(c2)) {
189         return -1;
190       } else {
191         return c1.getName().compareTo(c2.getName());
192       }
193     }
194 
195     public String toString() {
196       return name + " : " + clazz.getName() + " : " + style.toString() + " line : " + lineNo;
197     }
198 
199     public Class getClazz() {
200       return clazz;
201     }
202 
203     public int getLineNo() {
204       return lineNo;
205     }
206 
207     public String getName() {
208       return name;
209     }
210 
211     public SmartStyle getStyle() {
212       return style;
213     }
214 
215     public void setClazz(Class class1) {
216       clazz = class1;
217     }
218 
219     public void setLineNo(int i) {
220       lineNo = i;
221     }
222 
223     public void setName(String string) {
224       name = string;
225     }
226 
227     public void setStyle(SmartStyle style) {
228       this.style = style;
229     }
230 
231   }
232   private ArrayList exceptionList = new ArrayList();
233   private boolean failOnInvalidAttributes = true;
234   private ArrayList groupList = new ArrayList();
235 
236   private ArrayList handlerList = new ArrayList();
237   private boolean hasParsingOccurred = false;
238   private ArrayList idList = new ArrayList();
239   private HashMap styleMap = new HashMap();
240   private ArrayList styleSheetGroupList = new ArrayList();
241   private boolean parseable = false;
242   
243   /**
244    * CssStyleSheet constructor
245    */
246   private CssStyleSheet() {
247     super();
248     // need StyleInfo support for the Echo classes
249     new CssEchoStyleSheetHandler();
250   }
251   /**
252    * Tries to apply a style to a component.  
253    * <p>
254    * SmartStyles are used so that attributes that have not
255    * been accessed can still be applied via reflection
256    * <p>
257    * It catches any exceptions and rethrows them 
258    * as StyleSheetInvalidValueException's 
259    */
260   private void _applyStyle(Component c, StyleEntry entry) {
261     try {
262       SmartStyle style = entry.getStyle(); 
263       c.applyStyle(style);
264       //
265       // use reflection to set an style attributes that
266       // might not be used via applyStyle();
267       //
268       style.setUnAccessedAttributes(c);
269     } catch (Exception e) {
270       String msg = "Style Failure : Component class : '" + c.getClass().getName() + "' CSS style Name : '" + entry.getName() + "'";
271       if (e.getMessage() != null)
272         msg += e.getMessage();
273       throw new StyleSheetInvalidValueException(entry.getLineNo(), msg, e);
274     }
275   }
276   /**
277    * Recursively applies the StyleSheet to a Component and its chidren
278    */
279   private void _applyStyleSheetRecursively(Component c) {
280     _applyStyleSheetToComponent(c);
281 
282     Component children[] = c.getComponents();
283     for (int i = 0; i < children.length; i++) {
284       _applyStyleSheetRecursively(children[i]);
285     }
286   }
287   /**
288    * This applyes a style to an Component.  It can be via class
289    * and/or via StyleSheetGroup and/or via component identifier (if present).
290    * <p>
291    * The style application is done in least specific class order. e.g.
292    * Component/AbstractButton/Button/PushButton
293    * <p>
294    * If any Exceptions are encountered during Style application, such as ClassCastExceptions,
295    * then they are caught and rethrown as StyleSheetInvalidValueExceptions
296    * 
297    * @deprecated StyleSheetGroup has been deprecated and will be removed in the next version
298    * of EchoPoint.  Use the new attributed identifier on the Component from now on, 
299    * eg component.setIdentifier("stylegroup=xxx;");
300    */
301   private void _applyStyleSheetToComponent(Component c) {
302 
303     Class classes[] = getClassesLeastSpecific(c.getClass());
304 
305     // apply styles in least specific class order, wich allows
306     // future entries to override their parents.
307     for (int i = 0; i < classes.length; i++) {
308       Class currentClazz = classes[i];
309       if (currentClazz.equals(Object.class))
310         continue;
311 
312       StyleEntry entry = (StyleEntry) styleMap.get(currentClazz);
313       if (entry != null)
314         _applyStyle(c, entry);
315     }
316 
317     // now try and find a grouping entry for the component
318     // We now use thew new attributed identifier method
319     // of setting group values
320     
321     for (int i = 0; i < groupList.size(); i++) {
322       StyleEntry entry = (StyleEntry) groupList.get(i);
323       //
324       // use new method first
325       Object identifier = c.getIdentifier();
326       if (identifier != null) {
327         Object groupName = IdKit.getAttribute(identifier,"stylegroup");
328         if (entry.name.equals(groupName) && entry.style != null) {
329           _applyStyle(c, entry);
330         }
331       }
332       //
333       // This needs to be removed eventually!
334       StyleSheetGroup ssGroup = _findStyleSheetGroup(entry.name);
335       if (ssGroup != null && ssGroup.contains(c) && entry.clazz.isAssignableFrom(c.getClass())) {
336         if (entry.style != null)
337           _applyStyle(c, entry);
338       }
339     }
340 
341     // now try and find an id entry for the component
342     if (c.getIdentifier() != null) {
343       for (int i = 0; i < idList.size(); i++) {
344         StyleEntry entry = (StyleEntry) idList.get(i);
345         if (entry.clazz.isAssignableFrom(c.getClass())) {
346           
347           Object id = IdKit.getAttribute(c.getIdentifier(),"style");
348           if (id == null)
349             id = c.getIdentifier();
350             
351           if (entry.name.equals(id) && entry.style != null) { 
352             _applyStyle(c, entry);
353           }
354         }
355       }
356     }
357   }
358   /**
359    * This method finds the Class object for a given name.  It also does some simple subsitution 
360    * to find inner classe names which must be loaded with the xx.yyy$InnerName syntax.
361    */
362   private Class _findClassFromName(String className) throws ClassNotFoundException {
363     ClassNotFoundException cnfe = null;
364     Class clazz = null;
365     try {
366       clazz = Class.forName(className);
367       return clazz;
368     } catch (ClassNotFoundException firstE) {
369       cnfe = firstE;
370     }
371 
372     //
373     // if they have excplicitly names the inner class then go no further
374     if (className.indexOf('$') != -1)
375       throw cnfe;
376 
377     //
378     // okay run through the class name and substitue . chars for $ chars starting
379     // from the back.  This will try and load inner classes
380     StringBuffer sb = new StringBuffer(className);
381     int index = className.lastIndexOf('.');
382     while (index != -1) {
383       sb = sb.replace(index, index + 1, "$");
384       try {
385         clazz = Class.forName(sb.toString());
386         return clazz;
387       } catch (ClassNotFoundException firstE) {
388       }
389 
390       index = sb.toString().lastIndexOf('.');
391     }
392     throw cnfe;
393   }
394   /**
395    * @deprecated StyleSheetGroup has been deprecated and will be removed in the next version
396    * of EchoPoint.  Use the new attributed identifier on the Component from now on, 
397    * eg component.setIdentifier("stylegroup=xxx;");
398    */
399   private StyleSheetGroup _findStyleSheetGroup(String name) {
400     for (int i = 0; i < styleSheetGroupList.size(); i++) {
401       StyleSheetGroup group = (StyleSheetGroup) styleSheetGroupList.get(i);
402       if (group.getName().equals(name)) {
403         return group;
404       }
405     }
406     return null;
407   }
408   /**
409    * This will try and set a value into a Style if it really is of the given type
410    * valueClass!  It returns false if the value cannot be set.
411    */
412   private boolean _magicSetStyleAttribute(
413     Style style,
414     Class componentClass,
415     StyleAttrDescriptor descriptor,
416     String attrName,
417     String attrValue) {
418 
419     Class valueClass = descriptor.getType();
420 
421     // try and match any symbolic constants first
422     SymbolicValue[] symbolics = descriptor.getSymbolicValues();
423     if (symbolics != null) {
424       for (int i = 0; i < symbolics.length; i++) {
425         if (symbolics[i].matches(attrValue)) {
426           Object val = symbolics[i].getValue();
427           style.setAttribute(descriptor.getName(), val);
428           return true;
429         }
430       }
431     }
432 
433     if (valueClass.equals(nextapp.echo.Color.class))
434       return CssStyleSheetHelper.setColorAttribute(style, attrName, attrValue);
435 
436     if (valueClass.equals(nextapp.echo.Font.class))
437       return CssStyleSheetHelper.setFontAttribute(style, attrName, attrValue);
438 
439     if (valueClass.equals(nextapp.echo.Insets.class))
440       return CssStyleSheetHelper.setInsetsAttribute(style, attrName, attrValue);
441 
442     if (valueClass.equals(nextapp.echo.ImageReference.class))
443       return CssStyleSheetHelper.setImageAttribute(style, attrName, attrValue);
444 
445     if (valueClass.equals(echopoint.CornerImages.class))
446       return CssStyleSheetHelper.setCornerImagesAttribute(style, attrName, attrValue);
447 
448     if (valueClass.equals(echopoint.positionable.ClipRect.class))
449       return CssStyleSheetHelper.setClipRectAttribute(style, attrName, attrValue);
450 
451     // boolean
452     if (valueClass.equals(Boolean.class) || valueClass.equals(Boolean.TYPE))
453       return CssStyleSheetHelper.setBooleanAttribute(style, attrName, attrValue);
454 
455     // integer
456     if (valueClass.equals(Integer.class) || valueClass.equals(Integer.TYPE) || valueClass.equals(Long.class) || valueClass.equals(Long.TYPE)) {
457       return CssStyleSheetHelper.setIntegerAttribute(style, attrName, attrValue);
458     }
459     // string
460     if (valueClass.equals(String.class))
461       return CssStyleSheetHelper.setStringAttribute(style, attrName, attrValue);
462 
463     Class ct  = valueClass.getComponentType(); 
464     if ((ct == Integer.class || ct == Integer.TYPE) && valueClass.isArray())
465       return CssStyleSheetHelper.setIntegerArrayAttribute(style,attrName, attrValue);
466 
467     // we we dont know about it.  What about the handlers do they 
468     // know how to set it
469     boolean bHandled = false;
470     for (int j = 0; j < handlerList.size(); j++) {
471       CssStyleSheetHandler handler = (CssStyleSheetHandler) handlerList.get(j);
472       bHandled = handler.setKnownStyleAttribute(componentClass, valueClass, style, attrName, attrValue);
473       if (bHandled)
474         break;
475     }
476     return bHandled;
477   }
478   
479   /**
480    * Checks that the StyleInfo knows about the attr name.
481    */
482   private StyleAttrDescriptor _magicStyleFindDescriptor(StyleInfo styleInfo, String attrName) {
483     if (styleInfo == null)
484       return null;
485     StyleAttrDescriptor[] descriptors = styleInfo.getStyleDescriptors();
486     for (int i = 0; i < descriptors.length; i++) {
487       StyleAttrDescriptor descriptor = descriptors[i];
488       if (descriptor.getName().equalsIgnoreCase(attrName)) {
489         return descriptor;
490       }
491     }    
492     return null;
493   }
494   
495   /**
496    * Check with the component class whether it supports the magic StyleInfo 
497    * If yes it determines if the attrName is okay and the value
498    * can be set into a Style for the component class.  If not it returns false.
499    */
500   private boolean _magicStyleInfoSupport(CssStyleSheetParser.AttrEntry attrEntry, Class componentClass, Style style)
501     throws StyleSheetParseException {
502 
503     String attrName = attrEntry.attrName;
504     StyleAttrDescriptor theDescriptor = null;
505     StyleInfo info = null;
506     
507     try {
508       info = StyleSheetIntrospector.getStyleInfo(componentClass);
509       theDescriptor = _magicStyleFindDescriptor(info,attrName);
510     } catch (IntrospectionException e) {;}
511     
512     // finally ask the handlers    
513     if (theDescriptor == null) {
514       // ask each of the handlers in turn if they have 
515       // StyleInfo for a given componentClass 
516       for (int j = 0; j < handlerList.size(); j++) {
517         CssStyleSheetHandler handler = (CssStyleSheetHandler) handlerList.get(j);
518         info = handler.getStyleInfo(componentClass);
519         theDescriptor = _magicStyleFindDescriptor(info,attrName);
520         if (theDescriptor != null)
521           break;
522       }
523     }
524     if (theDescriptor != null) {
525       //
526       // the Component class knows about the attribute name.  Can it be parsed 
527       // into one of those class of values and set into the style??
528       boolean isSettable = _magicSetStyleAttribute(style, componentClass, theDescriptor, attrName, attrEntry.attrValue);
529       if (! isSettable) {
530         //
531         // the attrName is known however the attr value is not the right type
532         _throwTPE(
533           attrEntry.lineNo,
534           "Style attribute '"
535             + attrEntry.attrName
536             + "' in class '"
537             //+ componentClass.getName()
538             + attrEntry.className
539             + "' has an invalid value '"
540             + attrEntry.attrValue);
541       }
542       return true;
543     } else {
544       // we dont know about it for this component class
545       return false;
546     }
547   }
548   /**
549    * Processes a style attribute and make sure it can be set okay
550    */
551   private void _processStyleAttribute(CssStyleSheetParser.AttrEntry attrEntry, Class classes[], Class componentClass, Style style)
552     throws StyleSheetParseException {
553 
554     CssStyleSheetHandler handler;
555 
556     String attrName = attrEntry.attrName;
557     String attrValue = attrEntry.attrValue.trim();
558 
559     boolean bHandled = false;
560     for (int i = 0; i < classes.length; i++) {
561       Class clazz = classes[i];
562       // skip Object
563       if (clazz.equals(Object.class))
564         continue;
565       //
566       // check to see if the Component class has the magic style sheet support
567       // and if so, then use it.
568       bHandled = _magicStyleInfoSupport(attrEntry, clazz, style);
569       //
570       // call the handler class to try and set the style attribute for the current 
571       // class
572       if (!bHandled) {
573         for (int j = 0; j < handlerList.size(); j++) {
574           handler = (CssStyleSheetHandler) handlerList.get(j);
575           bHandled = handler.setUnknownStyleAttribute(clazz, style, attrName, attrValue);
576           if (bHandled)
577             break;
578         }
579       }
580       // 
581       // if its still not handled, then call out to the handlers
582       // to parse an Object value.
583       //
584       if (!bHandled) {
585         for (int j = 0; j < handlerList.size(); j++) {
586           handler = (CssStyleSheetHandler) handlerList.get(j);
587           Object value = handler.parseUnknownStyleValue(clazz, attrName, attrValue);
588           if (value != null) {
589             bHandled = true;
590             style.setAttribute(attrName,value);
591             break;
592           }
593         }
594       }
595       // if its been handled then no need to go around any further!
596       if (bHandled) {
597         return;
598       }
599     }
600     //
601     // now the none of the component classes have magic support or they have it but dont know 
602     // about the attribute name, so we should guess.  In a certain order of course...
603     //
604     bHandled = bHandled || CssStyleSheetHelper.setColorAttribute(style, attrName, attrValue);
605     bHandled = bHandled || CssStyleSheetHelper.setFontAttribute(style, attrName, attrValue);
606     bHandled = bHandled || CssStyleSheetHelper.setInsetsAttribute(style, attrName, attrValue);
607     bHandled = bHandled || CssStyleSheetHelper.setImageAttribute(style, attrName, attrValue);
608     bHandled = bHandled || CssStyleSheetHelper.setCornerImagesAttribute(style, attrName, attrValue);
609     bHandled = bHandled || CssStyleSheetHelper.setClipRectAttribute(style, attrName, attrValue);
610     bHandled = bHandled || CssStyleSheetHelper.setIntegerArrayAttribute(style, attrName, attrValue);
611     
612     //
613     // okay  if we still havent handled it okay we need to try some of the more general cases
614     // such as boolean, integers and strings
615     //
616 
617     // boolean
618     if (attrValue.equalsIgnoreCase("true") || attrValue.equalsIgnoreCase("false"))
619       bHandled = bHandled || CssStyleSheetHelper.setBooleanAttribute(style, attrName, attrValue);
620     // integer
621     if (CssStyleSheetHelper.isInteger(attrValue))
622       bHandled = bHandled || CssStyleSheetHelper.setIntegerAttribute(style, attrName, attrValue);
623 
624     //
625     // not one of the well known object types so again
626     // give the handlers another chance to convert the value
627     // without much context.
628     //    
629     if (!bHandled) {
630       for (int j = 0; j < handlerList.size(); j++) {
631         handler = (CssStyleSheetHandler) handlerList.get(j);
632         Object value = handler.parseUnknownStyleValue(attrValue);
633         if (value != null) {
634           bHandled = true;
635           style.setAttribute(attrName,value);
636           break;
637         }
638       }
639     }
640     
641     //
642     // finally treat it as a simple string, 
643     // but only if it is not in obj(n,n) form
644     //
645     if (! _isObjValue(attrValue)) {
646       //
647       // The string value may be surrounded by quotes ' or "
648       // in which case we need to remove them
649       //
650       int last = attrValue.length()-1;
651       if (_isQuotedValue(attrValue))
652         attrValue = attrValue.substring(1,last);
653         
654       bHandled = bHandled || CssStyleSheetHelper.setStringAttribute(style, attrName, attrValue);
655     }
656 
657     if (!bHandled) {
658       //
659       // no one handled the attribute at all. BLAM!
660       //
661       _throwTPE(
662         attrEntry.lineNo,
663         "Unhandled style attribute '"
664           + attrName
665           + "' in class '"
666           //+ componentClass.getName()
667           + attrEntry.className
668           + "' with value '"
669           + attrValue);
670     }
671   }
672   
673   /**
674    * processes style sheet data
675    */
676   private void _processStyleSheet(Reader styleSheetReader) throws StyleSheetParseException {
677     CssStyleSheetHandler handler;
678 
679     parseable = false;
680     // clear all our lists
681     exceptionList.clear();
682     idList.clear();
683     groupList.clear();
684     styleMap.clear();
685 
686     CssStyleSheetParser parser = new CssStyleSheetParser();
687 
688     // and parse...and parse...and parse
689     try {
690       parser.parse(styleSheetReader);
691     } catch (StyleSheetParseException sse) {
692       exceptionList.add(sse);
693       throw sse;
694     }
695 
696     // go through all the class entries and process them
697     List classList = parser.getClassEntries();
698     for (int e = 0; e < classList.size(); e++) {
699       CssStyleSheetParser.ClassEntry classEntry = (CssStyleSheetParser.ClassEntry) classList.get(e);
700 
701       // try and load a class file for the entry
702       String className = classEntry.className;
703       Class clazz = null;
704       try {
705         // if we dont have a fully qualified class name then ask each 
706         // handler in turn until we do.  We do this in the order in which
707         // the handlers where added.
708         if (className.indexOf('.') == -1) {
709           String fullQualifiedClassName;
710           for (int h = 0; h < handlerList.size(); h++) {
711             handler = (CssStyleSheetHandler) handlerList.get(h);
712             fullQualifiedClassName = handler.getFullQualifiedClassName(className);
713             if (fullQualifiedClassName != null) {
714               className = fullQualifiedClassName;
715               break;
716             }
717           }
718         }
719         clazz = _findClassFromName(className);
720       } catch (ClassNotFoundException cnfe) {
721         StyleSheetParseException sse = new StyleSheetParseException(classEntry.lineNo, cnfe, "Style Failure : " + cnfe.toString());
722         exceptionList.add(sse);
723         if (isExceptionParseFailure())
724           throw sse;
725         // this class is a dud...next class please
726         continue;
727 
728       }
729 
730       //
731       // make sure the class name is derived from nextapp.echo.Component.  We cant 
732       // help them otherwise
733       if (!nextapp.echo.Component.class.isAssignableFrom(clazz)) {
734         StyleSheetParseException sse =
735           new StyleSheetParseException(
736             classEntry.lineNo,
737             null,
738             "Style Failure : The class '" + clazz.getName() + "' is not derived from nextapp.echo.Component");
739         exceptionList.add(sse);
740         if (isExceptionParseFailure())
741           throw sse;
742         // this class is a dud...next class please
743         continue;
744       }
745 
746       // ask the component classes about each attribute starting from the most
747       // specific first.  Note that this is opposite order in which we apply
748       // styles.
749       Class classes[] = getClassesMostSpecific(clazz);
750 
751       //
752       // parse all the style attributes and place them into a 
753       // SmartStyle
754       //
755       SmartStyle style = new SmartStyle();
756       List attrList = classEntry.listAttrEntries;
757       for (int a = 0; a < attrList.size(); a++) {
758         CssStyleSheetParser.AttrEntry attrEntry = (CssStyleSheetParser.AttrEntry) attrList.get(a);
759         _processStyleAttribute(attrEntry, classes, clazz, style);
760       }
761 
762       // save the new created style for later application
763       StyleEntry styleEntry = new StyleEntry();
764       styleEntry.setLineNo(classEntry.lineNo);
765       styleEntry.setClazz(clazz);
766       styleEntry.setStyle(style);
767 
768       if (classEntry.id == null && classEntry.grouping == null) {
769         styleEntry.setName(clazz.getName());
770         styleMap.put(clazz, styleEntry);
771       } else {
772         if (classEntry.id != null) {
773           styleEntry.setName(classEntry.id);
774           idList.add(styleEntry);
775         }
776         if (classEntry.grouping != null) {
777           styleEntry.setName(classEntry.grouping);
778           groupList.add(styleEntry);
779         }
780       }
781     }
782     
783     // we are parse valid if we dont get an exceptions
784     parseable = exceptionList.size() == 0;
785 
786     // we are now fully parsed.
787     hasParsingOccurred = true;
788   }
789   /**
790    * internal function to throw a formatted StyleSheetParseException
791    */
792   private StyleSheetParseException _throwTPE(int lineNo, String message) throws StyleSheetParseException {
793     message = "Style Failure : " + message;
794     StyleSheetParseException sse = new StyleSheetParseException(lineNo, null, message);
795     exceptionList.add(sse);
796     if (isExceptionParseFailure())
797       throw sse;
798     return sse;
799   }
800   
801    /** 
802    * Returns true if a value is in the form obj(...)
803    */
804   private boolean _isObjValue(String attrValue) {
805     if (attrValue == null)
806       return false;
807       
808     attrValue = attrValue.trim();
809     //
810     // if it's quoted as "ob(...)" then its 
811     // not in obj form
812     
813     if (_isQuotedValue(attrValue))
814       return false;
815       
816     if (attrValue.indexOf('(') > 1 && attrValue.indexOf(')') == attrValue.length()-1)
817       return true;
818     return false;
819   }
820   
821   private boolean _isQuotedValue(String attrValue) {
822     int last = attrValue.length()-1;
823     if (attrValue.charAt(0) == '"' && attrValue.charAt(last) == '"') {
824       return true;
825     } else if (attrValue.charAt(0) == '\'' && attrValue.charAt(last) == '\'') { 
826       return true;
827     }
828     return false;
829   }
830   
831   /**
832    * Adds a StyleSheetGroup to the StyleSheet.
833    * <p>
834    * @deprecated StyleSheetGroup has been deprecated and will be removed in the next version
835    * of EchoPoint.  Use the new attributed identifier on the Component from now on, 
836    * eg component.setIdentifier("stylegroup=xxx;");
837    */
838   public void addGroup(StyleSheetGroup styleSheetGroup) {
839     if (styleSheetGroup == null)
840       throw new IllegalArgumentException("A non null StyleSheetGroup must be provided");
841     
842     if (! styleSheetGroupList.contains(styleSheetGroup))
843       styleSheetGroupList.add(styleSheetGroup);
844     if (hasParsingOccurred) {
845       for (Iterator it = styleSheetGroup.getIterator(); it.hasNext();) {
846         Component c = (Component) it.next();
847         applyTo(c);
848       }
849     }
850   }
851   /**
852    * Adds a CssStyleSheetHandler to the CssStyleSheet.  Ths allows extension of the
853    * known style attribute names and values.
854    * <p>
855    */
856   public void addHandler(CssStyleSheetHandler handler) {
857     if (handler == null)
858       throw new IllegalArgumentException("A non null CssStyleSheetHandler must be provided");
859     handlerList.add(handler);
860   }
861   /**
862    * @see echopoint.stylesheet.StyleSheet#applyTo(nextapp.echo.Component)
863    */
864   public void applyTo(Component c) {
865     applyTo(c, false);
866   }
867   
868   /**
869    * @see echopoint.stylesheet.StyleSheet#applyTo(nextapp.echo.Component, boolean)
870    */
871   public void applyTo(Component c, boolean listenForFutureChildren) {
872     if (c == null)
873       throw new IllegalArgumentException("A non null Component must be provided");
874 
875     if (listenForFutureChildren)
876       startListeningTo(c);
877 
878     _applyStyleSheetRecursively(c);
879   }
880   
881   /**
882    * @see echopoint.stylesheet.StyleSheet#applyTo(nextapp.echo.EchoInstance)
883    */
884   public void applyTo(EchoInstance instance) {
885     applyTo(instance,false);
886   }
887   /**
888    * @see echopoint.stylesheet.StyleSheet#applyTo(nextapp.echo.EchoInstance, boolean)
889    */
890   public void applyTo(EchoInstance instance, boolean listenForFutureChildren) {
891     if (instance == null)
892       throw new IllegalArgumentException("A non null EchoInstance must be provided");
893 
894     if (listenForFutureChildren)
895       startListeningTo(instance);
896 
897     Window[] windows = instance.getWindows();
898     for (int i = 0; i < windows.length; i++) {
899       _applyStyleSheetRecursively(windows[i]);
900     }
901   }
902   
903   /**
904    * Returns all the derived classes of clazz, including clazz, in least 
905    * specific order (ie clazz is last).
906    */
907   private Class[] getClassesLeastSpecific(Class clazz) {
908 
909     ArrayList list = new ArrayList();
910     do {
911       list.add(clazz);
912       clazz = clazz.getSuperclass();
913     } while (clazz != null);
914     java.util.Collections.reverse(list);
915 
916     return (Class[]) list.toArray(new Class[list.size()]);
917   }
918   /**
919    * Returns all the derived classes of clazz, including clazz, in most specific
920    * order first (ie clazz is first)
921    */
922   private Class[] getClassesMostSpecific(Class clazz) {
923 
924     ArrayList list = new ArrayList();
925     do {
926       list.add(clazz);
927       clazz = clazz.getSuperclass();
928     } while (clazz != null);
929 
930     return (Class[]) list.toArray(new Class[list.size()]);
931   }
932   /**
933    * This method returns an array of StyleSheetParseExceptions that have occurred
934    * during parsing.  Note that this will only have more than 1 entries if
935    * setExceptionParseFailure is set to true.
936    */
937   public StyleSheetParseException[] getParseExceptions() {
938     return (StyleSheetParseException[]) exceptionList.toArray(new StyleSheetParseException[exceptionList.size()]);
939   }
940   /**
941    * This returns true if the CssStyleSheet will throw an StyleSheetParseException if it
942    * encounters an invalid or unknown attribute.  The default is true.
943    * <p>
944    * Note : this only covers the setting of style attributes values, not the general 
945    * parsing of the style sheet data.  A general parse problem will always throw a
946    * StyleSheetParseException.
947    * 
948    * @return boolean
949    */
950   public boolean isExceptionParseFailure() {
951     return failOnInvalidAttributes;
952   }
953   /**
954    * Loads the CssStyleSheet data contained in the Reader.
955    * <p>
956    * @throws StyleSheetParseException - if the data cannot be parsed
957    */
958   public void loadStyleSheet(Reader styleSheetReader) throws StyleSheetParseException {
959     _processStyleSheet(styleSheetReader);
960   }
961   /**
962    * Loads the CssStyleSheet data contained in the file named "fileName".
963    * <p>
964    * @throws StyleSheetParseException - if the file cannot be parsed
965    */
966   public void loadStyleSheet(String styleSheetFileName) throws StyleSheetParseException {
967     try {
968       loadStyleSheet(new java.io.BufferedReader(new java.io.FileReader(new File(styleSheetFileName))));
969     } catch (java.io.FileNotFoundException fne) {
970       throw new StyleSheetParseException(-1, fne, null);
971     }
972   }
973   /**
974    * Loads the CssStyleSheet data contained in the URL.
975    * <p>
976    * @throws StyleSheetParseException - if the data cannot be parsed
977    */
978   public void loadStyleSheet(URL styleSheetURL) throws StyleSheetParseException {
979     Reader in = null;
980     try {
981       in = new java.io.BufferedReader(new java.io.InputStreamReader(styleSheetURL.openStream(), "8859_1"));
982     } catch (java.io.IOException ioe) {
983       throw new StyleSheetParseException(-1, ioe, ioe.toString());
984     }
985     loadStyleSheet(in);
986   }
987   /**
988    * This method gets called when a bound property is changed.
989    * <p>
990    * The CssStyleSheet class will listen for Component.CHILDREN_CHANGED_PROPERTY
991    * and EchoInstance.WINDOWS_CHANGED_PROPERTY events and 
992    * will then apply any applicable Styles to the new Component and 
993    * its children, or conversely, stop listening if the child is being 
994    * removed.
995    * <p>
996    *
997    * @param evt A PropertyChangeEvent object describing the event source 
998    *     and the property that has changed.
999    */
1000  public void propertyChange(PropertyChangeEvent evt) {
1001    boolean eventQualifies = false;
1002    if (evt.getSource() instanceof Component && Component.CHILDREN_CHANGED_PROPERTY.equals(evt.getPropertyName())) {
1003      eventQualifies = true;
1004    }
1005    if (evt.getSource() instanceof EchoInstance && EchoInstance.WINDOWS_CHANGED_PROPERTY.equals(evt.getPropertyName())) {
1006      eventQualifies = true;
1007    }
1008      
1009    if (eventQualifies) {  
1010      Object newChild = evt.getNewValue();
1011      Object oldChild = evt.getOldValue();
1012      if (newChild != null && newChild instanceof Component) {
1013        //
1014        // we have a new child addition so start listening to it
1015        // and then apply some style to it.
1016        //
1017        Component child = (Component) evt.getNewValue();
1018        startListeningTo(child);
1019        _applyStyleSheetRecursively(child);
1020      }
1021      if (oldChild != null && oldChild instanceof Component && newChild == null) {
1022        //
1023        // we have a child deletion.  Stop listening to it
1024        //
1025        Component child = (Component) evt.getOldValue();
1026        stopListeningTo(child);
1027      }
1028    }
1029  }
1030  
1031  /**
1032   * This controls whether the CssStyleSheet will throw a StyleSheetParseException if it
1033   * encounters an invalid or unknown attribute.
1034   * <p>
1035   * Note : this only covers the setting of style attributes values, not the general 
1036   * parsing of the style sheet data.  A general parse problem will always throw a
1037   * StyleSheetParseException.
1038   * 
1039   * @param newExceptionParseFailure boolean
1040   */
1041  public void setExceptionParseFailure(boolean newExceptionParseFailure) {
1042    failOnInvalidAttributes = newExceptionParseFailure;
1043  }
1044  
1045  /** Listens to a Component and all its children */
1046  private void startListeningTo(Component c) {
1047    if (c != null) {
1048      c.addPropertyChangeListener(this);
1049      Component[] children = c.getComponents();
1050      for (int i = 0; i < children.length; i++) {
1051        startListeningTo(children[i]);
1052      }
1053    }
1054  }
1055  
1056  /** Listens to a Component and all its children */
1057  private void startListeningTo(EchoInstance instance) {
1058    if (instance != null) {
1059      instance.addPropertyChangeListener(this);
1060      Window[] windows = instance.getWindows();
1061      for (int i = 0; i < windows.length; i++) {
1062        startListeningTo(windows[i]);
1063      }
1064    }
1065  }
1066  
1067  /**
1068   * This stops the CssStyleSheet from listening for CHILDREN_PROPERTY_CHANGED events
1069   * on the given Component c.
1070   */
1071  public void stopListeningTo(Component c) {
1072    if (c != null) {
1073      c.removePropertyChangeListener(this);
1074      Component[] children = c.getComponents();
1075      for (int i = 0; i < children.length; i++) {
1076        stopListeningTo(children[i]);
1077      }
1078    }
1079  }
1080  
1081  /**
1082   * @see echopoint.stylesheet.StyleSheet#stopListeningTo(nextapp.echo.EchoInstance)
1083   */
1084  public void stopListeningTo(EchoInstance instance) {
1085    if (instance != null) {
1086      instance.removePropertyChangeListener(this);
1087      Window[] windows = instance.getWindows();
1088      for (int i = 0; i < windows.length; i++) {
1089        stopListeningTo(windows[i]);
1090      }
1091    }
1092  }
1093  
1094  
1095  /**
1096   * Returns an empty StyleSheet with no StyleSheetHandlers within it.
1097   * <p>
1098   * The returned object implements StyleSheet and is an instance of 
1099   * CssStyleSheet.
1100   * <p>
1101   * Using this form allows the developer to change the StyleSheetHandlers
1102   * used and/or the order in which they are applied.
1103   */
1104  public static CssStyleSheet getEmptyInstance() {
1105    return new CssStyleSheet();
1106  }
1107  /**
1108   * Returns a StyleSheet object.
1109   * <p>
1110   * The returned object implements StyleSheet and is an instance of 
1111   * CssStyleSheet.
1112   * <p>
1113   */
1114  public static CssStyleSheet getInstance() {
1115
1116    CssStyleSheet cssStyleSheet = new CssStyleSheet();
1117    cssStyleSheet.addHandler(new CssEchoStyleSheetHandler());
1118    cssStyleSheet.addHandler(new CssEchoPointStyleSheetHandler());
1119    return cssStyleSheet;
1120  }
1121  /**
1122   * Returns a StyleSheet object by parsing the given Reader style sheet data.  
1123   * <p>
1124   * The returned object implements StyleSheet and is an instance of 
1125   * CssStyleSheet.
1126   * <p>
1127   */
1128  public static CssStyleSheet getInstance(Reader styleSheetReader) throws StyleSheetParseException {
1129
1130    CssStyleSheet cssStyleSheet = new CssStyleSheet();
1131    cssStyleSheet.addHandler(new CssEchoStyleSheetHandler());
1132    cssStyleSheet.addHandler(new CssEchoPointStyleSheetHandler());
1133
1134    // and parse and load the style sheet entries
1135    cssStyleSheet.loadStyleSheet(styleSheetReader);
1136
1137    return cssStyleSheet;
1138  }
1139  /**
1140   * Returns a StyleSheet object by parsing the given style sheet file name.
1141   * <p>
1142   * The returned object implements StyleSheet and is an instance of 
1143   * CssStyleSheet.
1144   * <p>
1145   */
1146  public static CssStyleSheet getInstance(String styleSheetFileName) throws StyleSheetParseException {
1147
1148    CssStyleSheet cssStyleSheet = new CssStyleSheet();
1149    cssStyleSheet.addHandler(new CssEchoStyleSheetHandler());
1150    cssStyleSheet.addHandler(new CssEchoPointStyleSheetHandler());
1151
1152    // and parse and load the style sheet entries
1153    cssStyleSheet.loadStyleSheet(styleSheetFileName);
1154
1155    return cssStyleSheet;
1156  }
1157  /**
1158   * Returns a StyleSheet object by parsing the given URL style sheet data.  
1159   * <p>
1160   * The returned object implements StyleSheet and is an instance of 
1161   * CssStyleSheet.
1162   * <p>
1163   */
1164  public static CssStyleSheet getInstance(URL styleSheetURL) throws StyleSheetParseException {
1165
1166    CssStyleSheet cssStyleSheet = new CssStyleSheet();
1167    cssStyleSheet.addHandler(new CssEchoStyleSheetHandler());
1168    cssStyleSheet.addHandler(new CssEchoPointStyleSheetHandler());
1169
1170    // and parse and load the style sheet entries
1171    cssStyleSheet.loadStyleSheet(styleSheetURL);
1172
1173    return cssStyleSheet;
1174  }
1175  
1176  /**
1177   * Returns true if the current CssStyleSheet can be parsed.  This 
1178   * does not mean that it is perfectly valid, since the application
1179   * of the generated Style objects may still pose problems.  But it 
1180   * does mean that the CssStyleSheet has validated style attribute 
1181   * name and values as best it can.
1182   *  
1183   * @return true if the style sheet could be parsed
1184   */
1185  public boolean isParseable() {
1186    return parseable;
1187  }
1188}