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