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                 value = Boolean.parseBoolean(aValue);
 750                 break;
 751             case 2: // dimension
 752                 StringTokenizer tok = new StringTokenizer(aValue);
 753                 value = new DimensionUIResource(
 754                     nextInt(tok, "Invalid dimension"),
 755                     nextInt(tok, "Invalid dimension"));
 756                 break;
 757             case 3: // insets
 758                 value = parseInsets(aValue, property + " invalid insets");
 759                 break;
 760             case 4: // integer
 761                 try {
 762                     value = Integer.valueOf(aValue);
 763                 } catch (NumberFormatException nfe) {
 764                     throw new SAXException(property + " invalid value");
 765                 }
 766                 break;
 767             case 5: //string
 768                 value = aValue;
 769                 break;
 770             }
 771         }
 772         if (value == null || key == null) {
 773             throw new SAXException(property + ": you must supply a " +
 774                                    "key and value");
 775         }
 776         if (property == ELEMENT_DEFAULTS_PROPERTY) {
 777             _defaultsMap.put(key, value);
 778         }
 779         else if (_stateInfo != null) {
 780             if (_stateInfo.getData() == null) {
 781                 _stateInfo.setData(new HashMap<>());
 782             }
 783             _stateInfo.getData().put(key, value);
 784         }
 785         else if (_style != null) {
 786             if (_style.getData() == null) {
 787                 _style.setData(new HashMap<>());
 788             }
 789             _style.getData().put(key, value);
 790         }
 791     }
 792 
 793     private void startGraphics(Attributes attributes) throws SAXException {
 794         SynthGraphicsUtils graphics = null;
 795 
 796         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 797             String key = attributes.getQName(i);
 798             if (key.equals(ATTRIBUTE_IDREF)) {
 799                 graphics = (SynthGraphicsUtils)lookup(attributes.getValue(i),
 800                                                  SynthGraphicsUtils.class);
 801             }
 802         }
 803         if (graphics == null) {
 804             throw new SAXException("graphicsUtils: you must supply an idref");
 805         }
 806         if (_style != null) {
 807             _style.setGraphicsUtils(graphics);
 808         }
 809     }
 810 
 811     private void startInsets(Attributes attributes) throws SAXException {
 812         int top = 0;
 813         int bottom = 0;
 814         int left = 0;
 815         int right = 0;
 816         Insets insets = null;
 817         String id = null;
 818 
 819         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 820             String key = attributes.getQName(i);
 821 
 822             try {
 823                 if (key.equals(ATTRIBUTE_IDREF)) {
 824                     insets = (Insets)lookup(attributes.getValue(i),
 825                                                    Insets.class);
 826                 }
 827                 else if (key.equals(ATTRIBUTE_ID)) {
 828                     id = attributes.getValue(i);
 829                 }
 830                 else if (key.equals(ATTRIBUTE_TOP)) {
 831                     top = Integer.parseInt(attributes.getValue(i));
 832                 }
 833                 else if (key.equals(ATTRIBUTE_LEFT)) {
 834                     left = Integer.parseInt(attributes.getValue(i));
 835                 }
 836                 else if (key.equals(ATTRIBUTE_BOTTOM)) {
 837                     bottom = Integer.parseInt(attributes.getValue(i));
 838                 }
 839                 else if (key.equals(ATTRIBUTE_RIGHT)) {
 840                     right = Integer.parseInt(attributes.getValue(i));
 841                 }
 842             } catch (NumberFormatException nfe) {
 843                 throw new SAXException("insets: bad integer value for " +
 844                                        attributes.getValue(i));
 845             }
 846         }
 847         if (insets == null) {
 848             insets = new InsetsUIResource(top, left, bottom, right);
 849         }
 850         register(id, insets);
 851         if (_style != null) {
 852             _style.setInsets(insets);
 853         }
 854     }
 855 
 856     private void startBind(Attributes attributes) throws SAXException {
 857         ParsedSynthStyle style = null;
 858         String path = null;
 859         int type = -1;
 860 
 861         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 862             String key = attributes.getQName(i);
 863 
 864             if (key.equals(ATTRIBUTE_STYLE)) {
 865                 style = (ParsedSynthStyle)lookup(attributes.getValue(i),
 866                                                   ParsedSynthStyle.class);
 867             }
 868             else if (key.equals(ATTRIBUTE_TYPE)) {
 869                 String typeS = attributes.getValue(i).toUpperCase();
 870 
 871                 if (typeS.equals("NAME")) {
 872                     type = DefaultSynthStyleFactory.NAME;
 873                 }
 874                 else if (typeS.equals("REGION")) {
 875                     type = DefaultSynthStyleFactory.REGION;
 876                 }
 877                 else {
 878                     throw new SAXException("bind: unknown type " + typeS);
 879                 }
 880             }
 881             else if (key.equals(ATTRIBUTE_KEY)) {
 882                 path = attributes.getValue(i);
 883             }
 884         }
 885         if (style == null || path == null || type == -1) {
 886             throw new SAXException("bind: you must specify a style, type " +
 887                                    "and key");
 888         }
 889         try {
 890             _factory.addStyle(style, path, type);
 891         } catch (PatternSyntaxException pse) {
 892             throw new SAXException("bind: " + path + " is not a valid " +
 893                                    "regular expression");
 894         }
 895     }
 896 
 897     private void startPainter(Attributes attributes, String type) throws SAXException {
 898         Insets sourceInsets = null;
 899         Insets destInsets = null;
 900         String path = null;
 901         boolean paintCenter = true;
 902         boolean stretch = true;
 903         SynthPainter painter = null;
 904         String method = null;
 905         String id = null;
 906         int direction = -1;
 907         boolean center = false;
 908 
 909         boolean stretchSpecified = false;
 910         boolean paintCenterSpecified = false;
 911 
 912         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 913             String key = attributes.getQName(i);
 914             String value = attributes.getValue(i);
 915 
 916             if (key.equals(ATTRIBUTE_ID)) {
 917                 id = value;
 918             }
 919             else if (key.equals(ATTRIBUTE_METHOD)) {
 920                 method = value.toLowerCase(Locale.ENGLISH);
 921             }
 922             else if (key.equals(ATTRIBUTE_IDREF)) {
 923                 painter = (SynthPainter)lookup(value, SynthPainter.class);
 924             }
 925             else if (key.equals(ATTRIBUTE_PATH)) {
 926                 path = value;
 927             }
 928             else if (key.equals(ATTRIBUTE_SOURCE_INSETS)) {
 929                 sourceInsets = parseInsets(value, type +
 930                    ": sourceInsets must be top left bottom right");
 931             }
 932             else if (key.equals(ATTRIBUTE_DEST_INSETS)) {
 933                 destInsets = parseInsets(value, type +
 934                   ": destinationInsets must be top left bottom right");
 935             }
 936             else if (key.equals(ATTRIBUTE_PAINT_CENTER)) {
 937                 paintCenter = Boolean.parseBoolean(value);
 938                 paintCenterSpecified = true;
 939             }
 940             else if (key.equals(ATTRIBUTE_STRETCH)) {
 941                 stretch = Boolean.parseBoolean(value);
 942                 stretchSpecified = true;
 943             }
 944             else if (key.equals(ATTRIBUTE_DIRECTION)) {
 945                 value = value.toUpperCase().intern();
 946                 if (value == "EAST") {
 947                     direction = SwingConstants.EAST;
 948                 }
 949                 else if (value == "NORTH") {
 950                     direction = SwingConstants.NORTH;
 951                 }
 952                 else if (value == "SOUTH") {
 953                     direction = SwingConstants.SOUTH;
 954                 }
 955                 else if (value == "WEST") {
 956                     direction = SwingConstants.WEST;
 957                 }
 958                 else if (value == "TOP") {
 959                     direction = SwingConstants.TOP;
 960                 }
 961                 else if (value == "LEFT") {
 962                     direction = SwingConstants.LEFT;
 963                 }
 964                 else if (value == "BOTTOM") {
 965                     direction = SwingConstants.BOTTOM;
 966                 }
 967                 else if (value == "RIGHT") {
 968                     direction = SwingConstants.RIGHT;
 969                 }
 970                 else if (value == "HORIZONTAL") {
 971                     direction = SwingConstants.HORIZONTAL;
 972                 }
 973                 else if (value == "VERTICAL") {
 974                     direction = SwingConstants.VERTICAL;
 975                 }
 976                 else if (value == "HORIZONTAL_SPLIT") {
 977                     direction = JSplitPane.HORIZONTAL_SPLIT;
 978                 }
 979                 else if (value == "VERTICAL_SPLIT") {
 980                     direction = JSplitPane.VERTICAL_SPLIT;
 981                 }
 982                 else {
 983                     throw new SAXException(type + ": unknown direction");
 984                 }
 985             }
 986             else if (key.equals(ATTRIBUTE_CENTER)) {
 987                 center = Boolean.parseBoolean(value);
 988             }
 989         }
 990         if (painter == null) {
 991             if (type == ELEMENT_PAINTER) {
 992                 throw new SAXException(type +
 993                              ": you must specify an idref");
 994             }
 995             if (sourceInsets == null && !center) {
 996                 throw new SAXException(
 997                              "property: you must specify sourceInsets");
 998             }
 999             if (path == null) {
1000                 throw new SAXException("property: you must specify a path");
1001             }
1002             if (center && (sourceInsets != null || destInsets != null ||
1003                            paintCenterSpecified || stretchSpecified)) {
1004                 throw new SAXException("The attributes: sourceInsets, " +
1005                                        "destinationInsets, paintCenter and stretch " +
1006                                        " are not legal when center is true");
1007             }
1008             painter = new ImagePainter(!stretch, paintCenter,
1009                      sourceInsets, destInsets, getResource(path), center);
1010         }
1011         register(id, painter);
1012         if (_stateInfo != null) {
1013             addPainterOrMerge(_statePainters, method, painter, direction);
1014         }
1015         else if (_style != null) {
1016             addPainterOrMerge(_stylePainters, method, painter, direction);
1017         }
1018     }
1019 
1020     private void addPainterOrMerge(List<ParsedSynthStyle.PainterInfo> painters, String method,
1021                                    SynthPainter painter, int direction) {
1022         ParsedSynthStyle.PainterInfo painterInfo;
1023         painterInfo = new ParsedSynthStyle.PainterInfo(method,
1024                                                        painter,
1025                                                        direction);
1026 
1027         for (Object infoObject: painters) {
1028             ParsedSynthStyle.PainterInfo info;
1029             info = (ParsedSynthStyle.PainterInfo) infoObject;
1030 
1031             if (painterInfo.equalsPainter(info)) {
1032                 info.addPainter(painter);
1033                 return;
1034             }
1035         }
1036 
1037         painters.add(painterInfo);
1038     }
1039 
1040     private void startImageIcon(Attributes attributes) throws SAXException {
1041         String path = null;
1042         String id = null;
1043 
1044         for(int i = attributes.getLength() - 1; i >= 0; i--) {
1045             String key = attributes.getQName(i);
1046 
1047             if (key.equals(ATTRIBUTE_ID)) {
1048                 id = attributes.getValue(i);
1049             }
1050             else if (key.equals(ATTRIBUTE_PATH)) {
1051                 path = attributes.getValue(i);
1052             }
1053         }
1054         if (path == null) {
1055             throw new SAXException("imageIcon: you must specify a path");
1056         }
1057         register(id, new LazyImageIcon(getResource(path)));
1058        }
1059 
1060     private void startOpaque(Attributes attributes) {
1061         if (_style != null) {
1062             _style.setOpaque(true);
1063             for(int i = attributes.getLength() - 1; i >= 0; i--) {
1064                 String key = attributes.getQName(i);
1065 
1066                 if (key.equals(ATTRIBUTE_VALUE)) {
1067                     _style.setOpaque("true".equals(attributes.getValue(i).
1068                                                    toLowerCase()));
1069                 }
1070             }
1071         }
1072     }
1073 
1074     private void startInputMap(Attributes attributes) throws SAXException {
1075         _inputMapBindings.clear();
1076         _inputMapID = null;
1077         if (_style != null) {
1078             for(int i = attributes.getLength() - 1; i >= 0; i--) {
1079                 String key = attributes.getQName(i);
1080 
1081                 if (key.equals(ATTRIBUTE_ID)) {
1082                     _inputMapID = attributes.getValue(i);
1083                 }
1084             }
1085         }
1086     }
1087 
1088     private void endInputMap() throws SAXException {
1089         if (_inputMapID != null) {
1090             register(_inputMapID, new UIDefaults.LazyInputMap(
1091                      _inputMapBindings.toArray(new Object[_inputMapBindings.
1092                      size()])));
1093         }
1094         _inputMapBindings.clear();
1095         _inputMapID = null;
1096     }
1097 
1098     private void startBindKey(Attributes attributes) throws SAXException {
1099         if (_inputMapID == null) {
1100             // Not in an inputmap, bail.
1101             return;
1102         }
1103         if (_style != null) {
1104             String key = null;
1105             String value = null;
1106             for(int i = attributes.getLength() - 1; i >= 0; i--) {
1107                 String aKey = attributes.getQName(i);
1108 
1109                 if (aKey.equals(ATTRIBUTE_KEY)) {
1110                     key = attributes.getValue(i);
1111                 }
1112                 else if (aKey.equals(ATTRIBUTE_ACTION)) {
1113                     value = attributes.getValue(i);
1114                 }
1115             }
1116             if (key == null || value == null) {
1117                 throw new SAXException(
1118                     "bindKey: you must supply a key and action");
1119             }
1120             _inputMapBindings.add(key);
1121             _inputMapBindings.add(value);
1122         }
1123     }
1124 
1125     //
1126     // SAX methods, these forward to the DocumentHandler if we don't know
1127     // the element name.
1128     //
1129 
1130     public InputSource resolveEntity(String publicId, String systemId)
1131                               throws IOException, SAXException {
1132         if (isForwarding()) {
1133             return getHandler().resolveEntity(publicId, systemId);
1134         }
1135         return null;
1136     }
1137 
1138     public void notationDecl(String name, String publicId, String systemId) throws SAXException {
1139         if (isForwarding()) {
1140             getHandler().notationDecl(name, publicId, systemId);
1141         }
1142     }
1143 
1144     public void unparsedEntityDecl(String name, String publicId,
1145                                    String systemId, String notationName) throws SAXException {
1146         if (isForwarding()) {
1147             getHandler().unparsedEntityDecl(name, publicId, systemId,
1148                                             notationName);
1149         }
1150     }
1151 
1152     public void setDocumentLocator(Locator locator) {
1153         if (isForwarding()) {
1154             getHandler().setDocumentLocator(locator);
1155         }
1156     }
1157 
1158     public void startDocument() throws SAXException {
1159         if (isForwarding()) {
1160             getHandler().startDocument();
1161         }
1162     }
1163 
1164     public void endDocument() throws SAXException {
1165         if (isForwarding()) {
1166             getHandler().endDocument();
1167         }
1168     }
1169 
1170     public void startElement(String uri, String local, String name, Attributes attributes)
1171                      throws SAXException {
1172         name = name.intern();
1173         if (name == ELEMENT_STYLE) {
1174             startStyle(attributes);
1175         }
1176         else if (name == ELEMENT_STATE) {
1177             startState(attributes);
1178         }
1179         else if (name == ELEMENT_FONT) {
1180             startFont(attributes);
1181         }
1182         else if (name == ELEMENT_COLOR) {
1183             startColor(attributes);
1184         }
1185         else if (name == ELEMENT_PAINTER) {
1186             startPainter(attributes, name);
1187         }
1188         else if (name == ELEMENT_IMAGE_PAINTER) {
1189             startPainter(attributes, name);
1190         }
1191         else if (name == ELEMENT_PROPERTY) {
1192             startProperty(attributes, ELEMENT_PROPERTY);
1193         }
1194         else if (name == ELEMENT_DEFAULTS_PROPERTY) {
1195             startProperty(attributes, ELEMENT_DEFAULTS_PROPERTY);
1196         }
1197         else if (name == ELEMENT_SYNTH_GRAPHICS) {
1198             startGraphics(attributes);
1199         }
1200         else if (name == ELEMENT_INSETS) {
1201             startInsets(attributes);
1202         }
1203         else if (name == ELEMENT_BIND) {
1204             startBind(attributes);
1205         }
1206         else if (name == ELEMENT_BIND_KEY) {
1207             startBindKey(attributes);
1208         }
1209         else if (name == ELEMENT_IMAGE_ICON) {
1210             startImageIcon(attributes);
1211         }
1212         else if (name == ELEMENT_OPAQUE) {
1213             startOpaque(attributes);
1214         }
1215         else if (name == ELEMENT_INPUT_MAP) {
1216             startInputMap(attributes);
1217         }
1218         else if (name != ELEMENT_SYNTH) {
1219             if (_depth++ == 0) {
1220                 getHandler().startDocument();
1221             }
1222             getHandler().startElement(uri, local, name, attributes);
1223         }
1224     }
1225 
1226     public void endElement(String uri, String local, String name) throws SAXException {
1227         if (isForwarding()) {
1228             getHandler().endElement(uri, local, name);
1229             _depth--;
1230             if (!isForwarding()) {
1231                 getHandler().startDocument();
1232             }
1233         }
1234         else {
1235             name = name.intern();
1236             if (name == ELEMENT_STYLE) {
1237                 endStyle();
1238             }
1239             else if (name == ELEMENT_STATE) {
1240                 endState();
1241             }
1242             else if (name == ELEMENT_INPUT_MAP) {
1243                 endInputMap();
1244             }
1245         }
1246     }
1247 
1248     public void characters(char ch[], int start, int length)
1249                            throws SAXException {
1250         if (isForwarding()) {
1251             getHandler().characters(ch, start, length);
1252         }
1253     }
1254 
1255     public void ignorableWhitespace (char ch[], int start, int length)
1256         throws SAXException {
1257         if (isForwarding()) {
1258             getHandler().ignorableWhitespace(ch, start, length);
1259         }
1260     }
1261 
1262     public void processingInstruction(String target, String data)
1263                                      throws SAXException {
1264         if (isForwarding()) {
1265             getHandler().processingInstruction(target, data);
1266         }
1267     }
1268 
1269     public void warning(SAXParseException e) throws SAXException {
1270         if (isForwarding()) {
1271             getHandler().warning(e);
1272         }
1273     }
1274 
1275     public void error(SAXParseException e) throws SAXException {
1276         if (isForwarding()) {
1277             getHandler().error(e);
1278         }
1279     }
1280 
1281 
1282     public void fatalError(SAXParseException e) throws SAXException {
1283         if (isForwarding()) {
1284             getHandler().fatalError(e);
1285         }
1286         throw e;
1287     }
1288 
1289 
1290     /**
1291      * ImageIcon that lazily loads the image until needed.
1292      */
1293     @SuppressWarnings("serial") // Superclass is not serializable across versions
1294     private static class LazyImageIcon extends ImageIcon implements UIResource {
1295         private URL location;
1296 
1297         public LazyImageIcon(URL location) {
1298             super();
1299             this.location = location;
1300         }
1301 
1302         public void paintIcon(Component c, Graphics g, int x, int y) {
1303             if (getImage() != null) {
1304                 super.paintIcon(c, g, x, y);
1305             }
1306         }
1307 
1308         public int getIconWidth() {
1309             if (getImage() != null) {
1310                 return super.getIconWidth();
1311             }
1312             return 0;
1313         }
1314 
1315         public int getIconHeight() {
1316             if (getImage() != null) {
1317                 return super.getIconHeight();
1318             }
1319             return 0;
1320         }
1321 
1322         public Image getImage() {
1323             if (location != null) {
1324                 setImage(Toolkit.getDefaultToolkit().getImage(location));
1325                 location = null;
1326             }
1327             return super.getImage();
1328         }
1329     }
1330 }