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