1 /*
   2  * Copyright (c) 2003, 2006, 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.Locale;
  44 import java.util.Map;
  45 import java.util.StringTokenizer;
  46 import java.util.regex.PatternSyntaxException;
  47 
  48 import javax.swing.ImageIcon;
  49 import javax.swing.JSplitPane;
  50 import javax.swing.SwingConstants;
  51 import javax.swing.UIDefaults;
  52 import javax.swing.plaf.ColorUIResource;
  53 import javax.swing.plaf.DimensionUIResource;
  54 import javax.swing.plaf.FontUIResource;
  55 import javax.swing.plaf.InsetsUIResource;
  56 import javax.swing.plaf.UIResource;
  57 import javax.xml.parsers.ParserConfigurationException;
  58 import javax.xml.parsers.SAXParser;
  59 import javax.xml.parsers.SAXParserFactory;
  60 
  61 import org.xml.sax.AttributeList;
  62 import org.xml.sax.HandlerBase;
  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 
  68 import com.sun.beans.ObjectHandler;
  69 
  70 /**
  71  */
  72 class SynthParser extends HandlerBase {
  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 ObjectHandler _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 java.util.List _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 java.util.List _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 java.util.List _colorTypes;
 183 
 184     /**
 185      * defaultsPropertys are placed here.
 186      */
 187     private Map _defaultsMap;
 188 
 189     /**
 190      * List of SynthStyle.Painters that will be applied to the current style.
 191      */
 192     private java.util.List _stylePainters;
 193 
 194     /**
 195      * List of SynthStyle.Painters that will be applied to the current state.
 196      */
 197     private java.util.List _statePainters;
 198 
 199     SynthParser() {
 200         _mapping = new HashMap<String,Object>();
 201         _stateInfos = new ArrayList();
 202         _colorTypes = new ArrayList();
 203         _inputMapBindings = new ArrayList();
 204         _stylePainters = new ArrayList();
 205         _statePainters = new ArrayList();
 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 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 ObjectHandler getHandler() {
 297         if (_handler == null) {
 298             if (_urlResourceBase != null) {
 299                 // getHandler() is never called before parse() so it is safe
 300                 // to create a URLClassLoader with _resourceBase.
 301                 //
 302                 // getResource(".") is called to ensure we have the directory
 303                 // containing the resources in the case the resource base is a
 304                 // .class file.
 305                 URL[] urls = new URL[] { getResource(".") };
 306                 ClassLoader parent = Thread.currentThread().getContextClassLoader();
 307                 ClassLoader urlLoader = new URLClassLoader(urls, parent);
 308                 _handler = new ObjectHandler(null, urlLoader);
 309             } else {
 310                 _handler = new ObjectHandler(null,
 311                     _classResourceBase.getClassLoader());
 312             }
 313 
 314             for (String key : _mapping.keySet()) {
 315                 _handler.register(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 = null;
 339         if (_handler != null) {
 340             if ((value = _handler.lookup(key)) != null) {
 341                 return checkCast(value, 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.lookup(key) != null)) {
 359                 throw new SAXException("ID " + key + " is already defined");
 360             }
 361             if (_handler != null) {
 362                 _handler.register(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(AttributeList 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.getName(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() throws SAXException {
 426         int size = _stylePainters.size();
 427         if (size > 0) {
 428             _style.setPainters((ParsedSynthStyle.PainterInfo[])
 429                   _stylePainters.toArray(new ParsedSynthStyle.
 430                   PainterInfo[size]));
 431             _stylePainters.clear();
 432         }
 433         size = _stateInfos.size();
 434         if (size > 0) {
 435             _style.setStateInfo((ParsedSynthStyle.StateInfo[])_stateInfos.
 436                  toArray(new ParsedSynthStyle.StateInfo[size]));
 437             _stateInfos.clear();
 438         }
 439         _style = null;
 440     }
 441 
 442     private void startState(AttributeList attributes) throws SAXException {
 443         ParsedSynthStyle.StateInfo stateInfo = null;
 444         int state = 0;
 445         String id = null;
 446 
 447         _stateInfo = null;
 448         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 449             String key = attributes.getName(i);
 450             if (key.equals(ATTRIBUTE_ID)) {
 451                 id = attributes.getValue(i);
 452             }
 453             else if (key.equals(ATTRIBUTE_IDREF)) {
 454                 _stateInfo = (ParsedSynthStyle.StateInfo)lookup(
 455                    attributes.getValue(i), ParsedSynthStyle.StateInfo.class);
 456             }
 457             else if (key.equals(ATTRIBUTE_CLONE)) {
 458                 _stateInfo = (ParsedSynthStyle.StateInfo)((ParsedSynthStyle.
 459                              StateInfo)lookup(attributes.getValue(i),
 460                              ParsedSynthStyle.StateInfo.class)).clone();
 461             }
 462             else if (key.equals(ATTRIBUTE_VALUE)) {
 463                 StringTokenizer tokenizer = new StringTokenizer(
 464                                    attributes.getValue(i));
 465                 while (tokenizer.hasMoreTokens()) {
 466                     String stateString = tokenizer.nextToken().toUpperCase().
 467                                                    intern();
 468                     if (stateString == "ENABLED") {
 469                         state |= SynthConstants.ENABLED;
 470                     }
 471                     else if (stateString == "MOUSE_OVER") {
 472                         state |= SynthConstants.MOUSE_OVER;
 473                     }
 474                     else if (stateString == "PRESSED") {
 475                         state |= SynthConstants.PRESSED;
 476                     }
 477                     else if (stateString == "DISABLED") {
 478                         state |= SynthConstants.DISABLED;
 479                     }
 480                     else if (stateString == "FOCUSED") {
 481                         state |= SynthConstants.FOCUSED;
 482                     }
 483                     else if (stateString == "SELECTED") {
 484                         state |= SynthConstants.SELECTED;
 485                     }
 486                     else if (stateString == "DEFAULT") {
 487                         state |= SynthConstants.DEFAULT;
 488                     }
 489                     else if (stateString != "AND") {
 490                         throw new SAXException("Unknown state: " + state);
 491                     }
 492                 }
 493             }
 494         }
 495         if (_stateInfo == null) {
 496             _stateInfo = new ParsedSynthStyle.StateInfo();
 497         }
 498         _stateInfo.setComponentState(state);
 499         register(id, _stateInfo);
 500         _stateInfos.add(_stateInfo);
 501     }
 502 
 503     private void endState() throws SAXException {
 504         int size = _statePainters.size();
 505         if (size > 0) {
 506             _stateInfo.setPainters((ParsedSynthStyle.PainterInfo[])
 507                   _statePainters.toArray(new ParsedSynthStyle.
 508                   PainterInfo[size]));
 509             _statePainters.clear();
 510         }
 511         _stateInfo = null;
 512     }
 513 
 514     private void startFont(AttributeList attributes) throws SAXException {
 515         Font font = null;
 516         int style = Font.PLAIN;
 517         int size = 0;
 518         String id = null;
 519         String name = null;
 520 
 521         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 522             String key = attributes.getName(i);
 523             if (key.equals(ATTRIBUTE_ID)) {
 524                 id = attributes.getValue(i);
 525             }
 526             else if (key.equals(ATTRIBUTE_IDREF)) {
 527                 font = (Font)lookup(attributes.getValue(i), Font.class);
 528             }
 529             else if (key.equals(ATTRIBUTE_NAME)) {
 530                 name = attributes.getValue(i);
 531             }
 532             else if (key.equals(ATTRIBUTE_SIZE)) {
 533                 try {
 534                     size = Integer.parseInt(attributes.getValue(i));
 535                 } catch (NumberFormatException nfe) {
 536                     throw new SAXException("Invalid font size: " +
 537                                            attributes.getValue(i));
 538                 }
 539             }
 540             else if (key.equals(ATTRIBUTE_STYLE)) {
 541                 StringTokenizer tok = new StringTokenizer(
 542                                                 attributes.getValue(i));
 543                 while (tok.hasMoreTokens()) {
 544                     String token = tok.nextToken().intern();
 545                     if (token == "BOLD") {
 546                         style = ((style | Font.PLAIN) ^ Font.PLAIN) |
 547                                 Font.BOLD;
 548                     }
 549                     else if (token == "ITALIC") {
 550                         style |= Font.ITALIC;
 551                     }
 552                 }
 553             }
 554         }
 555         if (font == null) {
 556             if (name == null) {
 557                 throw new SAXException("You must define a name for the font");
 558             }
 559             if (size == 0) {
 560                 throw new SAXException("You must define a size for the font");
 561             }
 562             font = new FontUIResource(name, style, size);
 563         }
 564         else if (name != null || size != 0 || style != Font.PLAIN) {
 565             throw new SAXException("Name, size and style are not for use " +
 566                                    "with idref");
 567         }
 568         register(id, font);
 569         if (_stateInfo != null) {
 570             _stateInfo.setFont(font);
 571         }
 572         else if (_style != null) {
 573             _style.setFont(font);
 574         }
 575     }
 576 
 577     private void startColor(AttributeList attributes) throws SAXException {
 578         Color color = null;
 579         String id = null;
 580 
 581         _colorTypes.clear();
 582         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 583             String key = attributes.getName(i);
 584             if (key.equals(ATTRIBUTE_ID)) {
 585                 id = attributes.getValue(i);
 586             }
 587             else if (key.equals(ATTRIBUTE_IDREF)) {
 588                 color = (Color)lookup(attributes.getValue(i), Color.class);
 589             }
 590             else if (key.equals(ATTRIBUTE_NAME)) {
 591             }
 592             else if (key.equals(ATTRIBUTE_VALUE)) {
 593                 String value = attributes.getValue(i);
 594 
 595                 if (value.startsWith("#")) {
 596                     try {
 597                         int argb;
 598                         boolean hasAlpha;
 599 
 600                         int length = value.length();
 601                         if (length < 8) {
 602                             // Just RGB, or some portion of it.
 603                             argb = Integer.decode(value);
 604                             hasAlpha = false;
 605                         } else if (length == 8) {
 606                             // Single character alpha: #ARRGGBB.
 607                             argb = Integer.decode(value);
 608                             hasAlpha = true;
 609                         } else if (length == 9) {
 610                             // Color has alpha and is of the form
 611                             // #AARRGGBB.
 612                             // The following split decoding is mandatory due to
 613                             // Integer.decode() behavior which won't decode
 614                             // hexadecimal values higher than #7FFFFFFF.
 615                             // Thus, when an alpha channel is detected, it is
 616                             // decoded separately from the RGB channels.
 617                             int rgb = Integer.decode('#' +
 618                                                      value.substring(3, 9));
 619                             int a = Integer.decode(value.substring(0, 3));
 620                             argb = (a << 24) | rgb;
 621                             hasAlpha = true;
 622                         } else {
 623                             throw new SAXException("Invalid Color value: "
 624                                 + value);
 625                         }
 626 
 627                         color = new ColorUIResource(new Color(argb, hasAlpha));
 628                     } catch (NumberFormatException nfe) {
 629                         throw new SAXException("Invalid Color value: " +value);
 630                     }
 631                 }
 632                 else {
 633                     try {
 634                         color = new ColorUIResource((Color)Color.class.
 635                               getField(value.toUpperCase()).get(Color.class));
 636                     } catch (NoSuchFieldException nsfe) {
 637                         throw new SAXException("Invalid color name: " + value);
 638                     } catch (IllegalAccessException iae) {
 639                         throw new SAXException("Invalid color name: " + value);
 640                     }
 641                 }
 642             }
 643             else if (key.equals(ATTRIBUTE_TYPE)) {
 644                 StringTokenizer tokenizer = new StringTokenizer(
 645                                    attributes.getValue(i));
 646                 while (tokenizer.hasMoreTokens()) {
 647                     String typeName = tokenizer.nextToken();
 648                     int classIndex = typeName.lastIndexOf('.');
 649                     Class typeClass;
 650 
 651                     if (classIndex == -1) {
 652                         typeClass = ColorType.class;
 653                         classIndex = 0;
 654                     }
 655                     else {
 656                         try {
 657                             typeClass = Class.forName(typeName.substring(
 658                                                       0, classIndex));
 659                         } catch (ClassNotFoundException cnfe) {
 660                             throw new SAXException("Unknown class: " +
 661                                       typeName.substring(0, classIndex));
 662                         }
 663                         classIndex++;
 664                     }
 665                     try {
 666                         _colorTypes.add((ColorType)checkCast(typeClass.
 667                               getField(typeName.substring(classIndex,
 668                               typeName.length() - classIndex)).
 669                               get(typeClass), ColorType.class));
 670                     } catch (NoSuchFieldException nsfe) {
 671                         throw new SAXException("Unable to find color type: " +
 672                                                typeName);
 673                     } catch (IllegalAccessException iae) {
 674                         throw new SAXException("Unable to find color type: " +
 675                                                typeName);
 676                     }
 677                 }
 678             }
 679         }
 680         if (color == null) {
 681             throw new SAXException("color: you must specificy a value");
 682         }
 683         register(id, color);
 684         if (_stateInfo != null && _colorTypes.size() > 0) {
 685             Color[] colors = _stateInfo.getColors();
 686             int max = 0;
 687             for (int counter = _colorTypes.size() - 1; counter >= 0;
 688                      counter--) {
 689                 max = Math.max(max, ((ColorType)_colorTypes.get(counter)).
 690                                getID());
 691             }
 692             if (colors == null || colors.length <= max) {
 693                 Color[] newColors = new Color[max + 1];
 694                 if (colors != null) {
 695                     System.arraycopy(colors, 0, newColors, 0, colors.length);
 696                 }
 697                 colors = newColors;
 698             }
 699             for (int counter = _colorTypes.size() - 1; counter >= 0;
 700                      counter--) {
 701                 colors[((ColorType)_colorTypes.get(counter)).getID()] = color;
 702             }
 703             _stateInfo.setColors(colors);
 704         }
 705     }
 706 
 707     private void startProperty(AttributeList attributes,
 708                                Object property) throws SAXException {
 709         Object value = null;
 710         Object key = null;
 711         // Type of the value: 0=idref, 1=boolean, 2=dimension, 3=insets,
 712         // 4=integer,5=string
 713         int iType = 0;
 714         String aValue = null;
 715 
 716         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 717             String aName = attributes.getName(i);
 718             if (aName.equals(ATTRIBUTE_TYPE)) {
 719                 String type = attributes.getValue(i).toUpperCase();
 720                 if (type.equals("IDREF")) {
 721                     iType = 0;
 722                 }
 723                 else if (type.equals("BOOLEAN")) {
 724                     iType = 1;
 725                 }
 726                 else if (type.equals("DIMENSION")) {
 727                     iType = 2;
 728                 }
 729                 else if (type.equals("INSETS")) {
 730                     iType = 3;
 731                 }
 732                 else if (type.equals("INTEGER")) {
 733                     iType = 4;
 734                 }
 735                 else if (type.equals("STRING")) {
 736                     iType = 5;
 737                 }
 738                 else {
 739                     throw new SAXException(property + " unknown type, use" +
 740                         "idref, boolean, dimension, insets or integer");
 741                 }
 742             }
 743             else if (aName.equals(ATTRIBUTE_VALUE)) {
 744                 aValue = attributes.getValue(i);
 745             }
 746             else if (aName.equals(ATTRIBUTE_KEY)) {
 747                 key = attributes.getValue(i);
 748             }
 749         }
 750         if (aValue != null) {
 751             switch (iType) {
 752             case 0: // idref
 753                 value = lookup(aValue, Object.class);
 754                 break;
 755             case 1: // boolean
 756                 if (aValue.toUpperCase().equals("TRUE")) {
 757                     value = Boolean.TRUE;
 758                 }
 759                 else {
 760                     value = Boolean.FALSE;
 761                 }
 762                 break;
 763             case 2: // dimension
 764                 StringTokenizer tok = new StringTokenizer(aValue);
 765                 value = new DimensionUIResource(
 766                     nextInt(tok, "Invalid dimension"),
 767                     nextInt(tok, "Invalid dimension"));
 768                 break;
 769             case 3: // insets
 770                 value = parseInsets(aValue, property + " invalid insets");
 771                 break;
 772             case 4: // integer
 773                 try {
 774                     value = new Integer(Integer.parseInt(aValue));
 775                 } catch (NumberFormatException nfe) {
 776                     throw new SAXException(property + " invalid value");
 777                 }
 778                 break;
 779             case 5: //string
 780                 value = aValue;
 781                 break;
 782             }
 783         }
 784         if (value == null || key == null) {
 785             throw new SAXException(property + ": you must supply a " +
 786                                    "key and value");
 787         }
 788         if (property == ELEMENT_DEFAULTS_PROPERTY) {
 789             _defaultsMap.put(key, value);
 790         }
 791         else if (_stateInfo != null) {
 792             if (_stateInfo.getData() == null) {
 793                 _stateInfo.setData(new HashMap());
 794             }
 795             _stateInfo.getData().put(key, value);
 796         }
 797         else if (_style != null) {
 798             if (_style.getData() == null) {
 799                 _style.setData(new HashMap());
 800             }
 801             _style.getData().put(key, value);
 802         }
 803     }
 804 
 805     private void startGraphics(AttributeList attributes) throws SAXException {
 806         SynthGraphicsUtils graphics = null;
 807 
 808         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 809             String key = attributes.getName(i);
 810             if (key.equals(ATTRIBUTE_IDREF)) {
 811                 graphics = (SynthGraphicsUtils)lookup(attributes.getValue(i),
 812                                                  SynthGraphicsUtils.class);
 813             }
 814         }
 815         if (graphics == null) {
 816             throw new SAXException("graphicsUtils: you must supply an idref");
 817         }
 818         if (_style != null) {
 819             _style.setGraphicsUtils(graphics);
 820         }
 821     }
 822 
 823     private void startInsets(AttributeList attributes) throws SAXException {
 824         int top = 0;
 825         int bottom = 0;
 826         int left = 0;
 827         int right = 0;
 828         Insets insets = null;
 829         String id = null;
 830 
 831         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 832             String key = attributes.getName(i);
 833 
 834             try {
 835                 if (key.equals(ATTRIBUTE_IDREF)) {
 836                     insets = (Insets)lookup(attributes.getValue(i),
 837                                                    Insets.class);
 838                 }
 839                 else if (key.equals(ATTRIBUTE_ID)) {
 840                     id = attributes.getValue(i);
 841                 }
 842                 else if (key.equals(ATTRIBUTE_TOP)) {
 843                     top = Integer.parseInt(attributes.getValue(i));
 844                 }
 845                 else if (key.equals(ATTRIBUTE_LEFT)) {
 846                     left = Integer.parseInt(attributes.getValue(i));
 847                 }
 848                 else if (key.equals(ATTRIBUTE_BOTTOM)) {
 849                     bottom = Integer.parseInt(attributes.getValue(i));
 850                 }
 851                 else if (key.equals(ATTRIBUTE_RIGHT)) {
 852                     right = Integer.parseInt(attributes.getValue(i));
 853                 }
 854             } catch (NumberFormatException nfe) {
 855                 throw new SAXException("insets: bad integer value for " +
 856                                        attributes.getValue(i));
 857             }
 858         }
 859         if (insets == null) {
 860             insets = new InsetsUIResource(top, left, bottom, right);
 861         }
 862         register(id, insets);
 863         if (_style != null) {
 864             _style.setInsets(insets);
 865         }
 866     }
 867 
 868     private void startBind(AttributeList attributes) throws SAXException {
 869         ParsedSynthStyle style = null;
 870         String path = null;
 871         int type = -1;
 872 
 873         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 874             String key = attributes.getName(i);
 875 
 876             if (key.equals(ATTRIBUTE_STYLE)) {
 877                 style = (ParsedSynthStyle)lookup(attributes.getValue(i),
 878                                                   ParsedSynthStyle.class);
 879             }
 880             else if (key.equals(ATTRIBUTE_TYPE)) {
 881                 String typeS = attributes.getValue(i).toUpperCase();
 882 
 883                 if (typeS.equals("NAME")) {
 884                     type = DefaultSynthStyleFactory.NAME;
 885                 }
 886                 else if (typeS.equals("REGION")) {
 887                     type = DefaultSynthStyleFactory.REGION;
 888                 }
 889                 else {
 890                     throw new SAXException("bind: unknown type " + typeS);
 891                 }
 892             }
 893             else if (key.equals(ATTRIBUTE_KEY)) {
 894                 path = attributes.getValue(i);
 895             }
 896         }
 897         if (style == null || path == null || type == -1) {
 898             throw new SAXException("bind: you must specify a style, type " +
 899                                    "and key");
 900         }
 901         try {
 902             _factory.addStyle(style, path, type);
 903         } catch (PatternSyntaxException pse) {
 904             throw new SAXException("bind: " + path + " is not a valid " +
 905                                    "regular expression");
 906         }
 907     }
 908 
 909     private void startPainter(AttributeList attributes, String type) throws SAXException {
 910         Insets sourceInsets = null;
 911         Insets destInsets = null;
 912         String path = null;
 913         boolean paintCenter = true;
 914         boolean stretch = true;
 915         SynthPainter painter = null;
 916         String method = null;
 917         String id = null;
 918         int direction = -1;
 919         boolean center = false;
 920 
 921         boolean stretchSpecified = false;
 922         boolean paintCenterSpecified = false;
 923 
 924         for(int i = attributes.getLength() - 1; i >= 0; i--) {
 925             String key = attributes.getName(i);
 926             String value = attributes.getValue(i);
 927 
 928             if (key.equals(ATTRIBUTE_ID)) {
 929                 id = value;
 930             }
 931             else if (key.equals(ATTRIBUTE_METHOD)) {
 932                 method = value.toLowerCase(Locale.ENGLISH);
 933             }
 934             else if (key.equals(ATTRIBUTE_IDREF)) {
 935                 painter = (SynthPainter)lookup(value, SynthPainter.class);
 936             }
 937             else if (key.equals(ATTRIBUTE_PATH)) {
 938                 path = value;
 939             }
 940             else if (key.equals(ATTRIBUTE_SOURCE_INSETS)) {
 941                 sourceInsets = parseInsets(value, type +
 942                    ": sourceInsets must be top left bottom right");
 943             }
 944             else if (key.equals(ATTRIBUTE_DEST_INSETS)) {
 945                 destInsets = parseInsets(value, type +
 946                   ": destinationInsets must be top left bottom right");
 947             }
 948             else if (key.equals(ATTRIBUTE_PAINT_CENTER)) {
 949                 paintCenter = value.toLowerCase().equals("true");
 950                 paintCenterSpecified = true;
 951             }
 952             else if (key.equals(ATTRIBUTE_STRETCH)) {
 953                 stretch = value.toLowerCase().equals("true");
 954                 stretchSpecified = true;
 955             }
 956             else if (key.equals(ATTRIBUTE_DIRECTION)) {
 957                 value = value.toUpperCase().intern();
 958                 if (value == "EAST") {
 959                     direction = SwingConstants.EAST;
 960                 }
 961                 else if (value == "NORTH") {
 962                     direction = SwingConstants.NORTH;
 963                 }
 964                 else if (value == "SOUTH") {
 965                     direction = SwingConstants.SOUTH;
 966                 }
 967                 else if (value == "WEST") {
 968                     direction = SwingConstants.WEST;
 969                 }
 970                 else if (value == "TOP") {
 971                     direction = SwingConstants.TOP;
 972                 }
 973                 else if (value == "LEFT") {
 974                     direction = SwingConstants.LEFT;
 975                 }
 976                 else if (value == "BOTTOM") {
 977                     direction = SwingConstants.BOTTOM;
 978                 }
 979                 else if (value == "RIGHT") {
 980                     direction = SwingConstants.RIGHT;
 981                 }
 982                 else if (value == "HORIZONTAL") {
 983                     direction = SwingConstants.HORIZONTAL;
 984                 }
 985                 else if (value == "VERTICAL") {
 986                     direction = SwingConstants.VERTICAL;
 987                 }
 988                 else if (value == "HORIZONTAL_SPLIT") {
 989                     direction = JSplitPane.HORIZONTAL_SPLIT;
 990                 }
 991                 else if (value == "VERTICAL_SPLIT") {
 992                     direction = JSplitPane.VERTICAL_SPLIT;
 993                 }
 994                 else {
 995                     throw new SAXException(type + ": unknown direction");
 996                 }
 997             }
 998             else if (key.equals(ATTRIBUTE_CENTER)) {
 999                 center = value.toLowerCase().equals("true");
1000             }
1001         }
1002         if (painter == null) {
1003             if (type == ELEMENT_PAINTER) {
1004                 throw new SAXException(type +
1005                              ": you must specify an idref");
1006             }
1007             if (sourceInsets == null && !center) {
1008                 throw new SAXException(
1009                              "property: you must specify sourceInsets");
1010             }
1011             if (path == null) {
1012                 throw new SAXException("property: you must specify a path");
1013             }
1014             if (center && (sourceInsets != null || destInsets != null ||
1015                            paintCenterSpecified || stretchSpecified)) {
1016                 throw new SAXException("The attributes: sourceInsets, " +
1017                                        "destinationInsets, paintCenter and stretch " +
1018                                        " are not legal when center is true");
1019             }
1020             painter = new ImagePainter(!stretch, paintCenter,
1021                      sourceInsets, destInsets, getResource(path), center);
1022         }
1023         register(id, painter);
1024         if (_stateInfo != null) {
1025             addPainterOrMerge(_statePainters, method, painter, direction);
1026         }
1027         else if (_style != null) {
1028             addPainterOrMerge(_stylePainters, method, painter, direction);
1029         }
1030     }
1031 
1032     private void addPainterOrMerge(java.util.List painters, String method,
1033                                    SynthPainter painter, int direction) {
1034         ParsedSynthStyle.PainterInfo painterInfo;
1035         painterInfo = new ParsedSynthStyle.PainterInfo(method,
1036                                                        painter,
1037                                                        direction);
1038 
1039         for (Object infoObject: painters) {
1040             ParsedSynthStyle.PainterInfo info;
1041             info = (ParsedSynthStyle.PainterInfo) infoObject;
1042 
1043             if (painterInfo.equalsPainter(info)) {
1044                 info.addPainter(painter);
1045                 return;
1046             }
1047         }
1048 
1049         painters.add(painterInfo);
1050     }
1051 
1052     private void startImageIcon(AttributeList attributes) throws SAXException {
1053         String path = null;
1054         String id = null;
1055 
1056         for(int i = attributes.getLength() - 1; i >= 0; i--) {
1057             String key = attributes.getName(i);
1058 
1059             if (key.equals(ATTRIBUTE_ID)) {
1060                 id = attributes.getValue(i);
1061             }
1062             else if (key.equals(ATTRIBUTE_PATH)) {
1063                 path = attributes.getValue(i);
1064             }
1065         }
1066         if (path == null) {
1067             throw new SAXException("imageIcon: you must specify a path");
1068         }
1069         register(id, new LazyImageIcon(getResource(path)));
1070        }
1071 
1072     private void startOpaque(AttributeList attributes) throws
1073                       SAXException {
1074         if (_style != null) {
1075             _style.setOpaque(true);
1076             for(int i = attributes.getLength() - 1; i >= 0; i--) {
1077                 String key = attributes.getName(i);
1078 
1079                 if (key.equals(ATTRIBUTE_VALUE)) {
1080                     _style.setOpaque("true".equals(attributes.getValue(i).
1081                                                    toLowerCase()));
1082                 }
1083             }
1084         }
1085     }
1086 
1087     private void startInputMap(AttributeList attributes) throws SAXException {
1088         _inputMapBindings.clear();
1089         _inputMapID = null;
1090         if (_style != null) {
1091             for(int i = attributes.getLength() - 1; i >= 0; i--) {
1092                 String key = attributes.getName(i);
1093 
1094                 if (key.equals(ATTRIBUTE_ID)) {
1095                     _inputMapID = attributes.getValue(i);
1096                 }
1097             }
1098         }
1099     }
1100 
1101     private void endInputMap() throws SAXException {
1102         if (_inputMapID != null) {
1103             register(_inputMapID, new UIDefaults.LazyInputMap(
1104                      _inputMapBindings.toArray(new Object[_inputMapBindings.
1105                      size()])));
1106         }
1107         _inputMapBindings.clear();
1108         _inputMapID = null;
1109     }
1110 
1111     private void startBindKey(AttributeList attributes) throws SAXException {
1112         if (_inputMapID == null) {
1113             // Not in an inputmap, bail.
1114             return;
1115         }
1116         if (_style != null) {
1117             String key = null;
1118             String value = null;
1119             for(int i = attributes.getLength() - 1; i >= 0; i--) {
1120                 String aKey = attributes.getName(i);
1121 
1122                 if (aKey.equals(ATTRIBUTE_KEY)) {
1123                     key = attributes.getValue(i);
1124                 }
1125                 else if (aKey.equals(ATTRIBUTE_ACTION)) {
1126                     value = attributes.getValue(i);
1127                 }
1128             }
1129             if (key == null || value == null) {
1130                 throw new SAXException(
1131                     "bindKey: you must supply a key and action");
1132             }
1133             _inputMapBindings.add(key);
1134             _inputMapBindings.add(value);
1135         }
1136     }
1137 
1138     //
1139     // SAX methods, these forward to the ObjectHandler if we don't know
1140     // the element name.
1141     //
1142 
1143     public InputSource resolveEntity(String publicId, String systemId)
1144                               throws SAXException {
1145         if (isForwarding()) {
1146             return getHandler().resolveEntity(publicId, systemId);
1147         }
1148         return null;
1149     }
1150 
1151     public void notationDecl(String name, String publicId, String systemId) {
1152         if (isForwarding()) {
1153             getHandler().notationDecl(name, publicId, systemId);
1154         }
1155     }
1156 
1157     public void unparsedEntityDecl(String name, String publicId,
1158                                    String systemId, String notationName) {
1159         if (isForwarding()) {
1160             getHandler().unparsedEntityDecl(name, publicId, systemId,
1161                                             notationName);
1162         }
1163     }
1164 
1165     public void setDocumentLocator(Locator locator) {
1166         if (isForwarding()) {
1167             getHandler().setDocumentLocator(locator);
1168         }
1169     }
1170 
1171     public void startDocument() throws SAXException {
1172         if (isForwarding()) {
1173             getHandler().startDocument();
1174         }
1175     }
1176 
1177     public void endDocument() throws SAXException {
1178         if (isForwarding()) {
1179             getHandler().endDocument();
1180         }
1181     }
1182 
1183     public void startElement(String name, AttributeList attributes)
1184                      throws SAXException {
1185         name = name.intern();
1186         if (name == ELEMENT_STYLE) {
1187             startStyle(attributes);
1188         }
1189         else if (name == ELEMENT_STATE) {
1190             startState(attributes);
1191         }
1192         else if (name == ELEMENT_FONT) {
1193             startFont(attributes);
1194         }
1195         else if (name == ELEMENT_COLOR) {
1196             startColor(attributes);
1197         }
1198         else if (name == ELEMENT_PAINTER) {
1199             startPainter(attributes, name);
1200         }
1201         else if (name == ELEMENT_IMAGE_PAINTER) {
1202             startPainter(attributes, name);
1203         }
1204         else if (name == ELEMENT_PROPERTY) {
1205             startProperty(attributes, ELEMENT_PROPERTY);
1206         }
1207         else if (name == ELEMENT_DEFAULTS_PROPERTY) {
1208             startProperty(attributes, ELEMENT_DEFAULTS_PROPERTY);
1209         }
1210         else if (name == ELEMENT_SYNTH_GRAPHICS) {
1211             startGraphics(attributes);
1212         }
1213         else if (name == ELEMENT_INSETS) {
1214             startInsets(attributes);
1215         }
1216         else if (name == ELEMENT_BIND) {
1217             startBind(attributes);
1218         }
1219         else if (name == ELEMENT_BIND_KEY) {
1220             startBindKey(attributes);
1221         }
1222         else if (name == ELEMENT_IMAGE_ICON) {
1223             startImageIcon(attributes);
1224         }
1225         else if (name == ELEMENT_OPAQUE) {
1226             startOpaque(attributes);
1227         }
1228         else if (name == ELEMENT_INPUT_MAP) {
1229             startInputMap(attributes);
1230         }
1231         else if (name != ELEMENT_SYNTH) {
1232             if (_depth++ == 0) {
1233                 getHandler().reset();
1234             }
1235             getHandler().startElement(name, attributes);
1236         }
1237     }
1238 
1239     public void endElement(String name) throws SAXException {
1240         if (isForwarding()) {
1241             getHandler().endElement(name);
1242             _depth--;
1243             if (!isForwarding()) {
1244                 getHandler().reset();
1245             }
1246         }
1247         else {
1248             name = name.intern();
1249             if (name == ELEMENT_STYLE) {
1250                 endStyle();
1251             }
1252             else if (name == ELEMENT_STATE) {
1253                 endState();
1254             }
1255             else if (name == ELEMENT_INPUT_MAP) {
1256                 endInputMap();
1257             }
1258         }
1259     }
1260 
1261     public void characters(char ch[], int start, int length)
1262                            throws SAXException {
1263         if (isForwarding()) {
1264             getHandler().characters(ch, start, length);
1265         }
1266     }
1267 
1268     public void ignorableWhitespace (char ch[], int start, int length)
1269         throws SAXException {
1270         if (isForwarding()) {
1271             getHandler().ignorableWhitespace(ch, start, length);
1272         }
1273     }
1274 
1275     public void processingInstruction(String target, String data)
1276                                      throws SAXException {
1277         if (isForwarding()) {
1278             getHandler().processingInstruction(target, data);
1279         }
1280     }
1281 
1282     public void warning(SAXParseException e) throws SAXException {
1283         if (isForwarding()) {
1284             getHandler().warning(e);
1285         }
1286     }
1287 
1288     public void error(SAXParseException e) throws SAXException {
1289         if (isForwarding()) {
1290             getHandler().error(e);
1291         }
1292     }
1293 
1294 
1295     public void fatalError(SAXParseException e) throws SAXException {
1296         if (isForwarding()) {
1297             getHandler().fatalError(e);
1298         }
1299         throw e;
1300     }
1301 
1302 
1303     /**
1304      * ImageIcon that lazily loads the image until needed.
1305      */
1306     private static class LazyImageIcon extends ImageIcon implements UIResource {
1307         private URL location;
1308 
1309         public LazyImageIcon(URL location) {
1310             super();
1311             this.location = location;
1312         }
1313 
1314         public void paintIcon(Component c, Graphics g, int x, int y) {
1315             if (getImage() != null) {
1316                 super.paintIcon(c, g, x, y);
1317             }
1318         }
1319 
1320         public int getIconWidth() {
1321             if (getImage() != null) {
1322                 return super.getIconWidth();
1323             }
1324             return 0;
1325         }
1326 
1327         public int getIconHeight() {
1328             if (getImage() != null) {
1329                 return super.getIconHeight();
1330             }
1331             return 0;
1332         }
1333 
1334         public Image getImage() {
1335             if (location != null) {
1336                 setImage(Toolkit.getDefaultToolkit().getImage(location));
1337                 location = null;
1338             }
1339             return super.getImage();
1340         }
1341     }
1342 }