1 /*
   2  * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.plaf.synth;
  26 
  27 import java.awt.Color;
  28 import java.awt.Component;
  29 import java.awt.Font;
  30 import java.awt.Graphics;
  31 import java.awt.Image;
  32 import java.awt.Insets;
  33 import java.awt.Toolkit;
  34 import java.io.BufferedInputStream;
  35 import java.io.IOException;
  36 import java.io.InputStream;
  37 import java.net.MalformedURLException;
  38 import java.net.URL;
  39 import java.net.URLClassLoader;
  40 import java.text.ParseException;
  41 import java.util.ArrayList;
  42 import java.util.HashMap;
  43 import java.util.List;
  44 import java.util.Locale;
  45 import java.util.Map;
  46 import java.util.StringTokenizer;
  47 import java.util.regex.PatternSyntaxException;
  48 
  49 import javax.swing.ImageIcon;
  50 import javax.swing.JSplitPane;
  51 import javax.swing.SwingConstants;
  52 import javax.swing.UIDefaults;
  53 import javax.swing.plaf.ColorUIResource;
  54 import javax.swing.plaf.DimensionUIResource;
  55 import javax.swing.plaf.FontUIResource;
  56 import javax.swing.plaf.InsetsUIResource;
  57 import javax.swing.plaf.UIResource;
  58 import javax.xml.parsers.ParserConfigurationException;
  59 import javax.xml.parsers.SAXParser;
  60 import javax.xml.parsers.SAXParserFactory;
  61 
  62 import org.xml.sax.Attributes;
  63 import org.xml.sax.InputSource;
  64 import org.xml.sax.Locator;
  65 import org.xml.sax.SAXException;
  66 import org.xml.sax.SAXParseException;
  67 import org.xml.sax.helpers.DefaultHandler;
  68 
  69 import com.sun.beans.decoder.DocumentHandler;
  70 
  71 class SynthParser extends DefaultHandler {
  72     //
  73     // Known element names
  74     //
  75     private static final String ELEMENT_SYNTH = "synth";
  76     private static final String ELEMENT_STYLE = "style";
  77     private static final String ELEMENT_STATE = "state";
  78     private static final String ELEMENT_FONT = "font";
  79     private static final String ELEMENT_COLOR = "color";
  80     private static final String ELEMENT_IMAGE_PAINTER = "imagePainter";
  81     private static final String ELEMENT_PAINTER = "painter";
  82     private static final String ELEMENT_PROPERTY = "property";
  83     private static final String ELEMENT_SYNTH_GRAPHICS = "graphicsUtils";
  84     private static final String ELEMENT_IMAGE_ICON = "imageIcon";
  85     private static final String ELEMENT_BIND = "bind";
  86     private static final String ELEMENT_BIND_KEY = "bindKey";
  87     private static final String ELEMENT_INSETS = "insets";
  88     private static final String ELEMENT_OPAQUE = "opaque";
  89     private static final String ELEMENT_DEFAULTS_PROPERTY =
  90                                         "defaultsProperty";
  91     private static final String ELEMENT_INPUT_MAP = "inputMap";
  92 
  93     //
  94     // Known attribute names
  95     //
  96     private static final String ATTRIBUTE_ACTION = "action";
  97     private static final String ATTRIBUTE_ID = "id";
  98     private static final String ATTRIBUTE_IDREF = "idref";
  99     private static final String ATTRIBUTE_CLONE = "clone";
 100     private static final String ATTRIBUTE_VALUE = "value";
 101     private static final String ATTRIBUTE_NAME = "name";
 102     private static final String ATTRIBUTE_STYLE = "style";
 103     private static final String ATTRIBUTE_SIZE = "size";
 104     private static final String ATTRIBUTE_TYPE = "type";
 105     private static final String ATTRIBUTE_TOP = "top";
 106     private static final String ATTRIBUTE_LEFT = "left";
 107     private static final String ATTRIBUTE_BOTTOM = "bottom";
 108     private static final String ATTRIBUTE_RIGHT = "right";
 109     private static final String ATTRIBUTE_KEY = "key";
 110     private static final String ATTRIBUTE_SOURCE_INSETS = "sourceInsets";
 111     private static final String ATTRIBUTE_DEST_INSETS = "destinationInsets";
 112     private static final String ATTRIBUTE_PATH = "path";
 113     private static final String ATTRIBUTE_STRETCH = "stretch";
 114     private static final String ATTRIBUTE_PAINT_CENTER = "paintCenter";
 115     private static final String ATTRIBUTE_METHOD = "method";
 116     private static final String ATTRIBUTE_DIRECTION = "direction";
 117     private static final String ATTRIBUTE_CENTER = "center";
 118 
 119     /**
 120      * Lazily created, used for anything we don't understand.
 121      */
 122     private DocumentHandler _handler;
 123 
 124     /**
 125      * Indicates the depth of how many elements we've encountered but don't
 126      * understand. This is used when forwarding to beans persistance to know
 127      * when we hsould stop forwarding.
 128      */
 129     private int _depth;
 130 
 131     /**
 132      * Factory that new styles are added to.
 133      */
 134     private DefaultSynthStyleFactory _factory;
 135 
 136     /**
 137      * Array of state infos for the current style. These are pushed to the
 138      * style when </style> is received.
 139      */
 140     private List<ParsedSynthStyle.StateInfo> _stateInfos;
 141 
 142     /**
 143      * Current style.
 144      */
 145     private ParsedSynthStyle _style;
 146 
 147     /**
 148      * Current state info.
 149      */
 150     private ParsedSynthStyle.StateInfo _stateInfo;
 151 
 152     /**
 153      * Bindings for the current InputMap
 154      */
 155     private List<String> _inputMapBindings;
 156 
 157     /**
 158      * ID for the input map. This is cached as
 159      * the InputMap is created AFTER the inputMapProperty has ended.
 160      */
 161     private String _inputMapID;
 162 
 163     /**
 164      * Object references outside the scope of persistance.
 165      */
 166     private Map<String,Object> _mapping;
 167 
 168     /**
 169      * Based URL used to resolve paths.
 170      */
 171     private URL _urlResourceBase;
 172 
 173     /**
 174      * Based class used to resolve paths.
 175      */
 176     private Class<?> _classResourceBase;
 177 
 178     /**
 179      * List of ColorTypes. This is populated in startColorType.
 180      */
 181     private List<ColorType> _colorTypes;
 182 
 183     /**
 184      * defaultsPropertys are placed here.
 185      */
 186     private Map<String, Object> _defaultsMap;
 187 
 188     /**
 189      * List of SynthStyle.Painters that will be applied to the current style.
 190      */
 191     private List<ParsedSynthStyle.PainterInfo> _stylePainters;
 192 
 193     /**
 194      * List of SynthStyle.Painters that will be applied to the current state.
 195      */
 196     private List<ParsedSynthStyle.PainterInfo> _statePainters;
 197 
 198     SynthParser() {
 199         _mapping = new HashMap<String,Object>();
 200         _stateInfos = new ArrayList<ParsedSynthStyle.StateInfo>();
 201         _colorTypes = new ArrayList<ColorType>();
 202         _inputMapBindings = new ArrayList<String>();
 203         _stylePainters = new ArrayList<ParsedSynthStyle.PainterInfo>();
 204         _statePainters = new ArrayList<ParsedSynthStyle.PainterInfo>();
 205     }
 206 
 207     /**
 208      * Parses a set of styles from <code>inputStream</code>, adding the
 209      * resulting styles to the passed in DefaultSynthStyleFactory.
 210      * Resources are resolved either from a URL or from a Class. When calling
 211      * this method, one of the URL or the Class must be null but not both at
 212      * the same time.
 213      *
 214      * @param inputStream XML document containing the styles to read
 215      * @param factory DefaultSynthStyleFactory that new styles are added to
 216      * @param urlResourceBase the URL used to resolve any resources, such as Images
 217      * @param classResourceBase the Class used to resolve any resources, such as Images
 218      * @param defaultsMap Map that UIDefaults properties are placed in
 219      */
 220     public void parse(InputStream inputStream,
 221                       DefaultSynthStyleFactory factory,
 222                       URL urlResourceBase, Class<?> classResourceBase,
 223                       Map<String, Object> defaultsMap)
 224                       throws ParseException, IllegalArgumentException {
 225         if (inputStream == null || factory == null ||
 226             (urlResourceBase == null && classResourceBase == null)) {
 227             throw new IllegalArgumentException(
 228                 "You must supply an InputStream, StyleFactory and Class or URL");
 229         }
 230 
 231         assert(!(urlResourceBase != null && classResourceBase != null));
 232 
 233         _factory = factory;
 234         _classResourceBase = classResourceBase;
 235         _urlResourceBase = urlResourceBase;
 236         _defaultsMap = defaultsMap;
 237         try {
 238             try {
 239                 SAXParser saxParser = SAXParserFactory.newInstance().
 240                                                    newSAXParser();
 241                 saxParser.parse(new BufferedInputStream(inputStream), this);
 242             } catch (ParserConfigurationException e) {
 243                 throw new ParseException("Error parsing: " + e, 0);
 244             }
 245             catch (SAXException se) {
 246                 throw new ParseException("Error parsing: " + se + " " +
 247                                          se.getException(), 0);
 248             }
 249             catch (IOException ioe) {
 250                 throw new ParseException("Error parsing: " + ioe, 0);
 251             }
 252         } finally {
 253             reset();
 254         }
 255     }
 256 
 257     /**
 258      * Returns the path to a resource.
 259      */
 260     private URL getResource(String path) {
 261         if (_classResourceBase != null) {
 262             return _classResourceBase.getResource(path);
 263         } else {
 264             try {
 265                 return new URL(_urlResourceBase, path);
 266             } catch (MalformedURLException mue) {
 267                 return null;
 268             }
 269         }
 270     }
 271 
 272     /**
 273      * Clears our internal state.
 274      */
 275     private void reset() {
 276         _handler = null;
 277         _depth = 0;
 278         _mapping.clear();
 279         _stateInfos.clear();
 280         _colorTypes.clear();
 281         _statePainters.clear();
 282         _stylePainters.clear();
 283     }
 284 
 285     /**
 286      * Returns true if we are forwarding to persistance.
 287      */
 288     private boolean isForwarding() {
 289         return (_depth > 0);
 290     }
 291 
 292     /**
 293      * Handles beans persistance.
 294      */
 295     private DocumentHandler getHandler() {
 296         if (_handler == null) {
 297             _handler = new DocumentHandler();
 298             if (_urlResourceBase != null) {
 299                 // getHandler() is never called before parse() so it is safe
 300                 // to create a URLClassLoader with _resourceBase.
 301                 //
 302                 // getResource(".") is called to ensure we have the directory
 303                 // containing the resources in the case the resource base is a
 304                 // .class file.
 305                 URL[] urls = new URL[] { getResource(".") };
 306                 ClassLoader parent = Thread.currentThread().getContextClassLoader();
 307                 ClassLoader urlLoader = new URLClassLoader(urls, parent);
 308                 _handler.setClassLoader(urlLoader);
 309             } else {
 310                 _handler.setClassLoader(_classResourceBase.getClassLoader());
 311             }
 312 
 313             for (String key : _mapping.keySet()) {
 314                 _handler.setVariable(key, _mapping.get(key));
 315             }
 316         }
 317         return _handler;
 318     }
 319 
 320     /**
 321      * If <code>value</code> is an instance of <code>type</code> it is
 322      * returned, otherwise a SAXException is thrown.
 323      */
 324     private Object checkCast(Object value, Class type) throws SAXException {
 325         if (!type.isInstance(value)) {
 326             throw new SAXException("Expected type " + type + " got " +
 327                                    value.getClass());
 328         }
 329         return value;
 330     }
 331 
 332     /**
 333      * Returns an object created with id=key. If the object is not of
 334      * type type, this will throw an exception.
 335      */
 336     private Object lookup(String key, Class type) throws SAXException {
 337         Object value;
 338         if (_handler != null) {
 339             if (_handler.hasVariable(key)) {
 340                 return checkCast(_handler.getVariable(key), type);
 341             }
 342         }
 343         value = _mapping.get(key);
 344         if (value == null) {
 345             throw new SAXException("ID " + key + " has not been defined");
 346         }
 347         return checkCast(value, type);
 348     }
 349 
 350     /**
 351      * Registers an object by name. This will throw an exception if an
 352      * object has already been registered under the given name.
 353      */
 354     private void register(String key, Object value) throws SAXException {
 355         if (key != null) {
 356             if (_mapping.get(key) != null ||
 357                      (_handler != null && _handler.hasVariable(key))) {
 358                 throw new SAXException("ID " + key + " is already defined");
 359             }
 360             if (_handler != null) {
 361                 _handler.setVariable(key, value);
 362             }
 363             else {
 364                 _mapping.put(key, value);
 365             }
 366         }
 367     }
 368 
 369     /**
 370      * Convenience method to return the next int, or throw if there are no
 371      * more valid ints.
 372      */
 373     private int nextInt(StringTokenizer tok, String errorMsg) throws
 374                    SAXException {
 375         if (!tok.hasMoreTokens()) {
 376             throw new SAXException(errorMsg);
 377         }
 378         try {
 379             return Integer.parseInt(tok.nextToken());
 380         } catch (NumberFormatException nfe) {
 381             throw new SAXException(errorMsg);
 382         }
 383     }
 384 
 385     /**
 386      * Convenience method to return an Insets object.
 387      */
 388     private Insets parseInsets(String insets, String errorMsg) throws
 389                         SAXException {
 390         StringTokenizer tokenizer = new StringTokenizer(insets);
 391         return new Insets(nextInt(tokenizer, errorMsg),
 392                           nextInt(tokenizer, errorMsg),
 393                           nextInt(tokenizer, errorMsg),
 394                           nextInt(tokenizer, errorMsg));
 395     }
 396 
 397 
 398 
 399     //
 400     // The following methods are invoked from startElement/stopElement
 401     //
 402 
 403     private void startStyle(Attributes attributes) throws SAXException {
 404         String id = null;
 405 
 406         _style = null;
 407         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 408             String key = attributes.getQName(i);
 409             if (key.equals(ATTRIBUTE_CLONE)) {
 410                 _style = (ParsedSynthStyle)((ParsedSynthStyle)lookup(
 411                          attributes.getValue(i), ParsedSynthStyle.class)).
 412                          clone();
 413             }
 414             else if (key.equals(ATTRIBUTE_ID)) {
 415                 id = attributes.getValue(i);
 416             }
 417         }
 418         if (_style == null) {
 419             _style = new ParsedSynthStyle();
 420         }
 421         register(id, _style);
 422     }
 423 
 424     private void endStyle() {
 425         int size = _stylePainters.size();
 426         if (size > 0) {
 427             _style.setPainters(_stylePainters.toArray(new ParsedSynthStyle.PainterInfo[size]));
 428             _stylePainters.clear();
 429         }
 430         size = _stateInfos.size();
 431         if (size > 0) {
 432             _style.setStateInfo(_stateInfos.toArray(new ParsedSynthStyle.StateInfo[size]));
 433             _stateInfos.clear();
 434         }
 435         _style = null;
 436     }
 437 
 438     private void startState(Attributes attributes) throws SAXException {
 439         ParsedSynthStyle.StateInfo stateInfo = null;
 440         int state = 0;
 441         String id = null;
 442 
 443         _stateInfo = null;
 444         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 445             String key = attributes.getQName(i);
 446             if (key.equals(ATTRIBUTE_ID)) {
 447                 id = attributes.getValue(i);
 448             }
 449             else if (key.equals(ATTRIBUTE_IDREF)) {
 450                 _stateInfo = (ParsedSynthStyle.StateInfo)lookup(
 451                    attributes.getValue(i), ParsedSynthStyle.StateInfo.class);
 452             }
 453             else if (key.equals(ATTRIBUTE_CLONE)) {
 454                 _stateInfo = (ParsedSynthStyle.StateInfo)((ParsedSynthStyle.
 455                              StateInfo)lookup(attributes.getValue(i),
 456                              ParsedSynthStyle.StateInfo.class)).clone();
 457             }
 458             else if (key.equals(ATTRIBUTE_VALUE)) {
 459                 StringTokenizer tokenizer = new StringTokenizer(
 460                                    attributes.getValue(i));
 461                 while (tokenizer.hasMoreTokens()) {
 462                     String stateString = tokenizer.nextToken().toUpperCase().
 463                                                    intern();
 464                     if (stateString == "ENABLED") {
 465                         state |= SynthConstants.ENABLED;
 466                     }
 467                     else if (stateString == "MOUSE_OVER") {
 468                         state |= SynthConstants.MOUSE_OVER;
 469                     }
 470                     else if (stateString == "PRESSED") {
 471                         state |= SynthConstants.PRESSED;
 472                     }
 473                     else if (stateString == "DISABLED") {
 474                         state |= SynthConstants.DISABLED;
 475                     }
 476                     else if (stateString == "FOCUSED") {
 477                         state |= SynthConstants.FOCUSED;
 478                     }
 479                     else if (stateString == "SELECTED") {
 480                         state |= SynthConstants.SELECTED;
 481                     }
 482                     else if (stateString == "DEFAULT") {
 483                         state |= SynthConstants.DEFAULT;
 484                     }
 485                     else if (stateString != "AND") {
 486                         throw new SAXException("Unknown state: " + state);
 487                     }
 488                 }
 489             }
 490         }
 491         if (_stateInfo == null) {
 492             _stateInfo = new ParsedSynthStyle.StateInfo();
 493         }
 494         _stateInfo.setComponentState(state);
 495         register(id, _stateInfo);
 496         _stateInfos.add(_stateInfo);
 497     }
 498 
 499     private void endState() {
 500         int size = _statePainters.size();
 501         if (size > 0) {
 502             _stateInfo.setPainters(_statePainters.toArray(new ParsedSynthStyle.PainterInfo[size]));
 503             _statePainters.clear();
 504         }
 505         _stateInfo = null;
 506     }
 507 
 508     private void startFont(Attributes attributes) throws SAXException {
 509         Font font = null;
 510         int style = Font.PLAIN;
 511         int size = 0;
 512         String id = null;
 513         String name = null;
 514 
 515         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 516             String key = attributes.getQName(i);
 517             if (key.equals(ATTRIBUTE_ID)) {
 518                 id = attributes.getValue(i);
 519             }
 520             else if (key.equals(ATTRIBUTE_IDREF)) {
 521                 font = (Font)lookup(attributes.getValue(i), Font.class);
 522             }
 523             else if (key.equals(ATTRIBUTE_NAME)) {
 524                 name = attributes.getValue(i);
 525             }
 526             else if (key.equals(ATTRIBUTE_SIZE)) {
 527                 try {
 528                     size = Integer.parseInt(attributes.getValue(i));
 529                 } catch (NumberFormatException nfe) {
 530                     throw new SAXException("Invalid font size: " +
 531                                            attributes.getValue(i));
 532                 }
 533             }
 534             else if (key.equals(ATTRIBUTE_STYLE)) {
 535                 StringTokenizer tok = new StringTokenizer(
 536                                                 attributes.getValue(i));
 537                 while (tok.hasMoreTokens()) {
 538                     String token = tok.nextToken().intern();
 539                     if (token == "BOLD") {
 540                         style = ((style | Font.PLAIN) ^ Font.PLAIN) |
 541                                 Font.BOLD;
 542                     }
 543                     else if (token == "ITALIC") {
 544                         style |= Font.ITALIC;
 545                     }
 546                 }
 547             }
 548         }
 549         if (font == null) {
 550             if (name == null) {
 551                 throw new SAXException("You must define a name for the font");
 552             }
 553             if (size == 0) {
 554                 throw new SAXException("You must define a size for the font");
 555             }
 556             font = new FontUIResource(name, style, size);
 557         }
 558         else if (name != null || size != 0 || style != Font.PLAIN) {
 559             throw new SAXException("Name, size and style are not for use " +
 560                                    "with idref");
 561         }
 562         register(id, font);
 563         if (_stateInfo != null) {
 564             _stateInfo.setFont(font);
 565         }
 566         else if (_style != null) {
 567             _style.setFont(font);
 568         }
 569     }
 570 
 571     private void startColor(Attributes attributes) throws SAXException {
 572         Color color = null;
 573         String id = null;
 574 
 575         _colorTypes.clear();
 576         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 577             String key = attributes.getQName(i);
 578             if (key.equals(ATTRIBUTE_ID)) {
 579                 id = attributes.getValue(i);
 580             }
 581             else if (key.equals(ATTRIBUTE_IDREF)) {
 582                 color = (Color)lookup(attributes.getValue(i), Color.class);
 583             }
 584             else if (key.equals(ATTRIBUTE_NAME)) {
 585             }
 586             else if (key.equals(ATTRIBUTE_VALUE)) {
 587                 String value = attributes.getValue(i);
 588 
 589                 if (value.startsWith("#")) {
 590                     try {
 591                         int argb;
 592                         boolean hasAlpha;
 593 
 594                         int length = value.length();
 595                         if (length < 8) {
 596                             // Just RGB, or some portion of it.
 597                             argb = Integer.decode(value);
 598                             hasAlpha = false;
 599                         } else if (length == 8) {
 600                             // Single character alpha: #ARRGGBB.
 601                             argb = Integer.decode(value);
 602                             hasAlpha = true;
 603                         } else if (length == 9) {
 604                             // Color has alpha and is of the form
 605                             // #AARRGGBB.
 606                             // The following split decoding is mandatory due to
 607                             // Integer.decode() behavior which won't decode
 608                             // hexadecimal values higher than #7FFFFFFF.
 609                             // Thus, when an alpha channel is detected, it is
 610                             // decoded separately from the RGB channels.
 611                             int rgb = Integer.decode('#' +
 612                                                      value.substring(3, 9));
 613                             int a = Integer.decode(value.substring(0, 3));
 614                             argb = (a << 24) | rgb;
 615                             hasAlpha = true;
 616                         } else {
 617                             throw new SAXException("Invalid Color value: "
 618                                 + value);
 619                         }
 620 
 621                         color = new ColorUIResource(new Color(argb, hasAlpha));
 622                     } catch (NumberFormatException nfe) {
 623                         throw new SAXException("Invalid Color value: " +value);
 624                     }
 625                 }
 626                 else {
 627                     try {
 628                         color = new ColorUIResource((Color)Color.class.
 629                               getField(value.toUpperCase()).get(Color.class));
 630                     } catch (NoSuchFieldException nsfe) {
 631                         throw new SAXException("Invalid color name: " + value);
 632                     } catch (IllegalAccessException iae) {
 633                         throw new SAXException("Invalid color name: " + value);
 634                     }
 635                 }
 636             }
 637             else if (key.equals(ATTRIBUTE_TYPE)) {
 638                 StringTokenizer tokenizer = new StringTokenizer(
 639                                    attributes.getValue(i));
 640                 while (tokenizer.hasMoreTokens()) {
 641                     String typeName = tokenizer.nextToken();
 642                     int classIndex = typeName.lastIndexOf('.');
 643                     Class typeClass;
 644 
 645                     if (classIndex == -1) {
 646                         typeClass = ColorType.class;
 647                         classIndex = 0;
 648                     }
 649                     else {
 650                         try {
 651                             typeClass = Class.forName(typeName.substring(
 652                                                       0, classIndex));
 653                         } catch (ClassNotFoundException cnfe) {
 654                             throw new SAXException("Unknown class: " +
 655                                       typeName.substring(0, classIndex));
 656                         }
 657                         classIndex++;
 658                     }
 659                     try {
 660                         _colorTypes.add((ColorType)checkCast(typeClass.
 661                               getField(typeName.substring(classIndex)).
 662                               get(typeClass), ColorType.class));
 663                     } catch (NoSuchFieldException nsfe) {
 664                         throw new SAXException("Unable to find color type: " +
 665                                                typeName);
 666                     } catch (IllegalAccessException iae) {
 667                         throw new SAXException("Unable to find color type: " +
 668                                                typeName);
 669                     }
 670                 }
 671             }
 672         }
 673         if (color == null) {
 674             throw new SAXException("color: you must specificy a value");
 675         }
 676         register(id, color);
 677         if (_stateInfo != null && _colorTypes.size() > 0) {
 678             Color[] colors = _stateInfo.getColors();
 679             int max = 0;
 680             for (int counter = _colorTypes.size() - 1; counter >= 0;
 681                      counter--) {
 682                 max = Math.max(max, _colorTypes.get(counter).getID());
 683             }
 684             if (colors == null || colors.length <= max) {
 685                 Color[] newColors = new Color[max + 1];
 686                 if (colors != null) {
 687                     System.arraycopy(colors, 0, newColors, 0, colors.length);
 688                 }
 689                 colors = newColors;
 690             }
 691             for (int counter = _colorTypes.size() - 1; counter >= 0;
 692                      counter--) {
 693                 colors[_colorTypes.get(counter).getID()] = color;
 694             }
 695             _stateInfo.setColors(colors);
 696         }
 697     }
 698 
 699     private void startProperty(Attributes attributes,
 700                                Object property) throws SAXException {
 701         Object value = null;
 702         String key = null;
 703         // Type of the value: 0=idref, 1=boolean, 2=dimension, 3=insets,
 704         // 4=integer,5=string
 705         int iType = 0;
 706         String aValue = null;
 707 
 708         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 709             String aName = attributes.getQName(i);
 710             if (aName.equals(ATTRIBUTE_TYPE)) {
 711                 String type = attributes.getValue(i).toUpperCase();
 712                 if (type.equals("IDREF")) {
 713                     iType = 0;
 714                 }
 715                 else if (type.equals("BOOLEAN")) {
 716                     iType = 1;
 717                 }
 718                 else if (type.equals("DIMENSION")) {
 719                     iType = 2;
 720                 }
 721                 else if (type.equals("INSETS")) {
 722                     iType = 3;
 723                 }
 724                 else if (type.equals("INTEGER")) {
 725                     iType = 4;
 726                 }
 727                 else if (type.equals("STRING")) {
 728                     iType = 5;
 729                 }
 730                 else {
 731                     throw new SAXException(property + " unknown type, use" +
 732                         "idref, boolean, dimension, insets or integer");
 733                 }
 734             }
 735             else if (aName.equals(ATTRIBUTE_VALUE)) {
 736                 aValue = attributes.getValue(i);
 737             }
 738             else if (aName.equals(ATTRIBUTE_KEY)) {
 739                 key = attributes.getValue(i);
 740             }
 741         }
 742         if (aValue != null) {
 743             switch (iType) {
 744             case 0: // idref
 745                 value = lookup(aValue, Object.class);
 746                 break;
 747             case 1: // boolean
 748                 if (aValue.toUpperCase().equals("TRUE")) {
 749                     value = Boolean.TRUE;
 750                 }
 751                 else {
 752                     value = Boolean.FALSE;
 753                 }
 754                 break;
 755             case 2: // dimension
 756                 StringTokenizer tok = new StringTokenizer(aValue);
 757                 value = new DimensionUIResource(
 758                     nextInt(tok, "Invalid dimension"),
 759                     nextInt(tok, "Invalid dimension"));
 760                 break;
 761             case 3: // insets
 762                 value = parseInsets(aValue, property + " invalid insets");
 763                 break;
 764             case 4: // integer
 765                 try {
 766                     value = new Integer(Integer.parseInt(aValue));
 767                 } catch (NumberFormatException nfe) {
 768                     throw new SAXException(property + " invalid value");
 769                 }
 770                 break;
 771             case 5: //string
 772                 value = aValue;
 773                 break;
 774             }
 775         }
 776         if (value == null || key == null) {
 777             throw new SAXException(property + ": you must supply a " +
 778                                    "key and value");
 779         }
 780         if (property == ELEMENT_DEFAULTS_PROPERTY) {
 781             _defaultsMap.put(key, value);
 782         }
 783         else if (_stateInfo != null) {
 784             if (_stateInfo.getData() == null) {
 785                 _stateInfo.setData(new HashMap());
 786             }
 787             _stateInfo.getData().put(key, value);
 788         }
 789         else if (_style != null) {
 790             if (_style.getData() == null) {
 791                 _style.setData(new HashMap());
 792             }
 793             _style.getData().put(key, value);
 794         }
 795     }
 796 
 797     private void startGraphics(Attributes attributes) throws SAXException {
 798         SynthGraphicsUtils graphics = null;
 799 
 800         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 801             String key = attributes.getQName(i);
 802             if (key.equals(ATTRIBUTE_IDREF)) {
 803                 graphics = (SynthGraphicsUtils)lookup(attributes.getValue(i),
 804                                                  SynthGraphicsUtils.class);
 805             }
 806         }
 807         if (graphics == null) {
 808             throw new SAXException("graphicsUtils: you must supply an idref");
 809         }
 810         if (_style != null) {
 811             _style.setGraphicsUtils(graphics);
 812         }
 813     }
 814 
 815     private void startInsets(Attributes attributes) throws SAXException {
 816         int top = 0;
 817         int bottom = 0;
 818         int left = 0;
 819         int right = 0;
 820         Insets insets = null;
 821         String id = null;
 822 
 823         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 824             String key = attributes.getQName(i);
 825 
 826             try {
 827                 if (key.equals(ATTRIBUTE_IDREF)) {
 828                     insets = (Insets)lookup(attributes.getValue(i),
 829                                                    Insets.class);
 830                 }
 831                 else if (key.equals(ATTRIBUTE_ID)) {
 832                     id = attributes.getValue(i);
 833                 }
 834                 else if (key.equals(ATTRIBUTE_TOP)) {
 835                     top = Integer.parseInt(attributes.getValue(i));
 836                 }
 837                 else if (key.equals(ATTRIBUTE_LEFT)) {
 838                     left = Integer.parseInt(attributes.getValue(i));
 839                 }
 840                 else if (key.equals(ATTRIBUTE_BOTTOM)) {
 841                     bottom = Integer.parseInt(attributes.getValue(i));
 842                 }
 843                 else if (key.equals(ATTRIBUTE_RIGHT)) {
 844                     right = Integer.parseInt(attributes.getValue(i));
 845                 }
 846             } catch (NumberFormatException nfe) {
 847                 throw new SAXException("insets: bad integer value for " +
 848                                        attributes.getValue(i));
 849             }
 850         }
 851         if (insets == null) {
 852             insets = new InsetsUIResource(top, left, bottom, right);
 853         }
 854         register(id, insets);
 855         if (_style != null) {
 856             _style.setInsets(insets);
 857         }
 858     }
 859 
 860     private void startBind(Attributes attributes) throws SAXException {
 861         ParsedSynthStyle style = null;
 862         String path = null;
 863         int type = -1;
 864 
 865         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 866             String key = attributes.getQName(i);
 867 
 868             if (key.equals(ATTRIBUTE_STYLE)) {
 869                 style = (ParsedSynthStyle)lookup(attributes.getValue(i),
 870                                                   ParsedSynthStyle.class);
 871             }
 872             else if (key.equals(ATTRIBUTE_TYPE)) {
 873                 String typeS = attributes.getValue(i).toUpperCase();
 874 
 875                 if (typeS.equals("NAME")) {
 876                     type = DefaultSynthStyleFactory.NAME;
 877                 }
 878                 else if (typeS.equals("REGION")) {
 879                     type = DefaultSynthStyleFactory.REGION;
 880                 }
 881                 else {
 882                     throw new SAXException("bind: unknown type " + typeS);
 883                 }
 884             }
 885             else if (key.equals(ATTRIBUTE_KEY)) {
 886                 path = attributes.getValue(i);
 887             }
 888         }
 889         if (style == null || path == null || type == -1) {
 890             throw new SAXException("bind: you must specify a style, type " +
 891                                    "and key");
 892         }
 893         try {
 894             _factory.addStyle(style, path, type);
 895         } catch (PatternSyntaxException pse) {
 896             throw new SAXException("bind: " + path + " is not a valid " +
 897                                    "regular expression");
 898         }
 899     }
 900 
 901     private void startPainter(Attributes attributes, String type) throws SAXException {
 902         Insets sourceInsets = null;
 903         Insets destInsets = null;
 904         String path = null;
 905         boolean paintCenter = true;
 906         boolean stretch = true;
 907         SynthPainter painter = null;
 908         String method = null;
 909         String id = null;
 910         int direction = -1;
 911         boolean center = false;
 912 
 913         boolean stretchSpecified = false;
 914         boolean paintCenterSpecified = false;
 915 
 916         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 917             String key = attributes.getQName(i);
 918             String value = attributes.getValue(i);
 919 
 920             if (key.equals(ATTRIBUTE_ID)) {
 921                 id = value;
 922             }
 923             else if (key.equals(ATTRIBUTE_METHOD)) {
 924                 method = value.toLowerCase(Locale.ENGLISH);
 925             }
 926             else if (key.equals(ATTRIBUTE_IDREF)) {
 927                 painter = (SynthPainter)lookup(value, SynthPainter.class);
 928             }
 929             else if (key.equals(ATTRIBUTE_PATH)) {
 930                 path = value;
 931             }
 932             else if (key.equals(ATTRIBUTE_SOURCE_INSETS)) {
 933                 sourceInsets = parseInsets(value, type +
 934                    ": sourceInsets must be top left bottom right");
 935             }
 936             else if (key.equals(ATTRIBUTE_DEST_INSETS)) {
 937                 destInsets = parseInsets(value, type +
 938                   ": destinationInsets must be top left bottom right");
 939             }
 940             else if (key.equals(ATTRIBUTE_PAINT_CENTER)) {
 941                 paintCenter = value.toLowerCase().equals("true");
 942                 paintCenterSpecified = true;
 943             }
 944             else if (key.equals(ATTRIBUTE_STRETCH)) {
 945                 stretch = value.toLowerCase().equals("true");
 946                 stretchSpecified = true;
 947             }
 948             else if (key.equals(ATTRIBUTE_DIRECTION)) {
 949                 value = value.toUpperCase().intern();
 950                 if (value == "EAST") {
 951                     direction = SwingConstants.EAST;
 952                 }
 953                 else if (value == "NORTH") {
 954                     direction = SwingConstants.NORTH;
 955                 }
 956                 else if (value == "SOUTH") {
 957                     direction = SwingConstants.SOUTH;
 958                 }
 959                 else if (value == "WEST") {
 960                     direction = SwingConstants.WEST;
 961                 }
 962                 else if (value == "TOP") {
 963                     direction = SwingConstants.TOP;
 964                 }
 965                 else if (value == "LEFT") {
 966                     direction = SwingConstants.LEFT;
 967                 }
 968                 else if (value == "BOTTOM") {
 969                     direction = SwingConstants.BOTTOM;
 970                 }
 971                 else if (value == "RIGHT") {
 972                     direction = SwingConstants.RIGHT;
 973                 }
 974                 else if (value == "HORIZONTAL") {
 975                     direction = SwingConstants.HORIZONTAL;
 976                 }
 977                 else if (value == "VERTICAL") {
 978                     direction = SwingConstants.VERTICAL;
 979                 }
 980                 else if (value == "HORIZONTAL_SPLIT") {
 981                     direction = JSplitPane.HORIZONTAL_SPLIT;
 982                 }
 983                 else if (value == "VERTICAL_SPLIT") {
 984                     direction = JSplitPane.VERTICAL_SPLIT;
 985                 }
 986                 else {
 987                     throw new SAXException(type + ": unknown direction");
 988                 }
 989             }
 990             else if (key.equals(ATTRIBUTE_CENTER)) {
 991                 center = value.toLowerCase().equals("true");
 992             }
 993         }
 994         if (painter == null) {
 995             if (type == ELEMENT_PAINTER) {
 996                 throw new SAXException(type +
 997                              ": you must specify an idref");
 998             }
 999             if (sourceInsets == null && !center) {
1000                 throw new SAXException(
1001                              "property: you must specify sourceInsets");
1002             }
1003             if (path == null) {
1004                 throw new SAXException("property: you must specify a path");
1005             }
1006             if (center && (sourceInsets != null || destInsets != null ||
1007                            paintCenterSpecified || stretchSpecified)) {
1008                 throw new SAXException("The attributes: sourceInsets, " +
1009                                        "destinationInsets, paintCenter and stretch " +
1010                                        " are not legal when center is true");
1011             }
1012             painter = new ImagePainter(!stretch, paintCenter,
1013                      sourceInsets, destInsets, getResource(path), center);
1014         }
1015         register(id, painter);
1016         if (_stateInfo != null) {
1017             addPainterOrMerge(_statePainters, method, painter, direction);
1018         }
1019         else if (_style != null) {
1020             addPainterOrMerge(_stylePainters, method, painter, direction);
1021         }
1022     }
1023 
1024     private void addPainterOrMerge(List<ParsedSynthStyle.PainterInfo> painters, String method,
1025                                    SynthPainter painter, int direction) {
1026         ParsedSynthStyle.PainterInfo painterInfo;
1027         painterInfo = new ParsedSynthStyle.PainterInfo(method,
1028                                                        painter,
1029                                                        direction);
1030 
1031         for (Object infoObject: painters) {
1032             ParsedSynthStyle.PainterInfo info;
1033             info = (ParsedSynthStyle.PainterInfo) infoObject;
1034 
1035             if (painterInfo.equalsPainter(info)) {
1036                 info.addPainter(painter);
1037                 return;
1038             }
1039         }
1040 
1041         painters.add(painterInfo);
1042     }
1043 
1044     private void startImageIcon(Attributes attributes) throws SAXException {
1045         String path = null;
1046         String id = null;
1047 
1048         for(int i = attributes.getLength() - 1; i >= 0; i--) {
1049             String key = attributes.getQName(i);
1050 
1051             if (key.equals(ATTRIBUTE_ID)) {
1052                 id = attributes.getValue(i);
1053             }
1054             else if (key.equals(ATTRIBUTE_PATH)) {
1055                 path = attributes.getValue(i);
1056             }
1057         }
1058         if (path == null) {
1059             throw new SAXException("imageIcon: you must specify a path");
1060         }
1061         register(id, new LazyImageIcon(getResource(path)));
1062        }
1063 
1064     private void startOpaque(Attributes attributes) {
1065         if (_style != null) {
1066             _style.setOpaque(true);
1067             for(int i = attributes.getLength() - 1; i >= 0; i--) {
1068                 String key = attributes.getQName(i);
1069 
1070                 if (key.equals(ATTRIBUTE_VALUE)) {
1071                     _style.setOpaque("true".equals(attributes.getValue(i).
1072                                                    toLowerCase()));
1073                 }
1074             }
1075         }
1076     }
1077 
1078     private void startInputMap(Attributes attributes) throws SAXException {
1079         _inputMapBindings.clear();
1080         _inputMapID = null;
1081         if (_style != null) {
1082             for(int i = attributes.getLength() - 1; i >= 0; i--) {
1083                 String key = attributes.getQName(i);
1084 
1085                 if (key.equals(ATTRIBUTE_ID)) {
1086                     _inputMapID = attributes.getValue(i);
1087                 }
1088             }
1089         }
1090     }
1091 
1092     private void endInputMap() throws SAXException {
1093         if (_inputMapID != null) {
1094             register(_inputMapID, new UIDefaults.LazyInputMap(
1095                      _inputMapBindings.toArray(new Object[_inputMapBindings.
1096                      size()])));
1097         }
1098         _inputMapBindings.clear();
1099         _inputMapID = null;
1100     }
1101 
1102     private void startBindKey(Attributes attributes) throws SAXException {
1103         if (_inputMapID == null) {
1104             // Not in an inputmap, bail.
1105             return;
1106         }
1107         if (_style != null) {
1108             String key = null;
1109             String value = null;
1110             for(int i = attributes.getLength() - 1; i >= 0; i--) {
1111                 String aKey = attributes.getQName(i);
1112 
1113                 if (aKey.equals(ATTRIBUTE_KEY)) {
1114                     key = attributes.getValue(i);
1115                 }
1116                 else if (aKey.equals(ATTRIBUTE_ACTION)) {
1117                     value = attributes.getValue(i);
1118                 }
1119             }
1120             if (key == null || value == null) {
1121                 throw new SAXException(
1122                     "bindKey: you must supply a key and action");
1123             }
1124             _inputMapBindings.add(key);
1125             _inputMapBindings.add(value);
1126         }
1127     }
1128 
1129     //
1130     // SAX methods, these forward to the DocumentHandler if we don't know
1131     // the element name.
1132     //
1133 
1134     public InputSource resolveEntity(String publicId, String systemId)
1135                               throws IOException, SAXException {
1136         if (isForwarding()) {
1137             return getHandler().resolveEntity(publicId, systemId);
1138         }
1139         return null;
1140     }
1141 
1142     public void notationDecl(String name, String publicId, String systemId) throws SAXException {
1143         if (isForwarding()) {
1144             getHandler().notationDecl(name, publicId, systemId);
1145         }
1146     }
1147 
1148     public void unparsedEntityDecl(String name, String publicId,
1149                                    String systemId, String notationName) throws SAXException {
1150         if (isForwarding()) {
1151             getHandler().unparsedEntityDecl(name, publicId, systemId,
1152                                             notationName);
1153         }
1154     }
1155 
1156     public void setDocumentLocator(Locator locator) {
1157         if (isForwarding()) {
1158             getHandler().setDocumentLocator(locator);
1159         }
1160     }
1161 
1162     public void startDocument() throws SAXException {
1163         if (isForwarding()) {
1164             getHandler().startDocument();
1165         }
1166     }
1167 
1168     public void endDocument() throws SAXException {
1169         if (isForwarding()) {
1170             getHandler().endDocument();
1171         }
1172     }
1173 
1174     public void startElement(String uri, String local, String name, Attributes attributes)
1175                      throws SAXException {
1176         name = name.intern();
1177         if (name == ELEMENT_STYLE) {
1178             startStyle(attributes);
1179         }
1180         else if (name == ELEMENT_STATE) {
1181             startState(attributes);
1182         }
1183         else if (name == ELEMENT_FONT) {
1184             startFont(attributes);
1185         }
1186         else if (name == ELEMENT_COLOR) {
1187             startColor(attributes);
1188         }
1189         else if (name == ELEMENT_PAINTER) {
1190             startPainter(attributes, name);
1191         }
1192         else if (name == ELEMENT_IMAGE_PAINTER) {
1193             startPainter(attributes, name);
1194         }
1195         else if (name == ELEMENT_PROPERTY) {
1196             startProperty(attributes, ELEMENT_PROPERTY);
1197         }
1198         else if (name == ELEMENT_DEFAULTS_PROPERTY) {
1199             startProperty(attributes, ELEMENT_DEFAULTS_PROPERTY);
1200         }
1201         else if (name == ELEMENT_SYNTH_GRAPHICS) {
1202             startGraphics(attributes);
1203         }
1204         else if (name == ELEMENT_INSETS) {
1205             startInsets(attributes);
1206         }
1207         else if (name == ELEMENT_BIND) {
1208             startBind(attributes);
1209         }
1210         else if (name == ELEMENT_BIND_KEY) {
1211             startBindKey(attributes);
1212         }
1213         else if (name == ELEMENT_IMAGE_ICON) {
1214             startImageIcon(attributes);
1215         }
1216         else if (name == ELEMENT_OPAQUE) {
1217             startOpaque(attributes);
1218         }
1219         else if (name == ELEMENT_INPUT_MAP) {
1220             startInputMap(attributes);
1221         }
1222         else if (name != ELEMENT_SYNTH) {
1223             if (_depth++ == 0) {
1224                 getHandler().startDocument();
1225             }
1226             getHandler().startElement(uri, local, name, attributes);
1227         }
1228     }
1229 
1230     public void endElement(String uri, String local, String name) throws SAXException {
1231         if (isForwarding()) {
1232             getHandler().endElement(uri, local, name);
1233             _depth--;
1234             if (!isForwarding()) {
1235                 getHandler().startDocument();
1236             }
1237         }
1238         else {
1239             name = name.intern();
1240             if (name == ELEMENT_STYLE) {
1241                 endStyle();
1242             }
1243             else if (name == ELEMENT_STATE) {
1244                 endState();
1245             }
1246             else if (name == ELEMENT_INPUT_MAP) {
1247                 endInputMap();
1248             }
1249         }
1250     }
1251 
1252     public void characters(char ch[], int start, int length)
1253                            throws SAXException {
1254         if (isForwarding()) {
1255             getHandler().characters(ch, start, length);
1256         }
1257     }
1258 
1259     public void ignorableWhitespace (char ch[], int start, int length)
1260         throws SAXException {
1261         if (isForwarding()) {
1262             getHandler().ignorableWhitespace(ch, start, length);
1263         }
1264     }
1265 
1266     public void processingInstruction(String target, String data)
1267                                      throws SAXException {
1268         if (isForwarding()) {
1269             getHandler().processingInstruction(target, data);
1270         }
1271     }
1272 
1273     public void warning(SAXParseException e) throws SAXException {
1274         if (isForwarding()) {
1275             getHandler().warning(e);
1276         }
1277     }
1278 
1279     public void error(SAXParseException e) throws SAXException {
1280         if (isForwarding()) {
1281             getHandler().error(e);
1282         }
1283     }
1284 
1285 
1286     public void fatalError(SAXParseException e) throws SAXException {
1287         if (isForwarding()) {
1288             getHandler().fatalError(e);
1289         }
1290         throw e;
1291     }
1292 
1293 
1294     /**
1295      * ImageIcon that lazily loads the image until needed.
1296      */
1297     @SuppressWarnings("serial") // Superclass is not serializable across versions
1298     private static class LazyImageIcon extends ImageIcon implements UIResource {
1299         private URL location;
1300 
1301         public LazyImageIcon(URL location) {
1302             super();
1303             this.location = location;
1304         }
1305 
1306         public void paintIcon(Component c, Graphics g, int x, int y) {
1307             if (getImage() != null) {
1308                 super.paintIcon(c, g, x, y);
1309             }
1310         }
1311 
1312         public int getIconWidth() {
1313             if (getImage() != null) {
1314                 return super.getIconWidth();
1315             }
1316             return 0;
1317         }
1318 
1319         public int getIconHeight() {
1320             if (getImage() != null) {
1321                 return super.getIconHeight();
1322             }
1323             return 0;
1324         }
1325 
1326         public Image getImage() {
1327             if (location != null) {
1328                 setImage(Toolkit.getDefaultToolkit().getImage(location));
1329                 location = null;
1330             }
1331             return super.getImage();
1332         }
1333     }
1334 }