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