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}