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