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 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 @SuppressWarnings("serial") // Superclass is not serializable across versions 1298 private static class LazyImageIcon extends ImageIcon implements UIResource { 1299 private URL location; 1300 1301 public LazyImageIcon(URL location) { 1302 super(); 1303 this.location = location; 1304 } 1305 1306 public void paintIcon(Component c, Graphics g, int x, int y) { 1307 if (getImage() != null) { 1308 super.paintIcon(c, g, x, y); 1309 } 1310 } 1311 1312 public int getIconWidth() { 1313 if (getImage() != null) { 1314 return super.getIconWidth(); 1315 } 1316 return 0; 1317 } 1318 1319 public int getIconHeight() { 1320 if (getImage() != null) { 1321 return super.getIconHeight(); 1322 } 1323 return 0; 1324 } 1325 1326 public Image getImage() { 1327 if (location != null) { 1328 setImage(Toolkit.getDefaultToolkit().getImage(location)); 1329 location = null; 1330 } 1331 return super.getImage(); 1332 } 1333 } 1334 }