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