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