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