View Javadoc
1   /*
2    * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  package javax.swing.text.html;
26  
27  import sun.swing.SwingUtilities2;
28  import java.util.*;
29  import java.awt.*;
30  import java.io.*;
31  import java.net.*;
32  import javax.swing.Icon;
33  import javax.swing.ImageIcon;
34  import javax.swing.UIManager;
35  import javax.swing.border.*;
36  import javax.swing.event.ChangeListener;
37  import javax.swing.text.*;
38  
39  /**
40   * Support for defining the visual characteristics of
41   * HTML views being rendered.  The StyleSheet is used to
42   * translate the HTML model into visual characteristics.
43   * This enables views to be customized by a look-and-feel,
44   * multiple views over the same model can be rendered
45   * differently, etc.  This can be thought of as a CSS
46   * rule repository.  The key for CSS attributes is an
47   * object of type CSS.Attribute.  The type of the value
48   * is up to the StyleSheet implementation, but the
49   * <code>toString</code> method is required
50   * to return a string representation of CSS value.
51   * <p>
52   * The primary entry point for HTML View implementations
53   * to get their attributes is the
54   * {@link #getViewAttributes getViewAttributes}
55   * method.  This should be implemented to establish the
56   * desired policy used to associate attributes with the view.
57   * Each HTMLEditorKit (i.e. and therefore each associated
58   * JEditorPane) can have its own StyleSheet, but by default one
59   * sheet will be shared by all of the HTMLEditorKit instances.
60   * HTMLDocument instance can also have a StyleSheet, which
61   * holds the document-specific CSS specifications.
62   * <p>
63   * In order for Views to store less state and therefore be
64   * more lightweight, the StyleSheet can act as a factory for
65   * painters that handle some of the rendering tasks.  This allows
66   * implementations to determine what they want to cache
67   * and have the sharing potentially at the level that a
68   * selector is common to multiple views.  Since the StyleSheet
69   * may be used by views over multiple documents and typically
70   * the HTML attributes don't effect the selector being used,
71   * the potential for sharing is significant.
72   * <p>
73   * The rules are stored as named styles, and other information
74   * is stored to translate the context of an element to a
75   * rule quickly.  The following code fragment will display
76   * the named styles, and therefore the CSS rules contained.
77   * <pre><code>
78   * &nbsp;
79   * &nbsp; import java.util.*;
80   * &nbsp; import javax.swing.text.*;
81   * &nbsp; import javax.swing.text.html.*;
82   * &nbsp;
83   * &nbsp; public class ShowStyles {
84   * &nbsp;
85   * &nbsp;     public static void main(String[] args) {
86   * &nbsp;       HTMLEditorKit kit = new HTMLEditorKit();
87   * &nbsp;       HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
88   * &nbsp;       StyleSheet styles = doc.getStyleSheet();
89   * &nbsp;
90   * &nbsp;       Enumeration rules = styles.getStyleNames();
91   * &nbsp;       while (rules.hasMoreElements()) {
92   * &nbsp;           String name = (String) rules.nextElement();
93   * &nbsp;           Style rule = styles.getStyle(name);
94   * &nbsp;           System.out.println(rule.toString());
95   * &nbsp;       }
96   * &nbsp;       System.exit(0);
97   * &nbsp;     }
98   * &nbsp; }
99   * &nbsp;
100  * </code></pre>
101  * <p>
102  * The semantics for when a CSS style should overide visual attributes
103  * defined by an element are not well defined. For example, the html
104  * <code>&lt;body bgcolor=red&gt;</code> makes the body have a red
105  * background. But if the html file also contains the CSS rule
106  * <code>body { background: blue }</code> it becomes less clear as to
107  * what color the background of the body should be. The current
108  * implementation gives visual attributes defined in the element the
109  * highest precedence, that is they are always checked before any styles.
110  * Therefore, in the previous example the background would have a
111  * red color as the body element defines the background color to be red.
112  * <p>
113  * As already mentioned this supports CSS. We don't support the full CSS
114  * spec. Refer to the javadoc of the CSS class to see what properties
115  * we support. The two major CSS parsing related
116  * concepts we do not currently
117  * support are pseudo selectors, such as <code>A:link { color: red }</code>,
118  * and the <code>important</code> modifier.
119  * <p>
120  * <font color="red">Note: This implementation is currently
121  * incomplete.  It can be replaced with alternative implementations
122  * that are complete.  Future versions of this class will provide
123  * better CSS support.</font>
124  *
125  * @author  Timothy Prinzing
126  * @author  Sunita Mani
127  * @author  Sara Swanson
128  * @author  Jill Nakata
129  */
130 public class StyleSheet extends StyleContext {
131     // As the javadoc states, this class maintains a mapping between
132     // a CSS selector (such as p.bar) and a Style.
133     // This consists of a number of parts:
134     // . Each selector is broken down into its constituent simple selectors,
135     //   and stored in an inverted graph, for example:
136     //     p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt }
137     //   results in the graph:
138     //          root
139     //           |
140     //           p
141     //          / \
142     //         ol ul
143     //   each node (an instance of SelectorMapping) has an associated
144     //   specificity and potentially a Style.
145     // . Every rule that is asked for (either by way of getRule(String) or
146     //   getRule(HTML.Tag, Element)) results in a unique instance of
147     //   ResolvedStyle. ResolvedStyles contain the AttributeSets from the
148     //   SelectorMapping.
149     // . When a new rule is created it is inserted into the graph, and
150     //   the AttributeSets of each ResolvedStyles are updated appropriately.
151     // . This class creates special AttributeSets, LargeConversionSet and
152     //   SmallConversionSet, that maintain a mapping between StyleConstants
153     //   and CSS so that developers that wish to use the StyleConstants
154     //   methods can do so.
155     // . When one of the AttributeSets is mutated by way of a
156     //   StyleConstants key, all the associated CSS keys are removed. This is
157     //   done so that the two representations don't get out of sync. For
158     //   example, if the developer adds StyleConsants.BOLD, FALSE to an
159     //   AttributeSet that contains HTML.Tag.B, the HTML.Tag.B entry will
160     //   be removed.
161 
162     /**
163      * Construct a StyleSheet
164      */
165     public StyleSheet() {
166         super();
167         selectorMapping = new SelectorMapping(0);
168         resolvedStyles = new Hashtable<String, ResolvedStyle>();
169         if (css == null) {
170             css = new CSS();
171         }
172     }
173 
174     /**
175      * Fetches the style to use to render the given type
176      * of HTML tag.  The element given is representing
177      * the tag and can be used to determine the nesting
178      * for situations where the attributes will differ
179      * if nesting inside of elements.
180      *
181      * @param t the type to translate to visual attributes
182      * @param e the element representing the tag; the element
183      *  can be used to determine the nesting for situations where
184      *  the attributes will differ if nested inside of other
185      *  elements
186      * @return the set of CSS attributes to use to render
187      *  the tag
188      */
189     public Style getRule(HTML.Tag t, Element e) {
190         SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
191 
192         try {
193             // Build an array of all the parent elements.
194             Vector<Element> searchContext = sb.getVector();
195 
196             for (Element p = e; p != null; p = p.getParentElement()) {
197                 searchContext.addElement(p);
198             }
199 
200             // Build a fully qualified selector.
201             int              n = searchContext.size();
202             StringBuffer     cacheLookup = sb.getStringBuffer();
203             AttributeSet     attr;
204             String           eName;
205             Object           name;
206 
207             // >= 1 as the HTML.Tag for the 0th element is passed in.
208             for (int counter = n - 1; counter >= 1; counter--) {
209                 e = searchContext.elementAt(counter);
210                 attr = e.getAttributes();
211                 name = attr.getAttribute(StyleConstants.NameAttribute);
212                 eName = name.toString();
213                 cacheLookup.append(eName);
214                 if (attr != null) {
215                     if (attr.isDefined(HTML.Attribute.ID)) {
216                         cacheLookup.append('#');
217                         cacheLookup.append(attr.getAttribute
218                                            (HTML.Attribute.ID));
219                     }
220                     else if (attr.isDefined(HTML.Attribute.CLASS)) {
221                         cacheLookup.append('.');
222                         cacheLookup.append(attr.getAttribute
223                                            (HTML.Attribute.CLASS));
224                     }
225                 }
226                 cacheLookup.append(' ');
227             }
228             cacheLookup.append(t.toString());
229             e = searchContext.elementAt(0);
230             attr = e.getAttributes();
231             if (e.isLeaf()) {
232                 // For leafs, we use the second tier attributes.
233                 Object testAttr = attr.getAttribute(t);
234                 if (testAttr instanceof AttributeSet) {
235                     attr = (AttributeSet)testAttr;
236                 }
237                 else {
238                     attr = null;
239                 }
240             }
241             if (attr != null) {
242                 if (attr.isDefined(HTML.Attribute.ID)) {
243                     cacheLookup.append('#');
244                     cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
245                 }
246                 else if (attr.isDefined(HTML.Attribute.CLASS)) {
247                     cacheLookup.append('.');
248                     cacheLookup.append(attr.getAttribute
249                                        (HTML.Attribute.CLASS));
250                 }
251             }
252 
253             Style style = getResolvedStyle(cacheLookup.toString(),
254                                            searchContext, t);
255             return style;
256         }
257         finally {
258             SearchBuffer.releaseSearchBuffer(sb);
259         }
260     }
261 
262     /**
263      * Fetches the rule that best matches the selector given
264      * in string form. Where <code>selector</code> is a space separated
265      * String of the element names. For example, <code>selector</code>
266      * might be 'html body tr td''<p>
267      * The attributes of the returned Style will change
268      * as rules are added and removed. That is if you to ask for a rule
269      * with a selector "table p" and a new rule was added with a selector
270      * of "p" the returned Style would include the new attributes from
271      * the rule "p".
272      */
273     public Style getRule(String selector) {
274         selector = cleanSelectorString(selector);
275         if (selector != null) {
276             Style style = getResolvedStyle(selector);
277             return style;
278         }
279         return null;
280     }
281 
282     /**
283      * Adds a set of rules to the sheet.  The rules are expected to
284      * be in valid CSS format.  Typically this would be called as
285      * a result of parsing a &lt;style&gt; tag.
286      */
287     public void addRule(String rule) {
288         if (rule != null) {
289             //tweaks to control display properties
290             //see BasicEditorPaneUI
291             final String baseUnitsDisable = "BASE_SIZE_DISABLE";
292             final String baseUnits = "BASE_SIZE ";
293             final String w3cLengthUnitsEnable = "W3C_LENGTH_UNITS_ENABLE";
294             final String w3cLengthUnitsDisable = "W3C_LENGTH_UNITS_DISABLE";
295             if (rule == baseUnitsDisable) {
296                 sizeMap = sizeMapDefault;
297             } else if (rule.startsWith(baseUnits)) {
298                 rebaseSizeMap(Integer.
299                               parseInt(rule.substring(baseUnits.length())));
300             } else if (rule == w3cLengthUnitsEnable) {
301                 w3cLengthUnits = true;
302             } else if (rule == w3cLengthUnitsDisable) {
303                 w3cLengthUnits = false;
304             } else {
305                 CssParser parser = new CssParser();
306                 try {
307                     parser.parse(getBase(), new StringReader(rule), false, false);
308                 } catch (IOException ioe) { }
309             }
310         }
311     }
312 
313     /**
314      * Translates a CSS declaration to an AttributeSet that represents
315      * the CSS declaration.  Typically this would be called as a
316      * result of encountering an HTML style attribute.
317      */
318     public AttributeSet getDeclaration(String decl) {
319         if (decl == null) {
320             return SimpleAttributeSet.EMPTY;
321         }
322         CssParser parser = new CssParser();
323         return parser.parseDeclaration(decl);
324     }
325 
326     /**
327      * Loads a set of rules that have been specified in terms of
328      * CSS1 grammar.  If there are collisions with existing rules,
329      * the newly specified rule will win.
330      *
331      * @param in the stream to read the CSS grammar from
332      * @param ref the reference URL.  This value represents the
333      *  location of the stream and may be null.  All relative
334      *  URLs specified in the stream will be based upon this
335      *  parameter.
336      */
337     public void loadRules(Reader in, URL ref) throws IOException {
338         CssParser parser = new CssParser();
339         parser.parse(ref, in, false, false);
340     }
341 
342     /**
343      * Fetches a set of attributes to use in the view for
344      * displaying.  This is basically a set of attributes that
345      * can be used for View.getAttributes.
346      */
347     public AttributeSet getViewAttributes(View v) {
348         return new ViewAttributeSet(v);
349     }
350 
351     /**
352      * Removes a named style previously added to the document.
353      *
354      * @param nm  the name of the style to remove
355      */
356     public void removeStyle(String nm) {
357         Style       aStyle = getStyle(nm);
358 
359         if (aStyle != null) {
360             String selector = cleanSelectorString(nm);
361             String[] selectors = getSimpleSelectors(selector);
362             synchronized(this) {
363                 SelectorMapping mapping = getRootSelectorMapping();
364                 for (int i = selectors.length - 1; i >= 0; i--) {
365                     mapping = mapping.getChildSelectorMapping(selectors[i],
366                                                               true);
367                 }
368                 Style rule = mapping.getStyle();
369                 if (rule != null) {
370                     mapping.setStyle(null);
371                     if (resolvedStyles.size() > 0) {
372                         Enumeration<ResolvedStyle> values = resolvedStyles.elements();
373                         while (values.hasMoreElements()) {
374                             ResolvedStyle style = values.nextElement();
375                             style.removeStyle(rule);
376                         }
377                     }
378                 }
379             }
380         }
381         super.removeStyle(nm);
382     }
383 
384     /**
385      * Adds the rules from the StyleSheet <code>ss</code> to those of
386      * the receiver. <code>ss's</code> rules will override the rules of
387      * any previously added style sheets. An added StyleSheet will never
388      * override the rules of the receiving style sheet.
389      *
390      * @since 1.3
391      */
392     public void addStyleSheet(StyleSheet ss) {
393         synchronized(this) {
394             if (linkedStyleSheets == null) {
395                 linkedStyleSheets = new Vector<StyleSheet>();
396             }
397             if (!linkedStyleSheets.contains(ss)) {
398                 int index = 0;
399                 if (ss instanceof javax.swing.plaf.UIResource
400                     && linkedStyleSheets.size() > 1) {
401                     index = linkedStyleSheets.size() - 1;
402                 }
403                 linkedStyleSheets.insertElementAt(ss, index);
404                 linkStyleSheetAt(ss, index);
405             }
406         }
407     }
408 
409     /**
410      * Removes the StyleSheet <code>ss</code> from those of the receiver.
411      *
412      * @since 1.3
413      */
414     public void removeStyleSheet(StyleSheet ss) {
415         synchronized(this) {
416             if (linkedStyleSheets != null) {
417                 int index = linkedStyleSheets.indexOf(ss);
418                 if (index != -1) {
419                     linkedStyleSheets.removeElementAt(index);
420                     unlinkStyleSheet(ss, index);
421                     if (index == 0 && linkedStyleSheets.size() == 0) {
422                         linkedStyleSheets = null;
423                     }
424                 }
425             }
426         }
427     }
428 
429     //
430     // The following is used to import style sheets.
431     //
432 
433     /**
434      * Returns an array of the linked StyleSheets. Will return null
435      * if there are no linked StyleSheets.
436      *
437      * @since 1.3
438      */
439     public StyleSheet[] getStyleSheets() {
440         StyleSheet[] retValue;
441 
442         synchronized(this) {
443             if (linkedStyleSheets != null) {
444                 retValue = new StyleSheet[linkedStyleSheets.size()];
445                 linkedStyleSheets.copyInto(retValue);
446             }
447             else {
448                 retValue = null;
449             }
450         }
451         return retValue;
452     }
453 
454     /**
455      * Imports a style sheet from <code>url</code>. The resulting rules
456      * are directly added to the receiver. If you do not want the rules
457      * to become part of the receiver, create a new StyleSheet and use
458      * addStyleSheet to link it in.
459      *
460      * @since 1.3
461      */
462     public void importStyleSheet(URL url) {
463         try {
464             InputStream is;
465 
466             is = url.openStream();
467             Reader r = new BufferedReader(new InputStreamReader(is));
468             CssParser parser = new CssParser();
469             parser.parse(url, r, false, true);
470             r.close();
471             is.close();
472         } catch (Throwable e) {
473             // on error we simply have no styles... the html
474             // will look mighty wrong but still function.
475         }
476     }
477 
478     /**
479      * Sets the base. All import statements that are relative, will be
480      * relative to <code>base</code>.
481      *
482      * @since 1.3
483      */
484     public void setBase(URL base) {
485         this.base = base;
486     }
487 
488     /**
489      * Returns the base.
490      *
491      * @since 1.3
492      */
493     public URL getBase() {
494         return base;
495     }
496 
497     /**
498      * Adds a CSS attribute to the given set.
499      *
500      * @since 1.3
501      */
502     public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
503                                 String value) {
504         css.addInternalCSSValue(attr, key, value);
505     }
506 
507     /**
508      * Adds a CSS attribute to the given set.
509      *
510      * @since 1.3
511      */
512     public boolean addCSSAttributeFromHTML(MutableAttributeSet attr,
513                                            CSS.Attribute key, String value) {
514         Object iValue = css.getCssValue(key, value);
515         if (iValue != null) {
516             attr.addAttribute(key, iValue);
517             return true;
518         }
519         return false;
520     }
521 
522     // ---- Conversion functionality ---------------------------------
523 
524     /**
525      * Converts a set of HTML attributes to an equivalent
526      * set of CSS attributes.
527      *
528      * @param htmlAttrSet AttributeSet containing the HTML attributes.
529      */
530     public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
531         AttributeSet cssAttrSet = css.translateHTMLToCSS(htmlAttrSet);
532 
533         MutableAttributeSet cssStyleSet = addStyle(null, null);
534         cssStyleSet.addAttributes(cssAttrSet);
535 
536         return cssStyleSet;
537     }
538 
539     /**
540      * Adds an attribute to the given set, and returns
541      * the new representative set.  This is reimplemented to
542      * convert StyleConstant attributes to CSS prior to forwarding
543      * to the superclass behavior.  The StyleConstants attribute
544      * has no corresponding CSS entry, the StyleConstants attribute
545      * is stored (but will likely be unused).
546      *
547      * @param old the old attribute set
548      * @param key the non-null attribute key
549      * @param value the attribute value
550      * @return the updated attribute set
551      * @see MutableAttributeSet#addAttribute
552      */
553     public AttributeSet addAttribute(AttributeSet old, Object key,
554                                      Object value) {
555         if (css == null) {
556             // supers constructor will call this before returning,
557             // and we need to make sure CSS is non null.
558             css = new CSS();
559         }
560         if (key instanceof StyleConstants) {
561             HTML.Tag tag = HTML.getTagForStyleConstantsKey(
562                                 (StyleConstants)key);
563 
564             if (tag != null && old.isDefined(tag)) {
565                 old = removeAttribute(old, tag);
566             }
567 
568             Object cssValue = css.styleConstantsValueToCSSValue
569                               ((StyleConstants)key, value);
570             if (cssValue != null) {
571                 Object cssKey = css.styleConstantsKeyToCSSKey
572                                     ((StyleConstants)key);
573                 if (cssKey != null) {
574                     return super.addAttribute(old, cssKey, cssValue);
575                 }
576             }
577         }
578         return super.addAttribute(old, key, value);
579     }
580 
581     /**
582      * Adds a set of attributes to the element.  If any of these attributes
583      * are StyleConstants attributes, they will be converted to CSS prior
584      * to forwarding to the superclass behavior.
585      *
586      * @param old the old attribute set
587      * @param attr the attributes to add
588      * @return the updated attribute set
589      * @see MutableAttributeSet#addAttribute
590      */
591     public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) {
592         if (!(attr instanceof HTMLDocument.TaggedAttributeSet)) {
593             old = removeHTMLTags(old, attr);
594         }
595         return super.addAttributes(old, convertAttributeSet(attr));
596     }
597 
598     /**
599      * Removes an attribute from the set.  If the attribute is a StyleConstants
600      * attribute, the request will be converted to a CSS attribute prior to
601      * forwarding to the superclass behavior.
602      *
603      * @param old the old set of attributes
604      * @param key the non-null attribute name
605      * @return the updated attribute set
606      * @see MutableAttributeSet#removeAttribute
607      */
608     public AttributeSet removeAttribute(AttributeSet old, Object key) {
609         if (key instanceof StyleConstants) {
610             HTML.Tag tag = HTML.getTagForStyleConstantsKey(
611                                    (StyleConstants)key);
612             if (tag != null) {
613                 old = super.removeAttribute(old, tag);
614             }
615 
616             Object cssKey = css.styleConstantsKeyToCSSKey((StyleConstants)key);
617             if (cssKey != null) {
618                 return super.removeAttribute(old, cssKey);
619             }
620         }
621         return super.removeAttribute(old, key);
622     }
623 
624     /**
625      * Removes a set of attributes for the element.  If any of the attributes
626      * is a StyleConstants attribute, the request will be converted to a CSS
627      * attribute prior to forwarding to the superclass behavior.
628      *
629      * @param old the old attribute set
630      * @param names the attribute names
631      * @return the updated attribute set
632      * @see MutableAttributeSet#removeAttributes
633      */
634     public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) {
635         // PENDING: Should really be doing something similar to
636         // removeHTMLTags here, but it is rather expensive to have to
637         // clone names
638         return super.removeAttributes(old, names);
639     }
640 
641     /**
642      * Removes a set of attributes. If any of the attributes
643      * is a StyleConstants attribute, the request will be converted to a CSS
644      * attribute prior to forwarding to the superclass behavior.
645      *
646      * @param old the old attribute set
647      * @param attrs the attributes
648      * @return the updated attribute set
649      * @see MutableAttributeSet#removeAttributes
650      */
651     public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) {
652         if (old != attrs) {
653             old = removeHTMLTags(old, attrs);
654         }
655         return super.removeAttributes(old, convertAttributeSet(attrs));
656     }
657 
658     /**
659      * Creates a compact set of attributes that might be shared.
660      * This is a hook for subclasses that want to alter the
661      * behavior of SmallAttributeSet.  This can be reimplemented
662      * to return an AttributeSet that provides some sort of
663      * attribute conversion.
664      *
665      * @param a The set of attributes to be represented in the
666      *  the compact form.
667      */
668     protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) {
669         return new SmallConversionSet(a);
670     }
671 
672     /**
673      * Creates a large set of attributes that should trade off
674      * space for time.  This set will not be shared.  This is
675      * a hook for subclasses that want to alter the behavior
676      * of the larger attribute storage format (which is
677      * SimpleAttributeSet by default).   This can be reimplemented
678      * to return a MutableAttributeSet that provides some sort of
679      * attribute conversion.
680      *
681      * @param a The set of attributes to be represented in the
682      *  the larger form.
683      */
684     protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) {
685         return new LargeConversionSet(a);
686     }
687 
688     /**
689      * For any StyleConstants key in attr that has an associated HTML.Tag,
690      * it is removed from old. The resulting AttributeSet is then returned.
691      */
692     private AttributeSet removeHTMLTags(AttributeSet old, AttributeSet attr) {
693         if (!(attr instanceof LargeConversionSet) &&
694             !(attr instanceof SmallConversionSet)) {
695             Enumeration names = attr.getAttributeNames();
696 
697             while (names.hasMoreElements()) {
698                 Object key = names.nextElement();
699 
700                 if (key instanceof StyleConstants) {
701                     HTML.Tag tag = HTML.getTagForStyleConstantsKey(
702                         (StyleConstants)key);
703 
704                     if (tag != null && old.isDefined(tag)) {
705                         old = super.removeAttribute(old, tag);
706                     }
707                 }
708             }
709         }
710         return old;
711     }
712 
713     /**
714      * Converts a set of attributes (if necessary) so that
715      * any attributes that were specified as StyleConstants
716      * attributes and have a CSS mapping, will be converted
717      * to CSS attributes.
718      */
719     AttributeSet convertAttributeSet(AttributeSet a) {
720         if ((a instanceof LargeConversionSet) ||
721             (a instanceof SmallConversionSet)) {
722             // known to be converted.
723             return a;
724         }
725         // in most cases, there are no StyleConstants attributes
726         // so we iterate the collection of keys to avoid creating
727         // a new set.
728         Enumeration names = a.getAttributeNames();
729         while (names.hasMoreElements()) {
730             Object name = names.nextElement();
731             if (name instanceof StyleConstants) {
732                 // we really need to do a conversion, iterate again
733                 // building a new set.
734                 MutableAttributeSet converted = new LargeConversionSet();
735                 Enumeration keys = a.getAttributeNames();
736                 while (keys.hasMoreElements()) {
737                     Object key = keys.nextElement();
738                     Object cssValue = null;
739                     if (key instanceof StyleConstants) {
740                         // convert the StyleConstants attribute if possible
741                         Object cssKey = css.styleConstantsKeyToCSSKey
742                                             ((StyleConstants)key);
743                         if (cssKey != null) {
744                             Object value = a.getAttribute(key);
745                             cssValue = css.styleConstantsValueToCSSValue
746                                            ((StyleConstants)key, value);
747                             if (cssValue != null) {
748                                 converted.addAttribute(cssKey, cssValue);
749                             }
750                         }
751                     }
752                     if (cssValue == null) {
753                         converted.addAttribute(key, a.getAttribute(key));
754                     }
755                 }
756                 return converted;
757             }
758         }
759         return a;
760     }
761 
762     /**
763      * Large set of attributes that does conversion of requests
764      * for attributes of type StyleConstants.
765      */
766     class LargeConversionSet extends SimpleAttributeSet {
767 
768         /**
769          * Creates a new attribute set based on a supplied set of attributes.
770          *
771          * @param source the set of attributes
772          */
773         public LargeConversionSet(AttributeSet source) {
774             super(source);
775         }
776 
777         public LargeConversionSet() {
778             super();
779         }
780 
781         /**
782          * Checks whether a given attribute is defined.
783          *
784          * @param key the attribute key
785          * @return true if the attribute is defined
786          * @see AttributeSet#isDefined
787          */
788         public boolean isDefined(Object key) {
789             if (key instanceof StyleConstants) {
790                 Object cssKey = css.styleConstantsKeyToCSSKey
791                                     ((StyleConstants)key);
792                 if (cssKey != null) {
793                     return super.isDefined(cssKey);
794                 }
795             }
796             return super.isDefined(key);
797         }
798 
799         /**
800          * Gets the value of an attribute.
801          *
802          * @param key the attribute name
803          * @return the attribute value
804          * @see AttributeSet#getAttribute
805          */
806         public Object getAttribute(Object key) {
807             if (key instanceof StyleConstants) {
808                 Object cssKey = css.styleConstantsKeyToCSSKey
809                                     ((StyleConstants)key);
810                 if (cssKey != null) {
811                     Object value = super.getAttribute(cssKey);
812                     if (value != null) {
813                         return css.cssValueToStyleConstantsValue
814                                            ((StyleConstants)key, value);
815                     }
816                 }
817             }
818             return super.getAttribute(key);
819         }
820     }
821 
822     /**
823      * Small set of attributes that does conversion of requests
824      * for attributes of type StyleConstants.
825      */
826     class SmallConversionSet extends SmallAttributeSet {
827 
828         /**
829          * Creates a new attribute set based on a supplied set of attributes.
830          *
831          * @param attrs the set of attributes
832          */
833         public SmallConversionSet(AttributeSet attrs) {
834             super(attrs);
835         }
836 
837         /**
838          * Checks whether a given attribute is defined.
839          *
840          * @param key the attribute key
841          * @return true if the attribute is defined
842          * @see AttributeSet#isDefined
843          */
844         public boolean isDefined(Object key) {
845             if (key instanceof StyleConstants) {
846                 Object cssKey = css.styleConstantsKeyToCSSKey
847                                     ((StyleConstants)key);
848                 if (cssKey != null) {
849                     return super.isDefined(cssKey);
850                 }
851             }
852             return super.isDefined(key);
853         }
854 
855         /**
856          * Gets the value of an attribute.
857          *
858          * @param key the attribute name
859          * @return the attribute value
860          * @see AttributeSet#getAttribute
861          */
862         public Object getAttribute(Object key) {
863             if (key instanceof StyleConstants) {
864                 Object cssKey = css.styleConstantsKeyToCSSKey
865                                     ((StyleConstants)key);
866                 if (cssKey != null) {
867                     Object value = super.getAttribute(cssKey);
868                     if (value != null) {
869                         return css.cssValueToStyleConstantsValue
870                                            ((StyleConstants)key, value);
871                     }
872                 }
873             }
874             return super.getAttribute(key);
875         }
876     }
877 
878     // ---- Resource handling ----------------------------------------
879 
880     /**
881      * Fetches the font to use for the given set of attributes.
882      */
883     public Font getFont(AttributeSet a) {
884         return css.getFont(this, a, 12, this);
885     }
886 
887     /**
888      * Takes a set of attributes and turn it into a foreground color
889      * specification.  This might be used to specify things
890      * like brighter, more hue, etc.
891      *
892      * @param a the set of attributes
893      * @return the color
894      */
895     public Color getForeground(AttributeSet a) {
896         Color c = css.getColor(a, CSS.Attribute.COLOR);
897         if (c == null) {
898             return Color.black;
899         }
900         return c;
901     }
902 
903     /**
904      * Takes a set of attributes and turn it into a background color
905      * specification.  This might be used to specify things
906      * like brighter, more hue, etc.
907      *
908      * @param a the set of attributes
909      * @return the color
910      */
911     public Color getBackground(AttributeSet a) {
912         return css.getColor(a, CSS.Attribute.BACKGROUND_COLOR);
913     }
914 
915     /**
916      * Fetches the box formatter to use for the given set
917      * of CSS attributes.
918      */
919     public BoxPainter getBoxPainter(AttributeSet a) {
920         return new BoxPainter(a, css, this);
921     }
922 
923     /**
924      * Fetches the list formatter to use for the given set
925      * of CSS attributes.
926      */
927     public ListPainter getListPainter(AttributeSet a) {
928         return new ListPainter(a, this);
929     }
930 
931     /**
932      * Sets the base font size, with valid values between 1 and 7.
933      */
934     public void setBaseFontSize(int sz) {
935         css.setBaseFontSize(sz);
936     }
937 
938     /**
939      * Sets the base font size from the passed in String. The string
940      * can either identify a specific font size, with legal values between
941      * 1 and 7, or identify a relative font size such as +1 or -2.
942      */
943     public void setBaseFontSize(String size) {
944         css.setBaseFontSize(size);
945     }
946 
947     public static int getIndexOfSize(float pt) {
948         return CSS.getIndexOfSize(pt, sizeMapDefault);
949     }
950 
951     /**
952      * Returns the point size, given a size index.
953      */
954     public float getPointSize(int index) {
955         return css.getPointSize(index, this);
956     }
957 
958     /**
959      *  Given a string such as "+2", "-2", or "2",
960      *  returns a point size value.
961      */
962     public float getPointSize(String size) {
963         return css.getPointSize(size, this);
964     }
965 
966     /**
967      * Converts a color string such as "RED" or "#NNNNNN" to a Color.
968      * Note: This will only convert the HTML3.2 color strings
969      *       or a string of length 7;
970      *       otherwise, it will return null.
971      */
972     public Color stringToColor(String string) {
973         return CSS.stringToColor(string);
974     }
975 
976     /**
977      * Returns the ImageIcon to draw in the background for
978      * <code>attr</code>.
979      */
980     ImageIcon getBackgroundImage(AttributeSet attr) {
981         Object value = attr.getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
982 
983         if (value != null) {
984             return ((CSS.BackgroundImage)value).getImage(getBase());
985         }
986         return null;
987     }
988 
989     /**
990      * Adds a rule into the StyleSheet.
991      *
992      * @param selector the selector to use for the rule.
993      *  This will be a set of simple selectors, and must
994      *  be a length of 1 or greater.
995      * @param declaration the set of CSS attributes that
996      *  make up the rule.
997      */
998     void addRule(String[] selector, AttributeSet declaration,
999                  boolean isLinked) {
1000         int n = selector.length;
1001         StringBuilder sb = new StringBuilder();
1002         sb.append(selector[0]);
1003         for (int counter = 1; counter < n; counter++) {
1004             sb.append(' ');
1005             sb.append(selector[counter]);
1006         }
1007         String selectorName = sb.toString();
1008         Style rule = getStyle(selectorName);
1009         if (rule == null) {
1010             // Notice how the rule is first created, and it not part of
1011             // the synchronized block. It is done like this as creating
1012             // a new rule will fire a ChangeEvent. We do not want to be
1013             // holding the lock when calling to other objects, it can
1014             // result in deadlock.
1015             Style altRule = addStyle(selectorName, null);
1016             synchronized(this) {
1017                 SelectorMapping mapping = getRootSelectorMapping();
1018                 for (int i = n - 1; i >= 0; i--) {
1019                     mapping = mapping.getChildSelectorMapping
1020                                       (selector[i], true);
1021                 }
1022                 rule = mapping.getStyle();
1023                 if (rule == null) {
1024                     rule = altRule;
1025                     mapping.setStyle(rule);
1026                     refreshResolvedRules(selectorName, selector, rule,
1027                                          mapping.getSpecificity());
1028                 }
1029             }
1030         }
1031         if (isLinked) {
1032             rule = getLinkedStyle(rule);
1033         }
1034         rule.addAttributes(declaration);
1035     }
1036 
1037     //
1038     // The following gaggle of methods is used in maintaining the rules from
1039     // the sheet.
1040     //
1041 
1042     /**
1043      * Updates the attributes of the rules to reference any related
1044      * rules in <code>ss</code>.
1045      */
1046     private synchronized void linkStyleSheetAt(StyleSheet ss, int index) {
1047         if (resolvedStyles.size() > 0) {
1048             Enumeration<ResolvedStyle> values = resolvedStyles.elements();
1049             while (values.hasMoreElements()) {
1050                 ResolvedStyle rule = values.nextElement();
1051                 rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
1052                                            index);
1053             }
1054         }
1055     }
1056 
1057     /**
1058      * Removes references to the rules in <code>ss</code>.
1059      * <code>index</code> gives the index the StyleSheet was at, that is
1060      * how many StyleSheets had been added before it.
1061      */
1062     private synchronized void unlinkStyleSheet(StyleSheet ss, int index) {
1063         if (resolvedStyles.size() > 0) {
1064             Enumeration<ResolvedStyle> values = resolvedStyles.elements();
1065             while (values.hasMoreElements()) {
1066                 ResolvedStyle rule = values.nextElement();
1067                 rule.removeExtendedStyleAt(index);
1068             }
1069         }
1070     }
1071 
1072     /**
1073      * Returns the simple selectors that comprise selector.
1074      */
1075     /* protected */
1076     String[] getSimpleSelectors(String selector) {
1077         selector = cleanSelectorString(selector);
1078         SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1079         Vector<String> selectors = sb.getVector();
1080         int lastIndex = 0;
1081         int length = selector.length();
1082         while (lastIndex != -1) {
1083             int newIndex = selector.indexOf(' ', lastIndex);
1084             if (newIndex != -1) {
1085                 selectors.addElement(selector.substring(lastIndex, newIndex));
1086                 if (++newIndex == length) {
1087                     lastIndex = -1;
1088                 }
1089                 else {
1090                     lastIndex = newIndex;
1091                 }
1092             }
1093             else {
1094                 selectors.addElement(selector.substring(lastIndex));
1095                 lastIndex = -1;
1096             }
1097         }
1098         String[] retValue = new String[selectors.size()];
1099         selectors.copyInto(retValue);
1100         SearchBuffer.releaseSearchBuffer(sb);
1101         return retValue;
1102     }
1103 
1104     /**
1105      * Returns a string that only has one space between simple selectors,
1106      * which may be the passed in String.
1107      */
1108     /*protected*/ String cleanSelectorString(String selector) {
1109         boolean lastWasSpace = true;
1110         for (int counter = 0, maxCounter = selector.length();
1111              counter < maxCounter; counter++) {
1112             switch(selector.charAt(counter)) {
1113             case ' ':
1114                 if (lastWasSpace) {
1115                     return _cleanSelectorString(selector);
1116                 }
1117                 lastWasSpace = true;
1118                 break;
1119             case '\n':
1120             case '\r':
1121             case '\t':
1122                 return _cleanSelectorString(selector);
1123             default:
1124                 lastWasSpace = false;
1125             }
1126         }
1127         if (lastWasSpace) {
1128             return _cleanSelectorString(selector);
1129         }
1130         // It was fine.
1131         return selector;
1132     }
1133 
1134     /**
1135      * Returns a new String that contains only one space between non
1136      * white space characters.
1137      */
1138     private String _cleanSelectorString(String selector) {
1139         SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1140         StringBuffer buff = sb.getStringBuffer();
1141         boolean lastWasSpace = true;
1142         int lastIndex = 0;
1143         char[] chars = selector.toCharArray();
1144         int numChars = chars.length;
1145         String retValue = null;
1146         try {
1147             for (int counter = 0; counter < numChars; counter++) {
1148                 switch(chars[counter]) {
1149                 case ' ':
1150                     if (!lastWasSpace) {
1151                         lastWasSpace = true;
1152                         if (lastIndex < counter) {
1153                             buff.append(chars, lastIndex,
1154                                         1 + counter - lastIndex);
1155                         }
1156                     }
1157                     lastIndex = counter + 1;
1158                     break;
1159                 case '\n':
1160                 case '\r':
1161                 case '\t':
1162                     if (!lastWasSpace) {
1163                         lastWasSpace = true;
1164                         if (lastIndex < counter) {
1165                             buff.append(chars, lastIndex,
1166                                         counter - lastIndex);
1167                             buff.append(' ');
1168                         }
1169                     }
1170                     lastIndex = counter + 1;
1171                     break;
1172                 default:
1173                     lastWasSpace = false;
1174                     break;
1175                 }
1176             }
1177             if (lastWasSpace && buff.length() > 0) {
1178                 // Remove last space.
1179                 buff.setLength(buff.length() - 1);
1180             }
1181             else if (lastIndex < numChars) {
1182                 buff.append(chars, lastIndex, numChars - lastIndex);
1183             }
1184             retValue = buff.toString();
1185         }
1186         finally {
1187             SearchBuffer.releaseSearchBuffer(sb);
1188         }
1189         return retValue;
1190     }
1191 
1192     /**
1193      * Returns the root selector mapping that all selectors are relative
1194      * to. This is an inverted graph of the selectors.
1195      */
1196     private SelectorMapping getRootSelectorMapping() {
1197         return selectorMapping;
1198     }
1199 
1200     /**
1201      * Returns the specificity of the passed in String. It assumes the
1202      * passed in string doesn't contain junk, that is each selector is
1203      * separated by a space and each selector at most contains one . or one
1204      * #. A simple selector has a weight of 1, an id selector has a weight
1205      * of 100, and a class selector has a weight of 10000.
1206      */
1207     /*protected*/ static int getSpecificity(String selector) {
1208         int specificity = 0;
1209         boolean lastWasSpace = true;
1210 
1211         for (int counter = 0, maxCounter = selector.length();
1212              counter < maxCounter; counter++) {
1213             switch(selector.charAt(counter)) {
1214             case '.':
1215                 specificity += 100;
1216                 break;
1217             case '#':
1218                 specificity += 10000;
1219                 break;
1220             case ' ':
1221                 lastWasSpace = true;
1222                 break;
1223             default:
1224                 if (lastWasSpace) {
1225                     lastWasSpace = false;
1226                     specificity += 1;
1227                 }
1228             }
1229         }
1230         return specificity;
1231     }
1232 
1233     /**
1234      * Returns the style that linked attributes should be added to. This
1235      * will create the style if necessary.
1236      */
1237     private Style getLinkedStyle(Style localStyle) {
1238         // NOTE: This is not synchronized, and the caller of this does
1239         // not synchronize. There is the chance for one of the callers to
1240         // overwrite the existing resolved parent, but it is quite rare.
1241         // The reason this is left like this is because setResolveParent
1242         // will fire a ChangeEvent. It is really, REALLY bad for us to
1243         // hold a lock when calling outside of us, it may cause a deadlock.
1244         Style retStyle = (Style)localStyle.getResolveParent();
1245         if (retStyle == null) {
1246             retStyle = addStyle(null, null);
1247             localStyle.setResolveParent(retStyle);
1248         }
1249         return retStyle;
1250     }
1251 
1252     /**
1253      * Returns the resolved style for <code>selector</code>. This will
1254      * create the resolved style, if necessary.
1255      */
1256     private synchronized Style getResolvedStyle(String selector,
1257                                                 Vector elements,
1258                                                 HTML.Tag t) {
1259         Style retStyle = resolvedStyles.get(selector);
1260         if (retStyle == null) {
1261             retStyle = createResolvedStyle(selector, elements, t);
1262         }
1263         return retStyle;
1264     }
1265 
1266     /**
1267      * Returns the resolved style for <code>selector</code>. This will
1268      * create the resolved style, if necessary.
1269      */
1270     private synchronized Style getResolvedStyle(String selector) {
1271         Style retStyle = resolvedStyles.get(selector);
1272         if (retStyle == null) {
1273             retStyle = createResolvedStyle(selector);
1274         }
1275         return retStyle;
1276     }
1277 
1278     /**
1279      * Adds <code>mapping</code> to <code>elements</code>. It is added
1280      * such that <code>elements</code> will remain ordered by
1281      * specificity.
1282      */
1283     private void addSortedStyle(SelectorMapping mapping, Vector<SelectorMapping> elements) {
1284         int       size = elements.size();
1285 
1286         if (size > 0) {
1287             int     specificity = mapping.getSpecificity();
1288 
1289             for (int counter = 0; counter < size; counter++) {
1290                 if (specificity >= elements.elementAt(counter).getSpecificity()) {
1291                     elements.insertElementAt(mapping, counter);
1292                     return;
1293                 }
1294             }
1295         }
1296         elements.addElement(mapping);
1297     }
1298 
1299     /**
1300      * Adds <code>parentMapping</code> to <code>styles</code>, and
1301      * recursively calls this method if <code>parentMapping</code> has
1302      * any child mappings for any of the Elements in <code>elements</code>.
1303      */
1304     private synchronized void getStyles(SelectorMapping parentMapping,
1305                            Vector<SelectorMapping> styles,
1306                            String[] tags, String[] ids, String[] classes,
1307                            int index, int numElements,
1308                            Hashtable<SelectorMapping, SelectorMapping> alreadyChecked) {
1309         // Avoid desending the same mapping twice.
1310         if (alreadyChecked.contains(parentMapping)) {
1311             return;
1312         }
1313         alreadyChecked.put(parentMapping, parentMapping);
1314         Style style = parentMapping.getStyle();
1315         if (style != null) {
1316             addSortedStyle(parentMapping, styles);
1317         }
1318         for (int counter = index; counter < numElements; counter++) {
1319             String tagString = tags[counter];
1320             if (tagString != null) {
1321                 SelectorMapping childMapping = parentMapping.
1322                                 getChildSelectorMapping(tagString, false);
1323                 if (childMapping != null) {
1324                     getStyles(childMapping, styles, tags, ids, classes,
1325                               counter + 1, numElements, alreadyChecked);
1326                 }
1327                 if (classes[counter] != null) {
1328                     String className = classes[counter];
1329                     childMapping = parentMapping.getChildSelectorMapping(
1330                                          tagString + "." + className, false);
1331                     if (childMapping != null) {
1332                         getStyles(childMapping, styles, tags, ids, classes,
1333                                   counter + 1, numElements, alreadyChecked);
1334                     }
1335                     childMapping = parentMapping.getChildSelectorMapping(
1336                                          "." + className, false);
1337                     if (childMapping != null) {
1338                         getStyles(childMapping, styles, tags, ids, classes,
1339                                   counter + 1, numElements, alreadyChecked);
1340                     }
1341                 }
1342                 if (ids[counter] != null) {
1343                     String idName = ids[counter];
1344                     childMapping = parentMapping.getChildSelectorMapping(
1345                                          tagString + "#" + idName, false);
1346                     if (childMapping != null) {
1347                         getStyles(childMapping, styles, tags, ids, classes,
1348                                   counter + 1, numElements, alreadyChecked);
1349                     }
1350                     childMapping = parentMapping.getChildSelectorMapping(
1351                                    "#" + idName, false);
1352                     if (childMapping != null) {
1353                         getStyles(childMapping, styles, tags, ids, classes,
1354                                   counter + 1, numElements, alreadyChecked);
1355                     }
1356                 }
1357             }
1358         }
1359     }
1360 
1361     /**
1362      * Creates and returns a Style containing all the rules that match
1363      *  <code>selector</code>.
1364      */
1365     private synchronized Style createResolvedStyle(String selector,
1366                                       String[] tags,
1367                                       String[] ids, String[] classes) {
1368         SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1369         Vector<SelectorMapping> tempVector = sb.getVector();
1370         Hashtable<SelectorMapping, SelectorMapping> tempHashtable = sb.getHashtable();
1371         // Determine all the Styles that are appropriate, placing them
1372         // in tempVector
1373         try {
1374             SelectorMapping mapping = getRootSelectorMapping();
1375             int numElements = tags.length;
1376             String tagString = tags[0];
1377             SelectorMapping childMapping = mapping.getChildSelectorMapping(
1378                                                    tagString, false);
1379             if (childMapping != null) {
1380                 getStyles(childMapping, tempVector, tags, ids, classes, 1,
1381                           numElements, tempHashtable);
1382             }
1383             if (classes[0] != null) {
1384                 String className = classes[0];
1385                 childMapping = mapping.getChildSelectorMapping(
1386                                        tagString + "." + className, false);
1387                 if (childMapping != null) {
1388                     getStyles(childMapping, tempVector, tags, ids, classes, 1,
1389                               numElements, tempHashtable);
1390                 }
1391                 childMapping = mapping.getChildSelectorMapping(
1392                                        "." + className, false);
1393                 if (childMapping != null) {
1394                     getStyles(childMapping, tempVector, tags, ids, classes,
1395                               1, numElements, tempHashtable);
1396                 }
1397             }
1398             if (ids[0] != null) {
1399                 String idName = ids[0];
1400                 childMapping = mapping.getChildSelectorMapping(
1401                                        tagString + "#" + idName, false);
1402                 if (childMapping != null) {
1403                     getStyles(childMapping, tempVector, tags, ids, classes,
1404                               1, numElements, tempHashtable);
1405                 }
1406                 childMapping = mapping.getChildSelectorMapping(
1407                                        "#" + idName, false);
1408                 if (childMapping != null) {
1409                     getStyles(childMapping, tempVector, tags, ids, classes,
1410                               1, numElements, tempHashtable);
1411                 }
1412             }
1413             // Create a new Style that will delegate to all the matching
1414             // Styles.
1415             int numLinkedSS = (linkedStyleSheets != null) ?
1416                               linkedStyleSheets.size() : 0;
1417             int numStyles = tempVector.size();
1418             AttributeSet[] attrs = new AttributeSet[numStyles + numLinkedSS];
1419             for (int counter = 0; counter < numStyles; counter++) {
1420                 attrs[counter] = tempVector.elementAt(counter).getStyle();
1421             }
1422             // Get the AttributeSet from linked style sheets.
1423             for (int counter = 0; counter < numLinkedSS; counter++) {
1424                 AttributeSet attr = linkedStyleSheets.elementAt(counter).getRule(selector);
1425                 if (attr == null) {
1426                     attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
1427                 }
1428                 else {
1429                     attrs[counter + numStyles] = attr;
1430                 }
1431             }
1432             ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
1433                                                        numStyles);
1434             resolvedStyles.put(selector, retStyle);
1435             return retStyle;
1436         }
1437         finally {
1438             SearchBuffer.releaseSearchBuffer(sb);
1439         }
1440     }
1441 
1442     /**
1443      * Creates and returns a Style containing all the rules that
1444      * matches <code>selector</code>.
1445      *
1446      * @param elements  a Vector of all the Elements
1447      *                  the style is being asked for. The
1448      *                  first Element is the deepest Element, with the last Element
1449      *                  representing the root.
1450      * @param t         the Tag to use for
1451      *                  the first Element in <code>elements</code>
1452      */
1453     private Style createResolvedStyle(String selector, Vector elements,
1454                                       HTML.Tag t) {
1455         int numElements = elements.size();
1456         // Build three arrays, one for tags, one for class's, and one for
1457         // id's
1458         String tags[] = new String[numElements];
1459         String ids[] = new String[numElements];
1460         String classes[] = new String[numElements];
1461         for (int counter = 0; counter < numElements; counter++) {
1462             Element e = (Element)elements.elementAt(counter);
1463             AttributeSet attr = e.getAttributes();
1464             if (counter == 0 && e.isLeaf()) {
1465                 // For leafs, we use the second tier attributes.
1466                 Object testAttr = attr.getAttribute(t);
1467                 if (testAttr instanceof AttributeSet) {
1468                     attr = (AttributeSet)testAttr;
1469                 }
1470                 else {
1471                     attr = null;
1472                 }
1473             }
1474             if (attr != null) {
1475                 HTML.Tag tag = (HTML.Tag)attr.getAttribute(StyleConstants.
1476                                                            NameAttribute);
1477                 if (tag != null) {
1478                     tags[counter] = tag.toString();
1479                 }
1480                 else {
1481                     tags[counter] = null;
1482                 }
1483                 if (attr.isDefined(HTML.Attribute.CLASS)) {
1484                     classes[counter] = attr.getAttribute
1485                                       (HTML.Attribute.CLASS).toString();
1486                 }
1487                 else {
1488                     classes[counter] = null;
1489                 }
1490                 if (attr.isDefined(HTML.Attribute.ID)) {
1491                     ids[counter] = attr.getAttribute(HTML.Attribute.ID).
1492                                         toString();
1493                 }
1494                 else {
1495                     ids[counter] = null;
1496                 }
1497             }
1498             else {
1499                 tags[counter] = ids[counter] = classes[counter] = null;
1500             }
1501         }
1502         tags[0] = t.toString();
1503         return createResolvedStyle(selector, tags, ids, classes);
1504     }
1505 
1506     /**
1507      * Creates and returns a Style containing all the rules that match
1508      *  <code>selector</code>. It is assumed that each simple selector
1509      * in <code>selector</code> is separated by a space.
1510      */
1511     private Style createResolvedStyle(String selector) {
1512         SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1513         // Will contain the tags, ids, and classes, in that order.
1514         Vector<String> elements = sb.getVector();
1515         try {
1516             boolean done;
1517             int dotIndex = 0;
1518             int spaceIndex;
1519             int poundIndex = 0;
1520             int lastIndex = 0;
1521             int length = selector.length();
1522             while (lastIndex < length) {
1523                 if (dotIndex == lastIndex) {
1524                     dotIndex = selector.indexOf('.', lastIndex);
1525                 }
1526                 if (poundIndex == lastIndex) {
1527                     poundIndex = selector.indexOf('#', lastIndex);
1528                 }
1529                 spaceIndex = selector.indexOf(' ', lastIndex);
1530                 if (spaceIndex == -1) {
1531                     spaceIndex = length;
1532                 }
1533                 if (dotIndex != -1 && poundIndex != -1 &&
1534                     dotIndex < spaceIndex && poundIndex < spaceIndex) {
1535                     if (poundIndex < dotIndex) {
1536                         // #.
1537                         if (lastIndex == poundIndex) {
1538                             elements.addElement("");
1539                         }
1540                         else {
1541                             elements.addElement(selector.substring(lastIndex,
1542                                                                   poundIndex));
1543                         }
1544                         if ((dotIndex + 1) < spaceIndex) {
1545                             elements.addElement(selector.substring
1546                                                 (dotIndex + 1, spaceIndex));
1547                         }
1548                         else {
1549                             elements.addElement(null);
1550                         }
1551                         if ((poundIndex + 1) == dotIndex) {
1552                             elements.addElement(null);
1553                         }
1554                         else {
1555                             elements.addElement(selector.substring
1556                                                 (poundIndex + 1, dotIndex));
1557                         }
1558                     }
1559                     else if(poundIndex < spaceIndex) {
1560                         // .#
1561                         if (lastIndex == dotIndex) {
1562                             elements.addElement("");
1563                         }
1564                         else {
1565                             elements.addElement(selector.substring(lastIndex,
1566                                                                   dotIndex));
1567                         }
1568                         if ((dotIndex + 1) < poundIndex) {
1569                             elements.addElement(selector.substring
1570                                                 (dotIndex + 1, poundIndex));
1571                         }
1572                         else {
1573                             elements.addElement(null);
1574                         }
1575                         if ((poundIndex + 1) == spaceIndex) {
1576                             elements.addElement(null);
1577                         }
1578                         else {
1579                             elements.addElement(selector.substring
1580                                                 (poundIndex + 1, spaceIndex));
1581                         }
1582                     }
1583                     dotIndex = poundIndex = spaceIndex + 1;
1584                 }
1585                 else if (dotIndex != -1 && dotIndex < spaceIndex) {
1586                     // .
1587                     if (dotIndex == lastIndex) {
1588                         elements.addElement("");
1589                     }
1590                     else {
1591                         elements.addElement(selector.substring(lastIndex,
1592                                                                dotIndex));
1593                     }
1594                     if ((dotIndex + 1) == spaceIndex) {
1595                         elements.addElement(null);
1596                     }
1597                     else {
1598                         elements.addElement(selector.substring(dotIndex + 1,
1599                                                                spaceIndex));
1600                     }
1601                     elements.addElement(null);
1602                     dotIndex = spaceIndex + 1;
1603                 }
1604                 else if (poundIndex != -1 && poundIndex < spaceIndex) {
1605                     // #
1606                     if (poundIndex == lastIndex) {
1607                         elements.addElement("");
1608                     }
1609                     else {
1610                         elements.addElement(selector.substring(lastIndex,
1611                                                                poundIndex));
1612                     }
1613                     elements.addElement(null);
1614                     if ((poundIndex + 1) == spaceIndex) {
1615                         elements.addElement(null);
1616                     }
1617                     else {
1618                         elements.addElement(selector.substring(poundIndex + 1,
1619                                                                spaceIndex));
1620                     }
1621                     poundIndex = spaceIndex + 1;
1622                 }
1623                 else {
1624                     // id
1625                     elements.addElement(selector.substring(lastIndex,
1626                                                            spaceIndex));
1627                     elements.addElement(null);
1628                     elements.addElement(null);
1629                 }
1630                 lastIndex = spaceIndex + 1;
1631             }
1632             // Create the tag, id, and class arrays.
1633             int total = elements.size();
1634             int numTags = total / 3;
1635             String[] tags = new String[numTags];
1636             String[] ids = new String[numTags];
1637             String[] classes = new String[numTags];
1638             for (int index = 0, eIndex = total - 3; index < numTags;
1639                  index++, eIndex -= 3) {
1640                 tags[index] = elements.elementAt(eIndex);
1641                 classes[index] = elements.elementAt(eIndex + 1);
1642                 ids[index] = elements.elementAt(eIndex + 2);
1643             }
1644             return createResolvedStyle(selector, tags, ids, classes);
1645         }
1646         finally {
1647             SearchBuffer.releaseSearchBuffer(sb);
1648         }
1649     }
1650 
1651     /**
1652      * Should be invoked when a new rule is added that did not previously
1653      * exist. Goes through and refreshes the necessary resolved
1654      * rules.
1655      */
1656     private synchronized void refreshResolvedRules(String selectorName,
1657                                                    String[] selector,
1658                                                    Style newStyle,
1659                                                    int specificity) {
1660         if (resolvedStyles.size() > 0) {
1661             Enumeration<ResolvedStyle> values = resolvedStyles.elements();
1662             while (values.hasMoreElements()) {
1663                 ResolvedStyle style = values.nextElement();
1664                 if (style.matches(selectorName)) {
1665                     style.insertStyle(newStyle, specificity);
1666                 }
1667             }
1668         }
1669     }
1670 
1671 
1672     /**
1673      * A temporary class used to hold a Vector, a StringBuffer and a
1674      * Hashtable. This is used to avoid allocing a lot of garbage when
1675      * searching for rules. Use the static method obtainSearchBuffer and
1676      * releaseSearchBuffer to get a SearchBuffer, and release it when
1677      * done.
1678      */
1679     private static class SearchBuffer {
1680         /** A stack containing instances of SearchBuffer. Used in getting
1681          * rules. */
1682         static Stack<SearchBuffer> searchBuffers = new Stack<SearchBuffer>();
1683         // A set of temporary variables that can be used in whatever way.
1684         Vector vector = null;
1685         StringBuffer stringBuffer = null;
1686         Hashtable hashtable = null;
1687 
1688         /**
1689          * Returns an instance of SearchBuffer. Be sure and issue
1690          * a releaseSearchBuffer when done with it.
1691          */
1692         static SearchBuffer obtainSearchBuffer() {
1693             SearchBuffer sb;
1694             try {
1695                 if(!searchBuffers.empty()) {
1696                    sb = searchBuffers.pop();
1697                 } else {
1698                    sb = new SearchBuffer();
1699                 }
1700             } catch (EmptyStackException ese) {
1701                 sb = new SearchBuffer();
1702             }
1703             return sb;
1704         }
1705 
1706         /**
1707          * Adds <code>sb</code> to the stack of SearchBuffers that can
1708          * be used.
1709          */
1710         static void releaseSearchBuffer(SearchBuffer sb) {
1711             sb.empty();
1712             searchBuffers.push(sb);
1713         }
1714 
1715         StringBuffer getStringBuffer() {
1716             if (stringBuffer == null) {
1717                 stringBuffer = new StringBuffer();
1718             }
1719             return stringBuffer;
1720         }
1721 
1722         Vector getVector() {
1723             if (vector == null) {
1724                 vector = new Vector();
1725             }
1726             return vector;
1727         }
1728 
1729         Hashtable getHashtable() {
1730             if (hashtable == null) {
1731                 hashtable = new Hashtable();
1732             }
1733             return hashtable;
1734         }
1735 
1736         void empty() {
1737             if (stringBuffer != null) {
1738                 stringBuffer.setLength(0);
1739             }
1740             if (vector != null) {
1741                 vector.removeAllElements();
1742             }
1743             if (hashtable != null) {
1744                 hashtable.clear();
1745             }
1746         }
1747     }
1748 
1749 
1750     static final Border noBorder = new EmptyBorder(0,0,0,0);
1751 
1752     /**
1753      * Class to carry out some of the duties of
1754      * CSS formatting.  Implementations of this
1755      * class enable views to present the CSS formatting
1756      * while not knowing anything about how the CSS values
1757      * are being cached.
1758      * <p>
1759      * As a delegate of Views, this object is responsible for
1760      * the insets of a View and making sure the background
1761      * is maintained according to the CSS attributes.
1762      */
1763     public static class BoxPainter implements Serializable {
1764 
1765         BoxPainter(AttributeSet a, CSS css, StyleSheet ss) {
1766             this.ss = ss;
1767             this.css = css;
1768             border = getBorder(a);
1769             binsets = border.getBorderInsets(null);
1770             topMargin = getLength(CSS.Attribute.MARGIN_TOP, a);
1771             bottomMargin = getLength(CSS.Attribute.MARGIN_BOTTOM, a);
1772             leftMargin = getLength(CSS.Attribute.MARGIN_LEFT, a);
1773             rightMargin = getLength(CSS.Attribute.MARGIN_RIGHT, a);
1774             bg = ss.getBackground(a);
1775             if (ss.getBackgroundImage(a) != null) {
1776                 bgPainter = new BackgroundImagePainter(a, css, ss);
1777             }
1778         }
1779 
1780         /**
1781          * Fetches a border to render for the given attributes.
1782          * PENDING(prinz) This is pretty badly hacked at the
1783          * moment.
1784          */
1785         Border getBorder(AttributeSet a) {
1786             return new CSSBorder(a);
1787         }
1788 
1789         /**
1790          * Fetches the color to use for borders.  This will either be
1791          * the value specified by the border-color attribute (which
1792          * is not inherited), or it will default to the color attribute
1793          * (which is inherited).
1794          */
1795         Color getBorderColor(AttributeSet a) {
1796             Color color = css.getColor(a, CSS.Attribute.BORDER_COLOR);
1797             if (color == null) {
1798                 color = css.getColor(a, CSS.Attribute.COLOR);
1799                 if (color == null) {
1800                     return Color.black;
1801                 }
1802             }
1803             return color;
1804         }
1805 
1806         /**
1807          * Fetches the inset needed on a given side to
1808          * account for the margin, border, and padding.
1809          *
1810          * @param side The size of the box to fetch the
1811          *  inset for.  This can be View.TOP,
1812          *  View.LEFT, View.BOTTOM, or View.RIGHT.
1813          * @param v the view making the request.  This is
1814          *  used to get the AttributeSet, and may be used to
1815          *  resolve percentage arguments.
1816          * @exception IllegalArgumentException for an invalid direction
1817          */
1818         public float getInset(int side, View v) {
1819             AttributeSet a = v.getAttributes();
1820             float inset = 0;
1821             switch(side) {
1822             case View.LEFT:
1823                 inset += getOrientationMargin(HorizontalMargin.LEFT,
1824                                               leftMargin, a, isLeftToRight(v));
1825                 inset += binsets.left;
1826                 inset += getLength(CSS.Attribute.PADDING_LEFT, a);
1827                 break;
1828             case View.RIGHT:
1829                 inset += getOrientationMargin(HorizontalMargin.RIGHT,
1830                                               rightMargin, a, isLeftToRight(v));
1831                 inset += binsets.right;
1832                 inset += getLength(CSS.Attribute.PADDING_RIGHT, a);
1833                 break;
1834             case View.TOP:
1835                 inset += topMargin;
1836                 inset += binsets.top;
1837                 inset += getLength(CSS.Attribute.PADDING_TOP, a);
1838                 break;
1839             case View.BOTTOM:
1840                 inset += bottomMargin;
1841                 inset += binsets.bottom;
1842                 inset += getLength(CSS.Attribute.PADDING_BOTTOM, a);
1843                 break;
1844             default:
1845                 throw new IllegalArgumentException("Invalid side: " + side);
1846             }
1847             return inset;
1848         }
1849 
1850         /**
1851          * Paints the CSS box according to the attributes
1852          * given.  This should paint the border, padding,
1853          * and background.
1854          *
1855          * @param g the rendering surface.
1856          * @param x the x coordinate of the allocated area to
1857          *  render into.
1858          * @param y the y coordinate of the allocated area to
1859          *  render into.
1860          * @param w the width of the allocated area to render into.
1861          * @param h the height of the allocated area to render into.
1862          * @param v the view making the request.  This is
1863          *  used to get the AttributeSet, and may be used to
1864          *  resolve percentage arguments.
1865          */
1866         public void paint(Graphics g, float x, float y, float w, float h, View v) {
1867             // PENDING(prinz) implement real rendering... which would
1868             // do full set of border and background capabilities.
1869             // remove margin
1870 
1871             float dx = 0;
1872             float dy = 0;
1873             float dw = 0;
1874             float dh = 0;
1875             AttributeSet a = v.getAttributes();
1876             boolean isLeftToRight = isLeftToRight(v);
1877             float localLeftMargin = getOrientationMargin(HorizontalMargin.LEFT,
1878                                                          leftMargin,
1879                                                          a, isLeftToRight);
1880             float localRightMargin = getOrientationMargin(HorizontalMargin.RIGHT,
1881                                                           rightMargin,
1882                                                           a, isLeftToRight);
1883             if (!(v instanceof HTMLEditorKit.HTMLFactory.BodyBlockView)) {
1884                 dx = localLeftMargin;
1885                 dy = topMargin;
1886                 dw = -(localLeftMargin + localRightMargin);
1887                 dh = -(topMargin + bottomMargin);
1888             }
1889             if (bg != null) {
1890                 g.setColor(bg);
1891                 g.fillRect((int) (x + dx),
1892                            (int) (y + dy),
1893                            (int) (w + dw),
1894                            (int) (h + dh));
1895             }
1896             if (bgPainter != null) {
1897                 bgPainter.paint(g, x + dx, y + dy, w + dw, h + dh, v);
1898             }
1899             x += localLeftMargin;
1900             y += topMargin;
1901             w -= localLeftMargin + localRightMargin;
1902             h -= topMargin + bottomMargin;
1903             if (border instanceof BevelBorder) {
1904                 //BevelBorder does not support border width
1905                 int bw = (int) getLength(CSS.Attribute.BORDER_TOP_WIDTH, a);
1906                 for (int i = bw - 1; i >= 0; i--) {
1907                     border.paintBorder(null, g, (int) x + i, (int) y + i,
1908                                        (int) w - 2 * i, (int) h - 2 * i);
1909                 }
1910             } else {
1911                 border.paintBorder(null, g, (int) x, (int) y, (int) w, (int) h);
1912             }
1913         }
1914 
1915         float getLength(CSS.Attribute key, AttributeSet a) {
1916             return css.getLength(a, key, ss);
1917         }
1918 
1919         static boolean isLeftToRight(View v) {
1920             boolean ret = true;
1921             if (isOrientationAware(v)) {
1922                 Container container;
1923                 if (v != null && (container = v.getContainer()) != null) {
1924                     ret = container.getComponentOrientation().isLeftToRight();
1925                 }
1926             }
1927             return ret;
1928         }
1929 
1930         /*
1931          * only certain tags are concerned about orientation
1932          * <dir>, <menu>, <ul>, <ol>
1933          * for all others we return true. It is implemented this way
1934          * for performance purposes
1935          */
1936         static boolean isOrientationAware(View v) {
1937             boolean ret = false;
1938             AttributeSet attr;
1939             Object obj;
1940             if (v != null
1941                 && (attr = v.getElement().getAttributes()) != null
1942                 && (obj = attr.getAttribute(StyleConstants.NameAttribute)) instanceof HTML.Tag
1943                 && (obj == HTML.Tag.DIR
1944                     || obj == HTML.Tag.MENU
1945                     || obj == HTML.Tag.UL
1946                     || obj == HTML.Tag.OL)) {
1947                 ret = true;
1948             }
1949 
1950             return ret;
1951         }
1952 
1953         static enum HorizontalMargin { LEFT, RIGHT }
1954 
1955         /**
1956          * for <dir>, <menu>, <ul> etc.
1957          * margins are Left-To-Right/Right-To-Left depended.
1958          * see 5088268 for more details
1959          * margin-(left|right)-(ltr|rtl) were introduced to describe it
1960          * if margin-(left|right) is present we are to use it.
1961          *
1962          * @param side The horizontal side to fetch margin for
1963          *  This can be HorizontalMargin.LEFT or HorizontalMargin.RIGHT
1964          * @param cssMargin margin from css
1965          * @param a AttributeSet for the View we getting margin for
1966          * @param isLeftToRight
1967          * @return orientation depended margin
1968          */
1969         float getOrientationMargin(HorizontalMargin side, float cssMargin,
1970                                    AttributeSet a, boolean isLeftToRight) {
1971             float margin = cssMargin;
1972             float orientationMargin = cssMargin;
1973             Object cssMarginValue = null;
1974             switch (side) {
1975             case RIGHT:
1976                 {
1977                     orientationMargin = (isLeftToRight) ?
1978                         getLength(CSS.Attribute.MARGIN_RIGHT_LTR, a) :
1979                         getLength(CSS.Attribute.MARGIN_RIGHT_RTL, a);
1980                     cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_RIGHT);
1981                 }
1982                 break;
1983             case LEFT :
1984                 {
1985                     orientationMargin = (isLeftToRight) ?
1986                         getLength(CSS.Attribute.MARGIN_LEFT_LTR, a) :
1987                         getLength(CSS.Attribute.MARGIN_LEFT_RTL, a);
1988                     cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_LEFT);
1989                 }
1990                 break;
1991             }
1992 
1993             if (cssMarginValue == null
1994                 && orientationMargin != Integer.MIN_VALUE) {
1995                 margin = orientationMargin;
1996             }
1997             return margin;
1998         }
1999 
2000         float topMargin;
2001         float bottomMargin;
2002         float leftMargin;
2003         float rightMargin;
2004         // Bitmask, used to indicate what margins are relative:
2005         // bit 0 for top, 1 for bottom, 2 for left and 3 for right.
2006         short marginFlags;
2007         Border border;
2008         Insets binsets;
2009         CSS css;
2010         StyleSheet ss;
2011         Color bg;
2012         BackgroundImagePainter bgPainter;
2013     }
2014 
2015     /**
2016      * Class to carry out some of the duties of CSS list
2017      * formatting.  Implementations of this
2018      * class enable views to present the CSS formatting
2019      * while not knowing anything about how the CSS values
2020      * are being cached.
2021      */
2022     public static class ListPainter implements Serializable {
2023 
2024         ListPainter(AttributeSet attr, StyleSheet ss) {
2025             this.ss = ss;
2026             /* Get the image to use as a list bullet */
2027             String imgstr = (String)attr.getAttribute(CSS.Attribute.
2028                                                       LIST_STYLE_IMAGE);
2029             type = null;
2030             if (imgstr != null && !imgstr.equals("none")) {
2031                 String tmpstr = null;
2032                 try {
2033                     StringTokenizer st = new StringTokenizer(imgstr, "()");
2034                     if (st.hasMoreTokens())
2035                         tmpstr = st.nextToken();
2036                     if (st.hasMoreTokens())
2037                         tmpstr = st.nextToken();
2038                     URL u = new URL(tmpstr);
2039                     img = new ImageIcon(u);
2040                 } catch (MalformedURLException e) {
2041                     if (tmpstr != null && ss != null && ss.getBase() != null) {
2042                         try {
2043                             URL u = new URL(ss.getBase(), tmpstr);
2044                             img = new ImageIcon(u);
2045                         } catch (MalformedURLException murle) {
2046                             img = null;
2047                         }
2048                     }
2049                     else {
2050                         img = null;
2051                     }
2052                 }
2053             }
2054 
2055             /* Get the type of bullet to use in the list */
2056             if (img == null) {
2057                 type = (CSS.Value)attr.getAttribute(CSS.Attribute.
2058                                                     LIST_STYLE_TYPE);
2059             }
2060             start = 1;
2061 
2062             paintRect = new Rectangle();
2063         }
2064 
2065         /**
2066          * Returns a string that represents the value
2067          * of the HTML.Attribute.TYPE attribute.
2068          * If this attributes is not defined, then
2069          * then the type defaults to "disc" unless
2070          * the tag is on Ordered list.  In the case
2071          * of the latter, the default type is "decimal".
2072          */
2073         private CSS.Value getChildType(View childView) {
2074             CSS.Value childtype = (CSS.Value)childView.getAttributes().
2075                                   getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
2076 
2077             if (childtype == null) {
2078                 if (type == null) {
2079                     // Parent view.
2080                     View v = childView.getParent();
2081                     HTMLDocument doc = (HTMLDocument)v.getDocument();
2082                     if (doc.matchNameAttribute(v.getElement().getAttributes(),
2083                                                HTML.Tag.OL)) {
2084                         childtype = CSS.Value.DECIMAL;
2085                     } else {
2086                         childtype = CSS.Value.DISC;
2087                     }
2088                 } else {
2089                     childtype = type;
2090                 }
2091             }
2092             return childtype;
2093         }
2094 
2095         /**
2096          * Obtains the starting index from <code>parent</code>.
2097          */
2098         private void getStart(View parent) {
2099             checkedForStart = true;
2100             Element element = parent.getElement();
2101             if (element != null) {
2102                 AttributeSet attr = element.getAttributes();
2103                 Object startValue;
2104                 if (attr != null && attr.isDefined(HTML.Attribute.START) &&
2105                     (startValue = attr.getAttribute
2106                      (HTML.Attribute.START)) != null &&
2107                     (startValue instanceof String)) {
2108 
2109                     try {
2110                         start = Integer.parseInt((String)startValue);
2111                     }
2112                     catch (NumberFormatException nfe) {}
2113                 }
2114             }
2115         }
2116 
2117         /**
2118          * Returns an integer that should be used to render the child at
2119          * <code>childIndex</code> with. The retValue will usually be
2120          * <code>childIndex</code> + 1, unless <code>parentView</code>
2121          * has some Views that do not represent LI's, or one of the views
2122          * has a HTML.Attribute.START specified.
2123          */
2124         private int getRenderIndex(View parentView, int childIndex) {
2125             if (!checkedForStart) {
2126                 getStart(parentView);
2127             }
2128             int retIndex = childIndex;
2129             for (int counter = childIndex; counter >= 0; counter--) {
2130                 AttributeSet as = parentView.getElement().getElement(counter).
2131                                   getAttributes();
2132                 if (as.getAttribute(StyleConstants.NameAttribute) !=
2133                     HTML.Tag.LI) {
2134                     retIndex--;
2135                 } else if (as.isDefined(HTML.Attribute.VALUE)) {
2136                     Object value = as.getAttribute(HTML.Attribute.VALUE);
2137                     if (value != null &&
2138                         (value instanceof String)) {
2139                         try {
2140                             int iValue = Integer.parseInt((String)value);
2141                             return retIndex - counter + iValue;
2142                         }
2143                         catch (NumberFormatException nfe) {}
2144                     }
2145                 }
2146             }
2147             return retIndex + start;
2148         }
2149 
2150         /**
2151          * Paints the CSS list decoration according to the
2152          * attributes given.
2153          *
2154          * @param g the rendering surface.
2155          * @param x the x coordinate of the list item allocation
2156          * @param y the y coordinate of the list item allocation
2157          * @param w the width of the list item allocation
2158          * @param h the height of the list item allocation
2159          * @param v the allocated area to paint into.
2160          * @param item which list item is being painted.  This
2161          *  is a number greater than or equal to 0.
2162          */
2163         public void paint(Graphics g, float x, float y, float w, float h, View v, int item) {
2164             View cv = v.getView(item);
2165             Container host = v.getContainer();
2166             Object name = cv.getElement().getAttributes().getAttribute
2167                          (StyleConstants.NameAttribute);
2168             // Only draw something if the View is a list item. This won't
2169             // be the case for comments.
2170             if (!(name instanceof HTML.Tag) ||
2171                 name != HTML.Tag.LI) {
2172                 return;
2173             }
2174             // deside on what side draw bullets, etc.
2175             isLeftToRight =
2176                 host.getComponentOrientation().isLeftToRight();
2177 
2178             // How the list indicator is aligned is not specified, it is
2179             // left up to the UA. IE and NS differ on this behavior.
2180             // This is closer to NS where we align to the first line of text.
2181             // If the child is not text we draw the indicator at the
2182             // origin (0).
2183             float align = 0;
2184             if (cv.getViewCount() > 0) {
2185                 View pView = cv.getView(0);
2186                 Object cName = pView.getElement().getAttributes().
2187                                getAttribute(StyleConstants.NameAttribute);
2188                 if ((cName == HTML.Tag.P || cName == HTML.Tag.IMPLIED) &&
2189                               pView.getViewCount() > 0) {
2190                     paintRect.setBounds((int)x, (int)y, (int)w, (int)h);
2191                     Shape shape = cv.getChildAllocation(0, paintRect);
2192                     if (shape != null && (shape = pView.getView(0).
2193                                  getChildAllocation(0, shape)) != null) {
2194                         Rectangle rect = (shape instanceof Rectangle) ?
2195                                          (Rectangle)shape : shape.getBounds();
2196 
2197                         align = pView.getView(0).getAlignment(View.Y_AXIS);
2198                         y = rect.y;
2199                         h = rect.height;
2200                     }
2201                 }
2202             }
2203 
2204             // set the color of a decoration
2205             Color c = (host.isEnabled()
2206                 ? (ss != null
2207                     ? ss.getForeground(cv.getAttributes())
2208                     : host.getForeground())
2209                 : UIManager.getColor("textInactiveText"));
2210             g.setColor(c);
2211 
2212             if (img != null) {
2213                 drawIcon(g, (int) x, (int) y, (int) w, (int) h, align, host);
2214                 return;
2215             }
2216             CSS.Value childtype = getChildType(cv);
2217             Font font = ((StyledDocument)cv.getDocument()).
2218                                          getFont(cv.getAttributes());
2219             if (font != null) {
2220                 g.setFont(font);
2221             }
2222             if (childtype == CSS.Value.SQUARE || childtype == CSS.Value.CIRCLE
2223                 || childtype == CSS.Value.DISC) {
2224                 drawShape(g, childtype, (int) x, (int) y,
2225                           (int) w, (int) h, align);
2226             } else if (childtype == CSS.Value.DECIMAL) {
2227                 drawLetter(g, '1', (int) x, (int) y, (int) w, (int) h, align,
2228                            getRenderIndex(v, item));
2229             } else if (childtype == CSS.Value.LOWER_ALPHA) {
2230                 drawLetter(g, 'a', (int) x, (int) y, (int) w, (int) h, align,
2231                            getRenderIndex(v, item));
2232             } else if (childtype == CSS.Value.UPPER_ALPHA) {
2233                 drawLetter(g, 'A', (int) x, (int) y, (int) w, (int) h, align,
2234                            getRenderIndex(v, item));
2235             } else if (childtype == CSS.Value.LOWER_ROMAN) {
2236                 drawLetter(g, 'i', (int) x, (int) y, (int) w, (int) h, align,
2237                            getRenderIndex(v, item));
2238             } else if (childtype == CSS.Value.UPPER_ROMAN) {
2239                 drawLetter(g, 'I', (int) x, (int) y, (int) w, (int) h, align,
2240                            getRenderIndex(v, item));
2241             }
2242         }
2243 
2244         /**
2245          * Draws the bullet icon specified by the list-style-image argument.
2246          *
2247          * @param g     the graphics context
2248          * @param ax    x coordinate to place the bullet
2249          * @param ay    y coordinate to place the bullet
2250          * @param aw    width of the container the bullet is placed in
2251          * @param ah    height of the container the bullet is placed in
2252          * @param align preferred alignment factor for the child view
2253          */
2254         void drawIcon(Graphics g, int ax, int ay, int aw, int ah,
2255                       float align, Component c) {
2256             // Align to bottom of icon.
2257             int gap = isLeftToRight ? - (img.getIconWidth() + bulletgap) :
2258                                         (aw + bulletgap);
2259             int x = ax + gap;
2260             int y = Math.max(ay, ay + (int)(align * ah) -img.getIconHeight());
2261 
2262             img.paintIcon(c, g, x, y);
2263         }
2264 
2265         /**
2266          * Draws the graphical bullet item specified by the type argument.
2267          *
2268          * @param g     the graphics context
2269          * @param type  type of bullet to draw (circle, square, disc)
2270          * @param ax    x coordinate to place the bullet
2271          * @param ay    y coordinate to place the bullet
2272          * @param aw    width of the container the bullet is placed in
2273          * @param ah    height of the container the bullet is placed in
2274          * @param align preferred alignment factor for the child view
2275          */
2276         void drawShape(Graphics g, CSS.Value type, int ax, int ay, int aw,
2277                        int ah, float align) {
2278             // Align to bottom of shape.
2279             int gap = isLeftToRight ? - (bulletgap + 8) : (aw + bulletgap);
2280             int x = ax + gap;
2281             int y = Math.max(ay, ay + (int)(align * ah) - 8);
2282 
2283             if (type == CSS.Value.SQUARE) {
2284                 g.drawRect(x, y, 8, 8);
2285             } else if (type == CSS.Value.CIRCLE) {
2286                 g.drawOval(x, y, 8, 8);
2287             } else {
2288                 g.fillOval(x, y, 8, 8);
2289             }
2290         }
2291 
2292         /**
2293          * Draws the letter or number for an ordered list.
2294          *
2295          * @param g     the graphics context
2296          * @param letter type of ordered list to draw
2297          * @param ax    x coordinate to place the bullet
2298          * @param ay    y coordinate to place the bullet
2299          * @param aw    width of the container the bullet is placed in
2300          * @param ah    height of the container the bullet is placed in
2301          * @param index position of the list item in the list
2302          */
2303         void drawLetter(Graphics g, char letter, int ax, int ay, int aw,
2304                         int ah, float align, int index) {
2305             String str = formatItemNum(index, letter);
2306             str = isLeftToRight ? str + "." : "." + str;
2307             FontMetrics fm = SwingUtilities2.getFontMetrics(null, g);
2308             int stringwidth = SwingUtilities2.stringWidth(null, fm, str);
2309             int gap = isLeftToRight ? - (stringwidth + bulletgap) :
2310                                         (aw + bulletgap);
2311             int x = ax + gap;
2312             int y = Math.max(ay + fm.getAscent(), ay + (int)(ah * align));
2313             SwingUtilities2.drawString(null, g, str, x, y);
2314         }
2315 
2316         /**
2317          * Converts the item number into the ordered list number
2318          * (i.e.  1 2 3, i ii iii, a b c, etc.
2319          *
2320          * @param itemNum number to format
2321          * @param type    type of ordered list
2322          */
2323         String formatItemNum(int itemNum, char type) {
2324             String numStyle = "1";
2325 
2326             boolean uppercase = false;
2327 
2328             String formattedNum;
2329 
2330             switch (type) {
2331             case '1':
2332             default:
2333                 formattedNum = String.valueOf(itemNum);
2334                 break;
2335 
2336             case 'A':
2337                 uppercase = true;
2338                 // fall through
2339             case 'a':
2340                 formattedNum = formatAlphaNumerals(itemNum);
2341                 break;
2342 
2343             case 'I':
2344                 uppercase = true;
2345                 // fall through
2346             case 'i':
2347                 formattedNum = formatRomanNumerals(itemNum);
2348             }
2349 
2350             if (uppercase) {
2351                 formattedNum = formattedNum.toUpperCase();
2352             }
2353 
2354             return formattedNum;
2355         }
2356 
2357         /**
2358          * Converts the item number into an alphabetic character
2359          *
2360          * @param itemNum number to format
2361          */
2362         String formatAlphaNumerals(int itemNum) {
2363             String result;
2364 
2365             if (itemNum > 26) {
2366                 result = formatAlphaNumerals(itemNum / 26) +
2367                     formatAlphaNumerals(itemNum % 26);
2368             } else {
2369                 // -1 because item is 1 based.
2370                 result = String.valueOf((char)('a' + itemNum - 1));
2371             }
2372 
2373             return result;
2374         }
2375 
2376         /* list of roman numerals */
2377         static final char romanChars[][] = {
2378             {'i', 'v'},
2379             {'x', 'l' },
2380             {'c', 'd' },
2381             {'m', '?' },
2382         };
2383 
2384         /**
2385          * Converts the item number into a roman numeral
2386          *
2387          * @param num  number to format
2388          */
2389         String formatRomanNumerals(int num) {
2390             return formatRomanNumerals(0, num);
2391         }
2392 
2393         /**
2394          * Converts the item number into a roman numeral
2395          *
2396          * @param num  number to format
2397          */
2398         String formatRomanNumerals(int level, int num) {
2399             if (num < 10) {
2400                 return formatRomanDigit(level, num);
2401             } else {
2402                 return formatRomanNumerals(level + 1, num / 10) +
2403                     formatRomanDigit(level, num % 10);
2404             }
2405         }
2406 
2407 
2408         /**
2409          * Converts the item number into a roman numeral
2410          *
2411          * @param level position
2412          * @param digit digit to format
2413          */
2414         String formatRomanDigit(int level, int digit) {
2415             String result = "";
2416             if (digit == 9) {
2417                 result = result + romanChars[level][0];
2418                 result = result + romanChars[level + 1][0];
2419                 return result;
2420             } else if (digit == 4) {
2421                 result = result + romanChars[level][0];
2422                 result = result + romanChars[level][1];
2423                 return result;
2424             } else if (digit >= 5) {
2425                 result = result + romanChars[level][1];
2426                 digit -= 5;
2427             }
2428 
2429             for (int i = 0; i < digit; i++) {
2430                 result = result + romanChars[level][0];
2431             }
2432 
2433             return result;
2434         }
2435 
2436         private Rectangle paintRect;
2437         private boolean checkedForStart;
2438         private int start;
2439         private CSS.Value type;
2440         URL imageurl;
2441         private StyleSheet ss = null;
2442         Icon img = null;
2443         private int bulletgap = 5;
2444         private boolean isLeftToRight;
2445     }
2446 
2447 
2448     /**
2449      * Paints the background image.
2450      */
2451     static class BackgroundImagePainter implements Serializable {
2452         ImageIcon   backgroundImage;
2453         float       hPosition;
2454         float       vPosition;
2455         // bit mask: 0 for repeat x, 1 for repeat y, 2 for horiz relative,
2456         // 3 for vert relative
2457         short       flags;
2458         // These are used when painting, updatePaintCoordinates updates them.
2459         private int paintX;
2460         private int paintY;
2461         private int paintMaxX;
2462         private int paintMaxY;
2463 
2464         BackgroundImagePainter(AttributeSet a, CSS css, StyleSheet ss) {
2465             backgroundImage = ss.getBackgroundImage(a);
2466             // Determine the position.
2467             CSS.BackgroundPosition pos = (CSS.BackgroundPosition)a.getAttribute
2468                                            (CSS.Attribute.BACKGROUND_POSITION);
2469             if (pos != null) {
2470                 hPosition = pos.getHorizontalPosition();
2471                 vPosition = pos.getVerticalPosition();
2472                 if (pos.isHorizontalPositionRelativeToSize()) {
2473                     flags |= 4;
2474                 }
2475                 else if (pos.isHorizontalPositionRelativeToSize()) {
2476                     hPosition *= css.getFontSize(a, 12, ss);
2477                 }
2478                 if (pos.isVerticalPositionRelativeToSize()) {
2479                     flags |= 8;
2480                 }
2481                 else if (pos.isVerticalPositionRelativeToFontSize()) {
2482                     vPosition *= css.getFontSize(a, 12, ss);
2483                 }
2484             }
2485             // Determine any repeating values.
2486             CSS.Value repeats = (CSS.Value)a.getAttribute(CSS.Attribute.
2487                                                           BACKGROUND_REPEAT);
2488             if (repeats == null || repeats == CSS.Value.BACKGROUND_REPEAT) {
2489                 flags |= 3;
2490             }
2491             else if (repeats == CSS.Value.BACKGROUND_REPEAT_X) {
2492                 flags |= 1;
2493             }
2494             else if (repeats == CSS.Value.BACKGROUND_REPEAT_Y) {
2495                 flags |= 2;
2496             }
2497         }
2498 
2499         void paint(Graphics g, float x, float y, float w, float h, View v) {
2500             Rectangle clip = g.getClipRect();
2501             if (clip != null) {
2502                 // Constrain the clip so that images don't draw outside the
2503                 // legal bounds.
2504                 g.clipRect((int)x, (int)y, (int)w, (int)h);
2505             }
2506             if ((flags & 3) == 0) {
2507                 // no repeating
2508                 int width = backgroundImage.getIconWidth();
2509                 int height = backgroundImage.getIconWidth();
2510                 if ((flags & 4) == 4) {
2511                     paintX = (int)(x + w * hPosition -
2512                                   (float)width * hPosition);
2513                 }
2514                 else {
2515                     paintX = (int)x + (int)hPosition;
2516                 }
2517                 if ((flags & 8) == 8) {
2518                     paintY = (int)(y + h * vPosition -
2519                                   (float)height * vPosition);
2520                 }
2521                 else {
2522                     paintY = (int)y + (int)vPosition;
2523                 }
2524                 if (clip == null ||
2525                     !((paintX + width <= clip.x) ||
2526                       (paintY + height <= clip.y) ||
2527                       (paintX >= clip.x + clip.width) ||
2528                       (paintY >= clip.y + clip.height))) {
2529                     backgroundImage.paintIcon(null, g, paintX, paintY);
2530                 }
2531             }
2532             else {
2533                 int width = backgroundImage.getIconWidth();
2534                 int height = backgroundImage.getIconHeight();
2535                 if (width > 0 && height > 0) {
2536                     paintX = (int)x;
2537                     paintY = (int)y;
2538                     paintMaxX = (int)(x + w);
2539                     paintMaxY = (int)(y + h);
2540                     if (updatePaintCoordinates(clip, width, height)) {
2541                         while (paintX < paintMaxX) {
2542                             int ySpot = paintY;
2543                             while (ySpot < paintMaxY) {
2544                                 backgroundImage.paintIcon(null, g, paintX,
2545                                                           ySpot);
2546                                 ySpot += height;
2547                             }
2548                             paintX += width;
2549                         }
2550                     }
2551                 }
2552             }
2553             if (clip != null) {
2554                 // Reset clip.
2555                 g.setClip(clip.x, clip.y, clip.width, clip.height);
2556             }
2557         }
2558 
2559         private boolean updatePaintCoordinates
2560                  (Rectangle clip, int width, int height){
2561             if ((flags & 3) == 1) {
2562                 paintMaxY = paintY + 1;
2563             }
2564             else if ((flags & 3) == 2) {
2565                 paintMaxX = paintX + 1;
2566             }
2567             if (clip != null) {
2568                 if ((flags & 3) == 1 && ((paintY + height <= clip.y) ||
2569                                          (paintY > clip.y + clip.height))) {
2570                     // not visible.
2571                     return false;
2572                 }
2573                 if ((flags & 3) == 2 && ((paintX + width <= clip.x) ||
2574                                          (paintX > clip.x + clip.width))) {
2575                     // not visible.
2576                     return false;
2577                 }
2578                 if ((flags & 1) == 1) {
2579                     if ((clip.x + clip.width) < paintMaxX) {
2580                         if ((clip.x + clip.width - paintX) % width == 0) {
2581                             paintMaxX = clip.x + clip.width;
2582                         }
2583                         else {
2584                             paintMaxX = ((clip.x + clip.width - paintX) /
2585                                          width + 1) * width + paintX;
2586                         }
2587                     }
2588                     if (clip.x > paintX) {
2589                         paintX = (clip.x - paintX) / width * width + paintX;
2590                     }
2591                 }
2592                 if ((flags & 2) == 2) {
2593                     if ((clip.y + clip.height) < paintMaxY) {
2594                         if ((clip.y + clip.height - paintY) % height == 0) {
2595                             paintMaxY = clip.y + clip.height;
2596                         }
2597                         else {
2598                             paintMaxY = ((clip.y + clip.height - paintY) /
2599                                          height + 1) * height + paintY;
2600                         }
2601                     }
2602                     if (clip.y > paintY) {
2603                         paintY = (clip.y - paintY) / height * height + paintY;
2604                     }
2605                 }
2606             }
2607             // Valid
2608             return true;
2609         }
2610     }
2611 
2612 
2613     /**
2614      * A subclass of MuxingAttributeSet that translates between
2615      * CSS and HTML and StyleConstants. The AttributeSets used are
2616      * the CSS rules that match the Views Elements.
2617      */
2618     class ViewAttributeSet extends MuxingAttributeSet {
2619         ViewAttributeSet(View v) {
2620             host = v;
2621 
2622             // PENDING(prinz) fix this up to be a more realistic
2623             // implementation.
2624             Document doc = v.getDocument();
2625             SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
2626             Vector<AttributeSet> muxList = sb.getVector();
2627             try {
2628                 if (doc instanceof HTMLDocument) {
2629                     StyleSheet styles = StyleSheet.this;
2630                     Element elem = v.getElement();
2631                     AttributeSet a = elem.getAttributes();
2632                     AttributeSet htmlAttr = styles.translateHTMLToCSS(a);
2633 
2634                     if (htmlAttr.getAttributeCount() != 0) {
2635                         muxList.addElement(htmlAttr);
2636                     }
2637                     if (elem.isLeaf()) {
2638                         Enumeration keys = a.getAttributeNames();
2639                         while (keys.hasMoreElements()) {
2640                             Object key = keys.nextElement();
2641                             if (key instanceof HTML.Tag) {
2642                                 if (key == HTML.Tag.A) {
2643                                     Object o = a.getAttribute(key);
2644                                 /**
2645                                    In the case of an A tag, the css rules
2646                                    apply only for tags that have their
2647                                    href attribute defined and not for
2648                                    anchors that only have their name attributes
2649                                    defined, i.e anchors that function as
2650                                    destinations.  Hence we do not add the
2651                                    attributes for that latter kind of
2652                                    anchors.  When CSS2 support is added,
2653                                    it will be possible to specificity this
2654                                    kind of conditional behaviour in the
2655                                    stylesheet.
2656                                  **/
2657                                     if (o != null && o instanceof AttributeSet) {
2658                                         AttributeSet attr = (AttributeSet)o;
2659                                         if (attr.getAttribute(HTML.Attribute.HREF) == null) {
2660                                             continue;
2661                                         }
2662                                     }
2663                                 }
2664                                 AttributeSet cssRule = styles.getRule((HTML.Tag) key, elem);
2665                                 if (cssRule != null) {
2666                                     muxList.addElement(cssRule);
2667                                 }
2668                             }
2669                         }
2670                     } else {
2671                         HTML.Tag t = (HTML.Tag) a.getAttribute
2672                                      (StyleConstants.NameAttribute);
2673                         AttributeSet cssRule = styles.getRule(t, elem);
2674                         if (cssRule != null) {
2675                             muxList.addElement(cssRule);
2676                         }
2677                     }
2678                 }
2679                 AttributeSet[] attrs = new AttributeSet[muxList.size()];
2680                 muxList.copyInto(attrs);
2681                 setAttributes(attrs);
2682             }
2683             finally {
2684                 SearchBuffer.releaseSearchBuffer(sb);
2685             }
2686         }
2687 
2688         //  --- AttributeSet methods ----------------------------
2689 
2690         /**
2691          * Checks whether a given attribute is defined.
2692          * This will convert the key over to CSS if the
2693          * key is a StyleConstants key that has a CSS
2694          * mapping.
2695          *
2696          * @param key the attribute key
2697          * @return true if the attribute is defined
2698          * @see AttributeSet#isDefined
2699          */
2700         public boolean isDefined(Object key) {
2701             if (key instanceof StyleConstants) {
2702                 Object cssKey = css.styleConstantsKeyToCSSKey
2703                                     ((StyleConstants)key);
2704                 if (cssKey != null) {
2705                     key = cssKey;
2706                 }
2707             }
2708             return super.isDefined(key);
2709         }
2710 
2711         /**
2712          * Gets the value of an attribute.  If the requested
2713          * attribute is a StyleConstants attribute that has
2714          * a CSS mapping, the request will be converted.
2715          *
2716          * @param key the attribute name
2717          * @return the attribute value
2718          * @see AttributeSet#getAttribute
2719          */
2720         public Object getAttribute(Object key) {
2721             if (key instanceof StyleConstants) {
2722                 Object cssKey = css.styleConstantsKeyToCSSKey
2723                                ((StyleConstants)key);
2724                 if (cssKey != null) {
2725                     Object value = doGetAttribute(cssKey);
2726                     if (value instanceof CSS.CssValue) {
2727                         return ((CSS.CssValue)value).toStyleConstants
2728                                      ((StyleConstants)key, host);
2729                     }
2730                 }
2731             }
2732             return doGetAttribute(key);
2733         }
2734 
2735         Object doGetAttribute(Object key) {
2736             Object retValue = super.getAttribute(key);
2737             if (retValue != null) {
2738                 return retValue;
2739             }
2740             // didn't find it... try parent if it's a css attribute
2741             // that is inherited.
2742             if (key instanceof CSS.Attribute) {
2743                 CSS.Attribute css = (CSS.Attribute) key;
2744                 if (css.isInherited()) {
2745                     AttributeSet parent = getResolveParent();
2746                     if (parent != null)
2747                         return parent.getAttribute(key);
2748                 }
2749             }
2750             return null;
2751         }
2752 
2753         /**
2754          * If not overriden, the resolving parent defaults to
2755          * the parent element.
2756          *
2757          * @return the attributes from the parent
2758          * @see AttributeSet#getResolveParent
2759          */
2760         public AttributeSet getResolveParent() {
2761             if (host == null) {
2762                 return null;
2763             }
2764             View parent = host.getParent();
2765             return (parent != null) ? parent.getAttributes() : null;
2766         }
2767 
2768         /** View created for. */
2769         View host;
2770     }
2771 
2772 
2773     /**
2774      * A subclass of MuxingAttributeSet that implements Style. Currently
2775      * the MutableAttributeSet methods are unimplemented, that is they
2776      * do nothing.
2777      */
2778     // PENDING(sky): Decide what to do with this. Either make it
2779     // contain a SimpleAttributeSet that modify methods are delegated to,
2780     // or change getRule to return an AttributeSet and then don't make this
2781     // implement Style.
2782     static class ResolvedStyle extends MuxingAttributeSet implements
2783                   Serializable, Style {
2784         ResolvedStyle(String name, AttributeSet[] attrs, int extendedIndex) {
2785             super(attrs);
2786             this.name = name;
2787             this.extendedIndex = extendedIndex;
2788         }
2789 
2790         /**
2791          * Inserts a Style into the receiver so that the styles the
2792          * receiver represents are still ordered by specificity.
2793          * <code>style</code> will be added before any extended styles, that
2794          * is before extendedIndex.
2795          */
2796         synchronized void insertStyle(Style style, int specificity) {
2797             AttributeSet[] attrs = getAttributes();
2798             int maxCounter = attrs.length;
2799             int counter = 0;
2800             for (;counter < extendedIndex; counter++) {
2801                 if (specificity > getSpecificity(((Style)attrs[counter]).
2802                                                  getName())) {
2803                     break;
2804                 }
2805             }
2806             insertAttributeSetAt(style, counter);
2807             extendedIndex++;
2808         }
2809 
2810         /**
2811          * Removes a previously added style. This will do nothing if
2812          * <code>style</code> is not referenced by the receiver.
2813          */
2814         synchronized void removeStyle(Style style) {
2815             AttributeSet[] attrs = getAttributes();
2816 
2817             for (int counter = attrs.length - 1; counter >= 0; counter--) {
2818                 if (attrs[counter] == style) {
2819                     removeAttributeSetAt(counter);
2820                     if (counter < extendedIndex) {
2821                         extendedIndex--;
2822                     }
2823                     break;
2824                 }
2825             }
2826         }
2827 
2828         /**
2829          * Adds <code>s</code> as one of the Attributesets to look up
2830          * attributes in.
2831          */
2832         synchronized void insertExtendedStyleAt(Style attr, int index) {
2833             insertAttributeSetAt(attr, extendedIndex + index);
2834         }
2835 
2836         /**
2837          * Adds <code>s</code> as one of the AttributeSets to look up
2838          * attributes in. It will be the AttributeSet last checked.
2839          */
2840         synchronized void addExtendedStyle(Style attr) {
2841             insertAttributeSetAt(attr, getAttributes().length);
2842         }
2843 
2844         /**
2845          * Removes the style at <code>index</code> +
2846          * <code>extendedIndex</code>.
2847          */
2848         synchronized void removeExtendedStyleAt(int index) {
2849             removeAttributeSetAt(extendedIndex + index);
2850         }
2851 
2852         /**
2853          * Returns true if the receiver matches <code>selector</code>, where
2854          * a match is defined by the CSS rule matching.
2855          * Each simple selector must be separated by a single space.
2856          */
2857         protected boolean matches(String selector) {
2858             int sLast = selector.length();
2859 
2860             if (sLast == 0) {
2861                 return false;
2862             }
2863             int thisLast = name.length();
2864             int sCurrent = selector.lastIndexOf(' ');
2865             int thisCurrent = name.lastIndexOf(' ');
2866             if (sCurrent >= 0) {
2867                 sCurrent++;
2868             }
2869             if (thisCurrent >= 0) {
2870                 thisCurrent++;
2871             }
2872             if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) {
2873                 return false;
2874             }
2875             while (sCurrent != -1) {
2876                 sLast = sCurrent - 1;
2877                 sCurrent = selector.lastIndexOf(' ', sLast - 1);
2878                 if (sCurrent >= 0) {
2879                     sCurrent++;
2880                 }
2881                 boolean match = false;
2882                 while (!match && thisCurrent != -1) {
2883                     thisLast = thisCurrent - 1;
2884                     thisCurrent = name.lastIndexOf(' ', thisLast - 1);
2885                     if (thisCurrent >= 0) {
2886                         thisCurrent++;
2887                     }
2888                     match = matches(selector, sCurrent, sLast, thisCurrent,
2889                                     thisLast);
2890                 }
2891                 if (!match) {
2892                     return false;
2893                 }
2894             }
2895             return true;
2896         }
2897 
2898         /**
2899          * Returns true if the substring of the receiver, in the range
2900          * thisCurrent, thisLast matches the substring of selector in
2901          * the ranme sCurrent to sLast based on CSS selector matching.
2902          */
2903         boolean matches(String selector, int sCurrent, int sLast,
2904                        int thisCurrent, int thisLast) {
2905             sCurrent = Math.max(sCurrent, 0);
2906             thisCurrent = Math.max(thisCurrent, 0);
2907             int thisDotIndex = boundedIndexOf(name, '.', thisCurrent,
2908                                               thisLast);
2909             int thisPoundIndex = boundedIndexOf(name, '#', thisCurrent,
2910                                                 thisLast);
2911             int sDotIndex = boundedIndexOf(selector, '.', sCurrent, sLast);
2912             int sPoundIndex = boundedIndexOf(selector, '#', sCurrent, sLast);
2913             if (sDotIndex != -1) {
2914                 // Selector has a '.', which indicates name must match it,
2915                 // or if the '.' starts the selector than name must have
2916                 // the same class (doesn't matter what element name).
2917                 if (thisDotIndex == -1) {
2918                     return false;
2919                 }
2920                 if (sCurrent == sDotIndex) {
2921                     if ((thisLast - thisDotIndex) != (sLast - sDotIndex) ||
2922                         !selector.regionMatches(sCurrent, name, thisDotIndex,
2923                                                 (thisLast - thisDotIndex))) {
2924                         return false;
2925                     }
2926                 }
2927                 else {
2928                     // Has to fully match.
2929                     if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
2930                         !selector.regionMatches(sCurrent, name, thisCurrent,
2931                                                 (thisLast - thisCurrent))) {
2932                         return false;
2933                     }
2934                 }
2935                 return true;
2936             }
2937             if (sPoundIndex != -1) {
2938                 // Selector has a '#', which indicates name must match it,
2939                 // or if the '#' starts the selector than name must have
2940                 // the same id (doesn't matter what element name).
2941                 if (thisPoundIndex == -1) {
2942                     return false;
2943                 }
2944                 if (sCurrent == sPoundIndex) {
2945                     if ((thisLast - thisPoundIndex) !=(sLast - sPoundIndex) ||
2946                         !selector.regionMatches(sCurrent, name, thisPoundIndex,
2947                                                 (thisLast - thisPoundIndex))) {
2948                         return false;
2949                     }
2950                 }
2951                 else {
2952                     // Has to fully match.
2953                     if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
2954                         !selector.regionMatches(sCurrent, name, thisCurrent,
2955                                                (thisLast - thisCurrent))) {
2956                         return false;
2957                     }
2958                 }
2959                 return true;
2960             }
2961             if (thisDotIndex != -1) {
2962                 // Receiver references a class, just check element name.
2963                 return (((thisDotIndex - thisCurrent) == (sLast - sCurrent)) &&
2964                         selector.regionMatches(sCurrent, name, thisCurrent,
2965                                                thisDotIndex - thisCurrent));
2966             }
2967             if (thisPoundIndex != -1) {
2968                 // Receiver references an id, just check element name.
2969                 return (((thisPoundIndex - thisCurrent) ==(sLast - sCurrent))&&
2970                         selector.regionMatches(sCurrent, name, thisCurrent,
2971                                                thisPoundIndex - thisCurrent));
2972             }
2973             // Fail through, no classes or ides, just check string.
2974             return (((thisLast - thisCurrent) == (sLast - sCurrent)) &&
2975                     selector.regionMatches(sCurrent, name, thisCurrent,
2976                                            thisLast - thisCurrent));
2977         }
2978 
2979         /**
2980          * Similar to String.indexOf, but allows an upper bound
2981          * (this is slower in that it will still check string starting at
2982          * start.
2983          */
2984         int boundedIndexOf(String string, char search, int start,
2985                            int end) {
2986             int retValue = string.indexOf(search, start);
2987             if (retValue >= end) {
2988                 return -1;
2989             }
2990             return retValue;
2991         }
2992 
2993         public void addAttribute(Object name, Object value) {}
2994         public void addAttributes(AttributeSet attributes) {}
2995         public void removeAttribute(Object name) {}
2996         public void removeAttributes(Enumeration<?> names) {}
2997         public void removeAttributes(AttributeSet attributes) {}
2998         public void setResolveParent(AttributeSet parent) {}
2999         public String getName() {return name;}
3000         public void addChangeListener(ChangeListener l) {}
3001         public void removeChangeListener(ChangeListener l) {}
3002         public ChangeListener[] getChangeListeners() {
3003             return new ChangeListener[0];
3004         }
3005 
3006         /** The name of the Style, which is the selector.
3007          * This will NEVER change!
3008          */
3009         String name;
3010         /** Start index of styles coming from other StyleSheets. */
3011         private int extendedIndex;
3012     }
3013 
3014 
3015     /**
3016      * SelectorMapping contains a specifitiy, as an integer, and an associated
3017      * Style. It can also reference children <code>SelectorMapping</code>s,
3018      * so that it behaves like a tree.
3019      * <p>
3020      * This is not thread safe, it is assumed the caller will take the
3021      * necessary precations if this is to be used in a threaded environment.
3022      */
3023     static class SelectorMapping implements Serializable {
3024         public SelectorMapping(int specificity) {
3025             this.specificity = specificity;
3026         }
3027 
3028         /**
3029          * Returns the specificity this mapping represents.
3030          */
3031         public int getSpecificity() {
3032             return specificity;
3033         }
3034 
3035         /**
3036          * Sets the Style associated with this mapping.
3037          */
3038         public void setStyle(Style style) {
3039             this.style = style;
3040         }
3041 
3042         /**
3043          * Returns the Style associated with this mapping.
3044          */
3045         public Style getStyle() {
3046             return style;
3047         }
3048 
3049         /**
3050          * Returns the child mapping identified by the simple selector
3051          * <code>selector</code>. If a child mapping does not exist for
3052          *<code>selector</code>, and <code>create</code> is true, a new
3053          * one will be created.
3054          */
3055         public SelectorMapping getChildSelectorMapping(String selector,
3056                                                        boolean create) {
3057             SelectorMapping retValue = null;
3058 
3059             if (children != null) {
3060                 retValue = children.get(selector);
3061             }
3062             else if (create) {
3063                 children = new HashMap<String, SelectorMapping>(7);
3064             }
3065             if (retValue == null && create) {
3066                 int specificity = getChildSpecificity(selector);
3067 
3068                 retValue = createChildSelectorMapping(specificity);
3069                 children.put(selector, retValue);
3070             }
3071             return retValue;
3072         }
3073 
3074         /**
3075          * Creates a child <code>SelectorMapping</code> with the specified
3076          * <code>specificity</code>.
3077          */
3078         protected SelectorMapping createChildSelectorMapping(int specificity) {
3079             return new SelectorMapping(specificity);
3080         }
3081 
3082         /**
3083          * Returns the specificity for the child selector
3084          * <code>selector</code>.
3085          */
3086         protected int getChildSpecificity(String selector) {
3087             // class (.) 100
3088             // id (#)    10000
3089             char    firstChar = selector.charAt(0);
3090             int     specificity = getSpecificity();
3091 
3092             if (firstChar == '.') {
3093                 specificity += 100;
3094             }
3095             else if (firstChar == '#') {
3096                 specificity += 10000;
3097             }
3098             else {
3099                 specificity += 1;
3100                 if (selector.indexOf('.') != -1) {
3101                     specificity += 100;
3102                 }
3103                 if (selector.indexOf('#') != -1) {
3104                     specificity += 10000;
3105                 }
3106             }
3107             return specificity;
3108         }
3109 
3110         /**
3111          * The specificity for this selector.
3112          */
3113         private int specificity;
3114         /**
3115          * Style for this selector.
3116          */
3117         private Style style;
3118         /**
3119          * Any sub selectors. Key will be String, and value will be
3120          * another SelectorMapping.
3121          */
3122         private HashMap<String, SelectorMapping> children;
3123     }
3124 
3125 
3126     // ---- Variables ---------------------------------------------
3127 
3128     final static int DEFAULT_FONT_SIZE = 3;
3129 
3130     private CSS css;
3131 
3132     /**
3133      * An inverted graph of the selectors.
3134      */
3135     private SelectorMapping selectorMapping;
3136 
3137     /** Maps from selector (as a string) to Style that includes all
3138      * relevant styles. */
3139     private Hashtable<String, ResolvedStyle> resolvedStyles;
3140 
3141     /** Vector of StyleSheets that the rules are to reference.
3142      */
3143     private Vector<StyleSheet> linkedStyleSheets;
3144 
3145     /** Where the style sheet was found. Used for relative imports. */
3146     private URL base;
3147 
3148 
3149     /**
3150      * Default parser for CSS specifications that get loaded into
3151      * the StyleSheet.<p>
3152      * This class is NOT thread safe, do not ask it to parse while it is
3153      * in the middle of parsing.
3154      */
3155     class CssParser implements CSSParser.CSSParserCallback {
3156 
3157         /**
3158          * Parses the passed in CSS declaration into an AttributeSet.
3159          */
3160         public AttributeSet parseDeclaration(String string) {
3161             try {
3162                 return parseDeclaration(new StringReader(string));
3163             } catch (IOException ioe) {}
3164             return null;
3165         }
3166 
3167         /**
3168          * Parses the passed in CSS declaration into an AttributeSet.
3169          */
3170         public AttributeSet parseDeclaration(Reader r) throws IOException {
3171             parse(base, r, true, false);
3172             return declaration.copyAttributes();
3173         }
3174 
3175         /**
3176          * Parse the given CSS stream
3177          */
3178         public void parse(URL base, Reader r, boolean parseDeclaration,
3179                           boolean isLink) throws IOException {
3180             this.base = base;
3181             this.isLink = isLink;
3182             this.parsingDeclaration = parseDeclaration;
3183             declaration.removeAttributes(declaration);
3184             selectorTokens.removeAllElements();
3185             selectors.removeAllElements();
3186             propertyName = null;
3187             parser.parse(r, this, parseDeclaration);
3188         }
3189 
3190         //
3191         // CSSParserCallback methods, public to implement the interface.
3192         //
3193 
3194         /**
3195          * Invoked when a valid @import is encountered, will call
3196          * <code>importStyleSheet</code> if a
3197          * <code>MalformedURLException</code> is not thrown in creating
3198          * the URL.
3199          */
3200         public void handleImport(String importString) {
3201             URL url = CSS.getURL(base, importString);
3202             if (url != null) {
3203                 importStyleSheet(url);
3204             }
3205         }
3206 
3207         /**
3208          * A selector has been encountered.
3209          */
3210         public void handleSelector(String selector) {
3211             //class and index selectors are case sensitive
3212             if (!(selector.startsWith(".")
3213                   || selector.startsWith("#"))) {
3214                 selector = selector.toLowerCase();
3215             }
3216             int length = selector.length();
3217 
3218             if (selector.endsWith(",")) {
3219                 if (length > 1) {
3220                     selector = selector.substring(0, length - 1);
3221                     selectorTokens.addElement(selector);
3222                 }
3223                 addSelector();
3224             }
3225             else if (length > 0) {
3226                 selectorTokens.addElement(selector);
3227             }
3228         }
3229 
3230         /**
3231          * Invoked when the start of a rule is encountered.
3232          */
3233         public void startRule() {
3234             if (selectorTokens.size() > 0) {
3235                 addSelector();
3236             }
3237             propertyName = null;
3238         }
3239 
3240         /**
3241          * Invoked when a property name is encountered.
3242          */
3243         public void handleProperty(String property) {
3244             propertyName = property;
3245         }
3246 
3247         /**
3248          * Invoked when a property value is encountered.
3249          */
3250         public void handleValue(String value) {
3251             if (propertyName != null && value != null && value.length() > 0) {
3252                 CSS.Attribute cssKey = CSS.getAttribute(propertyName);
3253                 if (cssKey != null) {
3254                     // There is currently no mechanism to determine real
3255                     // base that style sheet was loaded from. For the time
3256                     // being, this maps for LIST_STYLE_IMAGE, which appear
3257                     // to be the only one that currently matters. A more
3258                     // general mechanism is definately needed.
3259                     if (cssKey == CSS.Attribute.LIST_STYLE_IMAGE) {
3260                         if (value != null && !value.equals("none")) {
3261                             URL url = CSS.getURL(base, value);
3262 
3263                             if (url != null) {
3264                                 value = url.toString();
3265                             }
3266                         }
3267                     }
3268                     addCSSAttribute(declaration, cssKey, value);
3269                 }
3270                 propertyName = null;
3271             }
3272         }
3273 
3274         /**
3275          * Invoked when the end of a rule is encountered.
3276          */
3277         public void endRule() {
3278             int n = selectors.size();
3279             for (int i = 0; i < n; i++) {
3280                 String[] selector = selectors.elementAt(i);
3281                 if (selector.length > 0) {
3282                     StyleSheet.this.addRule(selector, declaration, isLink);
3283                 }
3284             }
3285             declaration.removeAttributes(declaration);
3286             selectors.removeAllElements();
3287         }
3288 
3289         private void addSelector() {
3290             String[] selector = new String[selectorTokens.size()];
3291             selectorTokens.copyInto(selector);
3292             selectors.addElement(selector);
3293             selectorTokens.removeAllElements();
3294         }
3295 
3296 
3297         Vector<String[]> selectors = new Vector<String[]>();
3298         Vector<String> selectorTokens = new Vector<String>();
3299         /** Name of the current property. */
3300         String propertyName;
3301         MutableAttributeSet declaration = new SimpleAttributeSet();
3302         /** True if parsing a declaration, that is the Reader will not
3303          * contain a selector. */
3304         boolean parsingDeclaration;
3305         /** True if the attributes are coming from a linked/imported style. */
3306         boolean isLink;
3307         /** Where the CSS stylesheet lives. */
3308         URL base;
3309         CSSParser parser = new CSSParser();
3310     }
3311 
3312     void rebaseSizeMap(int base) {
3313         final int minimalFontSize = 4;
3314         sizeMap = new int[sizeMapDefault.length];
3315         for (int i = 0; i < sizeMapDefault.length; i++) {
3316             sizeMap[i] = Math.max(base * sizeMapDefault[i] /
3317                                   sizeMapDefault[CSS.baseFontSizeIndex],
3318                                   minimalFontSize);
3319         }
3320 
3321     }
3322 
3323     int[] getSizeMap() {
3324         return sizeMap;
3325     }
3326     boolean isW3CLengthUnits() {
3327         return w3cLengthUnits;
3328     }
3329 
3330     /**
3331      * The HTML/CSS size model has seven slots
3332      * that one can assign sizes to.
3333      */
3334     static final int sizeMapDefault[] = { 8, 10, 12, 14, 18, 24, 36 };
3335 
3336     private int sizeMap[] = sizeMapDefault;
3337     private boolean w3cLengthUnits = false;
3338 }