1 /*
   2  * Copyright (c) 2010, 2017, 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 
  26 package javafx.fxml;
  27 
  28 import com.sun.javafx.util.Logging;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.io.InputStreamReader;
  32 import java.lang.reflect.Array;
  33 import java.lang.reflect.Constructor;
  34 import java.lang.reflect.Field;
  35 import java.lang.reflect.InvocationTargetException;
  36 import java.lang.reflect.Method;
  37 import java.lang.reflect.Modifier;
  38 import java.lang.reflect.ParameterizedType;
  39 import java.lang.reflect.Type;
  40 import java.net.URL;
  41 import java.nio.charset.Charset;
  42 import java.util.AbstractMap;
  43 import java.util.ArrayList;
  44 import java.util.Collections;
  45 import java.util.HashMap;
  46 import java.util.LinkedList;
  47 import java.util.List;
  48 import java.util.Map;
  49 import java.util.ResourceBundle;
  50 import java.util.Set;
  51 import java.util.regex.Pattern;
  52 
  53 import javafx.beans.DefaultProperty;
  54 import javafx.beans.InvalidationListener;
  55 import javafx.beans.property.Property;
  56 import javafx.beans.value.ChangeListener;
  57 import javafx.beans.value.ObservableValue;
  58 import javafx.collections.*;
  59 import javafx.event.Event;
  60 import javafx.event.EventHandler;
  61 import javafx.util.Builder;
  62 import javafx.util.BuilderFactory;
  63 import javafx.util.Callback;
  64 
  65 import javax.script.Bindings;
  66 import javax.script.ScriptContext;
  67 import javax.script.ScriptEngine;
  68 import javax.script.ScriptEngineManager;
  69 import javax.script.ScriptException;
  70 import javax.script.SimpleBindings;
  71 import javax.xml.stream.XMLInputFactory;
  72 import javax.xml.stream.XMLStreamConstants;
  73 import javax.xml.stream.XMLStreamException;
  74 import javax.xml.stream.XMLStreamReader;
  75 import javax.xml.stream.util.StreamReaderDelegate;
  76 
  77 import com.sun.javafx.beans.IDProperty;
  78 import com.sun.javafx.fxml.BeanAdapter;
  79 import com.sun.javafx.fxml.ParseTraceElement;
  80 import com.sun.javafx.fxml.PropertyNotFoundException;
  81 import com.sun.javafx.fxml.expression.Expression;
  82 import com.sun.javafx.fxml.expression.ExpressionValue;
  83 import com.sun.javafx.fxml.expression.KeyPath;
  84 import static com.sun.javafx.FXPermissions.MODIFY_FXML_CLASS_LOADER_PERMISSION;
  85 import com.sun.javafx.fxml.FXMLLoaderHelper;
  86 import java.net.MalformedURLException;
  87 import java.security.AccessController;
  88 import java.security.PrivilegedAction;
  89 import java.util.EnumMap;
  90 import java.util.Locale;
  91 import java.util.StringTokenizer;
  92 import sun.reflect.misc.ConstructorUtil;
  93 import sun.reflect.misc.MethodUtil;
  94 import sun.reflect.misc.ReflectUtil;
  95 
  96 /**
  97  * Loads an object hierarchy from an XML document.
  98  * @since JavaFX 2.0
  99  */
 100 public class FXMLLoader {
 101 
 102     // Indicates permission to get the ClassLoader
 103     private static final RuntimePermission GET_CLASSLOADER_PERMISSION =
 104         new RuntimePermission("getClassLoader");
 105 
 106     // Instance of StackWalker used to get caller class (must be private)
 107     private static final StackWalker walker =
 108         AccessController.doPrivileged((PrivilegedAction<StackWalker>) () ->
 109             StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE));
 110 
 111     // Abstract base class for elements
 112     private abstract class Element {
 113         public final Element parent;
 114 
 115         public Object value = null;
 116         private BeanAdapter valueAdapter = null;
 117 
 118         public final LinkedList<Attribute> eventHandlerAttributes = new LinkedList<Attribute>();
 119         public final LinkedList<Attribute> instancePropertyAttributes = new LinkedList<Attribute>();
 120         public final LinkedList<Attribute> staticPropertyAttributes = new LinkedList<Attribute>();
 121         public final LinkedList<PropertyElement> staticPropertyElements = new LinkedList<PropertyElement>();
 122 
 123         public Element() {
 124             parent = current;
 125         }
 126 
 127         public boolean isCollection() {
 128             // Return true if value is a list, or if the value's type defines
 129             // a default property that is a list
 130             boolean collection;
 131             if (value instanceof List<?>) {
 132                 collection = true;
 133             } else {
 134                 Class<?> type = value.getClass();
 135                 DefaultProperty defaultProperty = type.getAnnotation(DefaultProperty.class);
 136 
 137                 if (defaultProperty != null) {
 138                     collection = getProperties().get(defaultProperty.value()) instanceof List<?>;
 139                 } else {
 140                     collection = false;
 141                 }
 142             }
 143 
 144             return collection;
 145         }
 146 
 147         @SuppressWarnings("unchecked")
 148         public void add(Object element) throws LoadException {
 149             // If value is a list, add element to it; otherwise, get the value
 150             // of the default property, which is assumed to be a list and add
 151             // to that (coerce to the appropriate type)
 152             List<Object> list;
 153             if (value instanceof List<?>) {
 154                 list = (List<Object>)value;
 155             } else {
 156                 Class<?> type = value.getClass();
 157                 DefaultProperty defaultProperty = type.getAnnotation(DefaultProperty.class);
 158                 String defaultPropertyName = defaultProperty.value();
 159 
 160                 // Get the list value
 161                 list = (List<Object>)getProperties().get(defaultPropertyName);
 162 
 163                 // Coerce the element to the list item type
 164                 if (!Map.class.isAssignableFrom(type)) {
 165                     Type listType = getValueAdapter().getGenericType(defaultPropertyName);
 166                     element = BeanAdapter.coerce(element, BeanAdapter.getListItemType(listType));
 167                 }
 168             }
 169 
 170             list.add(element);
 171         }
 172 
 173         public void set(Object value) throws LoadException {
 174             if (this.value == null) {
 175                 throw constructLoadException("Cannot set value on this element.");
 176             }
 177 
 178             // Apply value to this element's properties
 179             Class<?> type = this.value.getClass();
 180             DefaultProperty defaultProperty = type.getAnnotation(DefaultProperty.class);
 181             if (defaultProperty == null) {
 182                 throw constructLoadException("Element does not define a default property.");
 183             }
 184 
 185             getProperties().put(defaultProperty.value(), value);
 186         }
 187 
 188         public void updateValue(Object value) {
 189             this.value = value;
 190             valueAdapter = null;
 191         }
 192 
 193         public boolean isTyped() {
 194             return !(value instanceof Map<?, ?>);
 195         }
 196 
 197         public BeanAdapter getValueAdapter() {
 198             if (valueAdapter == null) {
 199                 valueAdapter = new BeanAdapter(value);
 200             }
 201 
 202             return valueAdapter;
 203         }
 204 
 205         @SuppressWarnings("unchecked")
 206         public Map<String, Object> getProperties() {
 207             return (isTyped()) ? getValueAdapter() : (Map<String, Object>)value;
 208         }
 209 
 210         public void processStartElement() throws IOException {
 211             for (int i = 0, n = xmlStreamReader.getAttributeCount(); i < n; i++) {
 212                 String prefix = xmlStreamReader.getAttributePrefix(i);
 213                 String localName = xmlStreamReader.getAttributeLocalName(i);
 214                 String value = xmlStreamReader.getAttributeValue(i);
 215 
 216                 if (loadListener != null
 217                     && prefix != null
 218                     && prefix.equals(FX_NAMESPACE_PREFIX)) {
 219                     loadListener.readInternalAttribute(prefix + ":" + localName, value);
 220                 }
 221 
 222                 processAttribute(prefix, localName, value);
 223             }
 224         }
 225 
 226         public void processEndElement() throws IOException {
 227             // No-op
 228         }
 229 
 230         public void processCharacters() throws IOException {
 231             throw constructLoadException("Unexpected characters in input stream.");
 232         }
 233 
 234         public void processInstancePropertyAttributes() throws IOException {
 235             if (instancePropertyAttributes.size() > 0) {
 236                 for (Attribute attribute : instancePropertyAttributes) {
 237                     processPropertyAttribute(attribute);
 238                 }
 239             }
 240         }
 241 
 242         public void processAttribute(String prefix, String localName, String value)
 243             throws IOException{
 244             if (prefix == null) {
 245                 // Add the attribute to the appropriate list
 246                 if (localName.startsWith(EVENT_HANDLER_PREFIX)) {
 247                     if (loadListener != null) {
 248                         loadListener.readEventHandlerAttribute(localName, value);
 249                     }
 250 
 251                     eventHandlerAttributes.add(new Attribute(localName, null, value));
 252                 } else {
 253                     int i = localName.lastIndexOf('.');
 254 
 255                     if (i == -1) {
 256                         // The attribute represents an instance property
 257                         if (loadListener != null) {
 258                             loadListener.readPropertyAttribute(localName, null, value);
 259                         }
 260 
 261                         instancePropertyAttributes.add(new Attribute(localName, null, value));
 262                     } else {
 263                         // The attribute represents a static property
 264                         String name = localName.substring(i + 1);
 265                         Class<?> sourceType = getType(localName.substring(0, i));
 266 
 267                         if (sourceType != null) {
 268                             if (loadListener != null) {
 269                                 loadListener.readPropertyAttribute(name, sourceType, value);
 270                             }
 271 
 272                             staticPropertyAttributes.add(new Attribute(name, sourceType, value));
 273                         } else if (staticLoad) {
 274                             if (loadListener != null) {
 275                                 loadListener.readUnknownStaticPropertyAttribute(localName, value);
 276                             }
 277                         } else {
 278                             throw constructLoadException(localName + " is not a valid attribute.");
 279                         }
 280                     }
 281 
 282                 }
 283             } else {
 284                 throw constructLoadException(prefix + ":" + localName
 285                     + " is not a valid attribute.");
 286             }
 287         }
 288 
 289         @SuppressWarnings("unchecked")
 290         public void processPropertyAttribute(Attribute attribute) throws IOException {
 291             String value = attribute.value;
 292             if (isBindingExpression(value)) {
 293                 // Resolve the expression
 294                 Expression expression;
 295 
 296                 if (attribute.sourceType != null) {
 297                     throw constructLoadException("Cannot bind to static property.");
 298                 }
 299 
 300                 if (!isTyped()) {
 301                     throw constructLoadException("Cannot bind to untyped object.");
 302                 }
 303 
 304                 // TODO We may want to identify binding properties in processAttribute()
 305                 // and apply them after build() has been called
 306                 if (this.value instanceof Builder) {
 307                     throw constructLoadException("Cannot bind to builder property.");
 308                 }
 309 
 310                 if (!isStaticLoad()) {
 311                     value = value.substring(BINDING_EXPRESSION_PREFIX.length(),
 312                             value.length() - 1);
 313                     expression = Expression.valueOf(value);
 314 
 315                     // Create the binding
 316                     BeanAdapter targetAdapter = new BeanAdapter(this.value);
 317                     ObservableValue<Object> propertyModel = targetAdapter.getPropertyModel(attribute.name);
 318                     Class<?> type = targetAdapter.getType(attribute.name);
 319 
 320                     if (propertyModel instanceof Property<?>) {
 321                         ((Property<Object>) propertyModel).bind(new ExpressionValue(namespace, expression, type));
 322                     }
 323                 }
 324             } else if (isBidirectionalBindingExpression(value)) {
 325                 throw constructLoadException(new UnsupportedOperationException("This feature is not currently enabled."));
 326             } else {
 327                 processValue(attribute.sourceType, attribute.name, value);
 328             }
 329         }
 330 
 331         private boolean isBindingExpression(String aValue) {
 332             return aValue.startsWith(BINDING_EXPRESSION_PREFIX)
 333                    && aValue.endsWith(BINDING_EXPRESSION_SUFFIX);
 334         }
 335 
 336         private boolean isBidirectionalBindingExpression(String aValue) {
 337             return aValue.startsWith(BI_DIRECTIONAL_BINDING_PREFIX);
 338         }
 339 
 340         private boolean processValue(Class sourceType, String propertyName, String aValue)
 341             throws LoadException {
 342 
 343             boolean processed = false;
 344                 //process list or array first
 345                 if (sourceType == null && isTyped()) {
 346                     BeanAdapter valueAdapter = getValueAdapter();
 347                     Class<?> type = valueAdapter.getType(propertyName);
 348 
 349                     if (type == null) {
 350                         throw new PropertyNotFoundException("Property \"" + propertyName
 351                             + "\" does not exist" + " or is read-only.");
 352                     }
 353 
 354                     if (List.class.isAssignableFrom(type)
 355                         && valueAdapter.isReadOnly(propertyName)) {
 356                         populateListFromString(valueAdapter, propertyName, aValue);
 357                         processed = true;
 358                     } else if (type.isArray()) {
 359                         applyProperty(propertyName, sourceType,
 360                                 populateArrayFromString(type, aValue));
 361                         processed = true;
 362                     }
 363                 }
 364                 if (!processed) {
 365                     applyProperty(propertyName, sourceType, resolvePrefixedValue(aValue));
 366                     processed = true;
 367                 }
 368                 return processed;
 369         }
 370 
 371         /**
 372          * Resolves value prefixed with RELATIVE_PATH_PREFIX and RESOURCE_KEY_PREFIX.
 373          */
 374         private Object resolvePrefixedValue(String aValue) throws LoadException {
 375             if (aValue.startsWith(ESCAPE_PREFIX)) {
 376                 aValue = aValue.substring(ESCAPE_PREFIX.length());
 377 
 378                 if (aValue.length() == 0
 379                     || !(aValue.startsWith(ESCAPE_PREFIX)
 380                         || aValue.startsWith(RELATIVE_PATH_PREFIX)
 381                         || aValue.startsWith(RESOURCE_KEY_PREFIX)
 382                         || aValue.startsWith(EXPRESSION_PREFIX)
 383                         || aValue.startsWith(BI_DIRECTIONAL_BINDING_PREFIX))) {
 384                     throw constructLoadException("Invalid escape sequence.");
 385                 }
 386                 return aValue;
 387             } else if (aValue.startsWith(RELATIVE_PATH_PREFIX)) {
 388                 aValue = aValue.substring(RELATIVE_PATH_PREFIX.length());
 389                 if (aValue.length() == 0) {
 390                     throw constructLoadException("Missing relative path.");
 391                 }
 392                 if (aValue.startsWith(RELATIVE_PATH_PREFIX)) {
 393                     // The prefix was escaped
 394                     warnDeprecatedEscapeSequence(RELATIVE_PATH_PREFIX);
 395                     return aValue;
 396                 } else {
 397                         if (aValue.charAt(0) == '/') {
 398                             // FIXME: JIGSAW -- use Class.getResourceAsStream if resource is in a module
 399                             final URL res = getClassLoader().getResource(aValue.substring(1));
 400                             if (res == null) {
 401                                 throw constructLoadException("Invalid resource: " + aValue + " not found on the classpath");
 402                             }
 403                             return res.toString();
 404                         } else {
 405                             try {
 406                                 return new URL(FXMLLoader.this.location, aValue).toString();
 407                             } catch (MalformedURLException e) {
 408                                 System.err.println(FXMLLoader.this.location + "/" + aValue);
 409                             }
 410                         }
 411                 }
 412             } else if (aValue.startsWith(RESOURCE_KEY_PREFIX)) {
 413                 aValue = aValue.substring(RESOURCE_KEY_PREFIX.length());
 414                 if (aValue.length() == 0) {
 415                     throw constructLoadException("Missing resource key.");
 416                 }
 417                 if (aValue.startsWith(RESOURCE_KEY_PREFIX)) {
 418                     // The prefix was escaped
 419                     warnDeprecatedEscapeSequence(RESOURCE_KEY_PREFIX);
 420                     return aValue;
 421                 } else {
 422                     // Resolve the resource value
 423                     if (resources == null) {
 424                         throw constructLoadException("No resources specified.");
 425                     }
 426                     if (!resources.containsKey(aValue)) {
 427                         throw constructLoadException("Resource \"" + aValue + "\" not found.");
 428                     }
 429 
 430                     return resources.getString(aValue);
 431                 }
 432             } else if (aValue.startsWith(EXPRESSION_PREFIX)) {
 433                 aValue = aValue.substring(EXPRESSION_PREFIX.length());
 434                 if (aValue.length() == 0) {
 435                     throw constructLoadException("Missing expression.");
 436                 }
 437                 if (aValue.startsWith(EXPRESSION_PREFIX)) {
 438                     // The prefix was escaped
 439                     warnDeprecatedEscapeSequence(EXPRESSION_PREFIX);
 440                     return aValue;
 441                 } else if (aValue.equals(NULL_KEYWORD)) {
 442                     // The attribute value is null
 443                     return null;
 444                 }
 445                 return Expression.get(namespace, KeyPath.parse(aValue));
 446             }
 447             return aValue;
 448         }
 449 
 450         /**
 451          * Creates an array of given type and populates it with values from
 452          * a string where tokens are separated by ARRAY_COMPONENT_DELIMITER.
 453          * If token is prefixed with RELATIVE_PATH_PREFIX a value added to
 454          * the array becomes relative to document location.
 455          */
 456         private Object populateArrayFromString(
 457                 Class<?>type,
 458                 String stringValue) throws LoadException {
 459 
 460             Object propertyValue = null;
 461             // Split the string and set the values as an array
 462             Class<?> componentType = type.getComponentType();
 463 
 464             if (stringValue.length() > 0) {
 465                 String[] values = stringValue.split(ARRAY_COMPONENT_DELIMITER);
 466                 propertyValue = Array.newInstance(componentType, values.length);
 467                 for (int i = 0; i < values.length; i++) {
 468                     Array.set(propertyValue, i,
 469                             BeanAdapter.coerce(resolvePrefixedValue(values[i].trim()),
 470                             type.getComponentType()));
 471                 }
 472             } else {
 473                 propertyValue = Array.newInstance(componentType, 0);
 474             }
 475             return propertyValue;
 476         }
 477 
 478         /**
 479          * Populates list with values from a string where tokens are separated
 480          * by ARRAY_COMPONENT_DELIMITER. If token is prefixed with RELATIVE_PATH_PREFIX
 481          * a value added to the list becomes relative to document location.
 482          */
 483         private void populateListFromString(
 484                 BeanAdapter valueAdapter,
 485                 String listPropertyName,
 486                 String stringValue) throws LoadException {
 487             // Split the string and add the values to the list
 488             List<Object> list = (List<Object>)valueAdapter.get(listPropertyName);
 489             Type listType = valueAdapter.getGenericType(listPropertyName);
 490             Type itemType = (Class<?>)BeanAdapter.getGenericListItemType(listType);
 491 
 492             if (itemType instanceof ParameterizedType) {
 493                 itemType = ((ParameterizedType)itemType).getRawType();
 494             }
 495 
 496             if (stringValue.length() > 0) {
 497                 String[] values = stringValue.split(ARRAY_COMPONENT_DELIMITER);
 498 
 499                 for (String aValue: values) {
 500                     aValue = aValue.trim();
 501                     list.add(
 502                             BeanAdapter.coerce(resolvePrefixedValue(aValue),
 503                                                (Class<?>)itemType));
 504                 }
 505             }
 506         }
 507 
 508         public void warnDeprecatedEscapeSequence(String prefix) {
 509             System.err.println(prefix + prefix + " is a deprecated escape sequence. "
 510                 + "Please use \\" + prefix + " instead.");
 511         }
 512 
 513         public void applyProperty(String name, Class<?> sourceType, Object value) {
 514             if (sourceType == null) {
 515                 getProperties().put(name, value);
 516             } else {
 517                 BeanAdapter.put(this.value, sourceType, name, value);
 518             }
 519         }
 520 
 521         private Object getExpressionObject(String handlerValue) throws LoadException{
 522             if (handlerValue.startsWith(EXPRESSION_PREFIX)) {
 523                 handlerValue = handlerValue.substring(EXPRESSION_PREFIX.length());
 524 
 525                 if (handlerValue.length() == 0) {
 526                     throw constructLoadException("Missing expression reference.");
 527                 }
 528 
 529                 Object expression = Expression.get(namespace, KeyPath.parse(handlerValue));
 530                 if (expression == null) {
 531                     throw constructLoadException("Unable to resolve expression : $" + handlerValue);
 532                 }
 533                 return expression;
 534             }
 535             return null;
 536         }
 537 
 538         private <T> T getExpressionObjectOfType(String handlerValue, Class<T> type) throws LoadException{
 539             Object expression = getExpressionObject(handlerValue);
 540             if (expression != null) {
 541                 if (type.isInstance(expression)) {
 542                     return (T) expression;
 543                 }
 544                 throw constructLoadException("Error resolving \"" + handlerValue +"\" expression."
 545                         + "Does not point to a " + type.getName());
 546             }
 547             return null;
 548         }
 549 
 550         private MethodHandler getControllerMethodHandle(String handlerName, SupportedType... types) throws LoadException {
 551             if (handlerName.startsWith(CONTROLLER_METHOD_PREFIX)) {
 552                 handlerName = handlerName.substring(CONTROLLER_METHOD_PREFIX.length());
 553 
 554                 if (!handlerName.startsWith(CONTROLLER_METHOD_PREFIX)) {
 555                     if (handlerName.length() == 0) {
 556                         throw constructLoadException("Missing controller method.");
 557                     }
 558 
 559                     if (controller == null) {
 560                         throw constructLoadException("No controller specified.");
 561                     }
 562 
 563                     for (SupportedType t : types) {
 564                         Method method = controllerAccessor
 565                                             .getControllerMethods()
 566                                             .get(t)
 567                                             .get(handlerName);
 568                         if (method != null) {
 569                             return new MethodHandler(controller, method, t);
 570                         }
 571                     }
 572                     Method method = controllerAccessor
 573                                         .getControllerMethods()
 574                                         .get(SupportedType.PARAMETERLESS)
 575                                         .get(handlerName);
 576                     if (method != null) {
 577                         return new MethodHandler(controller, method, SupportedType.PARAMETERLESS);
 578                     }
 579 
 580                     return null;
 581 
 582                 }
 583 
 584             }
 585             return null;
 586         }
 587 
 588         public void processEventHandlerAttributes() throws LoadException {
 589             if (eventHandlerAttributes.size() > 0 && !staticLoad) {
 590                 for (Attribute attribute : eventHandlerAttributes) {
 591                     String handlerName = attribute.value;
 592                     if (value instanceof ObservableList && attribute.name.equals(COLLECTION_HANDLER_NAME)) {
 593                         processObservableListHandler(handlerName);
 594                     } else if (value instanceof ObservableMap && attribute.name.equals(COLLECTION_HANDLER_NAME)) {
 595                         processObservableMapHandler(handlerName);
 596                     } else if (value instanceof ObservableSet && attribute.name.equals(COLLECTION_HANDLER_NAME)) {
 597                         processObservableSetHandler(handlerName);
 598                     } else if (attribute.name.endsWith(CHANGE_EVENT_HANDLER_SUFFIX)) {
 599                         processPropertyHandler(attribute.name, handlerName);
 600                     } else {
 601                         EventHandler<? extends Event> eventHandler = null;
 602                         MethodHandler handler = getControllerMethodHandle(handlerName, SupportedType.EVENT);
 603                         if (handler != null) {
 604                             eventHandler = new ControllerMethodEventHandler<>(handler);
 605                         }
 606 
 607                         if (eventHandler == null) {
 608                             eventHandler = getExpressionObjectOfType(handlerName, EventHandler.class);
 609                         }
 610 
 611                         if (eventHandler == null) {
 612                             if (handlerName.length() == 0 || scriptEngine == null) {
 613                                 throw constructLoadException("Error resolving " + attribute.name + "='" + attribute.value
 614                                         + "', either the event handler is not in the Namespace or there is an error in the script.");
 615                             }
 616 
 617                             eventHandler = new ScriptEventHandler(handlerName, scriptEngine);
 618                         }
 619 
 620                         // Add the handler
 621                         getValueAdapter().put(attribute.name, eventHandler);
 622                     }
 623                 }
 624             }
 625         }
 626 
 627         private void processObservableListHandler(String handlerValue) throws LoadException {
 628             ObservableList list = (ObservableList)value;
 629             if (handlerValue.startsWith(CONTROLLER_METHOD_PREFIX)) {
 630                 MethodHandler handler = getControllerMethodHandle(handlerValue, SupportedType.LIST_CHANGE_LISTENER);
 631                 if (handler != null) {
 632                     list.addListener(new ObservableListChangeAdapter(handler));
 633                 } else {
 634                     throw constructLoadException("Controller method \"" + handlerValue + "\" not found.");
 635                 }
 636             } else if (handlerValue.startsWith(EXPRESSION_PREFIX)) {
 637                 Object listener = getExpressionObject(handlerValue);
 638                    if (listener instanceof ListChangeListener) {
 639                     list.addListener((ListChangeListener) listener);
 640                 } else if (listener instanceof InvalidationListener) {
 641                     list.addListener((InvalidationListener) listener);
 642                 } else {
 643                     throw constructLoadException("Error resolving \"" + handlerValue + "\" expression."
 644                             + "Must be either ListChangeListener or InvalidationListener");
 645                 }
 646             }
 647         }
 648 
 649         private void processObservableMapHandler(String handlerValue) throws LoadException {
 650             ObservableMap map = (ObservableMap)value;
 651             if (handlerValue.startsWith(CONTROLLER_METHOD_PREFIX)) {
 652                 MethodHandler handler = getControllerMethodHandle(handlerValue, SupportedType.MAP_CHANGE_LISTENER);
 653                 if (handler != null) {
 654                     map.addListener(new ObservableMapChangeAdapter(handler));
 655                 } else {
 656                     throw constructLoadException("Controller method \"" + handlerValue + "\" not found.");
 657                 }
 658             } else if (handlerValue.startsWith(EXPRESSION_PREFIX)) {
 659                 Object listener = getExpressionObject(handlerValue);
 660                 if (listener instanceof MapChangeListener) {
 661                     map.addListener((MapChangeListener) listener);
 662                 } else if (listener instanceof InvalidationListener) {
 663                     map.addListener((InvalidationListener) listener);
 664                 } else {
 665                     throw constructLoadException("Error resolving \"" + handlerValue + "\" expression."
 666                             + "Must be either MapChangeListener or InvalidationListener");
 667                 }
 668             }
 669         }
 670 
 671         private void processObservableSetHandler(String handlerValue) throws LoadException {
 672             ObservableSet set = (ObservableSet)value;
 673             if (handlerValue.startsWith(CONTROLLER_METHOD_PREFIX)) {
 674                 MethodHandler handler = getControllerMethodHandle(handlerValue, SupportedType.SET_CHANGE_LISTENER);
 675                 if (handler != null) {
 676                     set.addListener(new ObservableSetChangeAdapter(handler));
 677                 } else {
 678                     throw constructLoadException("Controller method \"" + handlerValue + "\" not found.");
 679                 }
 680             } else if (handlerValue.startsWith(EXPRESSION_PREFIX)) {
 681                 Object listener = getExpressionObject(handlerValue);
 682                 if (listener instanceof SetChangeListener) {
 683                     set.addListener((SetChangeListener) listener);
 684                 } else if (listener instanceof InvalidationListener) {
 685                     set.addListener((InvalidationListener) listener);
 686                 } else {
 687                     throw constructLoadException("Error resolving \"" + handlerValue + "\" expression."
 688                             + "Must be either SetChangeListener or InvalidationListener");
 689                 }
 690             }
 691         }
 692 
 693         private void processPropertyHandler(String attributeName, String handlerValue) throws LoadException {
 694             int i = EVENT_HANDLER_PREFIX.length();
 695             int j = attributeName.length() - CHANGE_EVENT_HANDLER_SUFFIX.length();
 696 
 697             if (i != j) {
 698                 String key = Character.toLowerCase(attributeName.charAt(i))
 699                         + attributeName.substring(i + 1, j);
 700 
 701                 ObservableValue<Object> propertyModel = getValueAdapter().getPropertyModel(key);
 702                 if (propertyModel == null) {
 703                     throw constructLoadException(value.getClass().getName() + " does not define"
 704                             + " a property model for \"" + key + "\".");
 705                 }
 706 
 707                 if (handlerValue.startsWith(CONTROLLER_METHOD_PREFIX)) {
 708                     final MethodHandler handler = getControllerMethodHandle(handlerValue, SupportedType.PROPERTY_CHANGE_LISTENER, SupportedType.EVENT);
 709                     if (handler != null) {
 710                         if (handler.type == SupportedType.EVENT) {
 711                             // Note: this part is solely for purpose of 2.2 backward compatibility where an Event object
 712                             // has been used instead of usual property change parameters
 713                             propertyModel.addListener(new ChangeListener<Object>() {
 714                                 @Override
 715                                 public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) {
 716                                     handler.invoke(new Event(value, null, Event.ANY));
 717                                 }
 718                             });
 719                         } else {
 720                             propertyModel.addListener(new PropertyChangeAdapter(handler));
 721                         }
 722                     } else {
 723                     throw constructLoadException("Controller method \"" + handlerValue + "\" not found.");
 724                     }
 725                 } else if (handlerValue.startsWith(EXPRESSION_PREFIX)) {
 726                     Object listener = getExpressionObject(handlerValue);
 727                     if (listener instanceof ChangeListener) {
 728                         propertyModel.addListener((ChangeListener) listener);
 729                     } else if (listener instanceof InvalidationListener) {
 730                         propertyModel.addListener((InvalidationListener) listener);
 731                     } else {
 732                         throw constructLoadException("Error resolving \"" + handlerValue + "\" expression."
 733                                 + "Must be either ChangeListener or InvalidationListener");
 734                     }
 735                 }
 736 
 737             }
 738         }
 739     }
 740 
 741     // Element representing a value
 742     private abstract class ValueElement extends Element {
 743         public String fx_id = null;
 744 
 745         @Override
 746         public void processStartElement() throws IOException {
 747             super.processStartElement();
 748 
 749             updateValue(constructValue());
 750 
 751             if (value instanceof Builder<?>) {
 752                 processInstancePropertyAttributes();
 753             } else {
 754                 processValue();
 755             }
 756         }
 757 
 758         @Override
 759         @SuppressWarnings("unchecked")
 760         public void processEndElement() throws IOException {
 761             super.processEndElement();
 762 
 763             // Build the value, if necessary
 764             if (value instanceof Builder<?>) {
 765                 Builder<Object> builder = (Builder<Object>)value;
 766                 updateValue(builder.build());
 767 
 768                 processValue();
 769             } else {
 770                 processInstancePropertyAttributes();
 771             }
 772 
 773             processEventHandlerAttributes();
 774 
 775             // Process static property attributes
 776             if (staticPropertyAttributes.size() > 0) {
 777                 for (Attribute attribute : staticPropertyAttributes) {
 778                     processPropertyAttribute(attribute);
 779                 }
 780             }
 781 
 782             // Process static property elements
 783             if (staticPropertyElements.size() > 0) {
 784                 for (PropertyElement element : staticPropertyElements) {
 785                     BeanAdapter.put(value, element.sourceType, element.name, element.value);
 786                 }
 787             }
 788 
 789             if (parent != null) {
 790                 if (parent.isCollection()) {
 791                     parent.add(value);
 792                 } else {
 793                     parent.set(value);
 794                 }
 795             }
 796         }
 797 
 798         private Object getListValue(Element parent, String listPropertyName, Object value) {
 799             // If possible, coerce the value to the list item type
 800             if (parent.isTyped()) {
 801                 Type listType = parent.getValueAdapter().getGenericType(listPropertyName);
 802 
 803                 if (listType != null) {
 804                     Type itemType = BeanAdapter.getGenericListItemType(listType);
 805 
 806                     if (itemType instanceof ParameterizedType) {
 807                         itemType = ((ParameterizedType)itemType).getRawType();
 808                     }
 809 
 810                     value = BeanAdapter.coerce(value, (Class<?>)itemType);
 811                 }
 812             }
 813 
 814             return value;
 815         }
 816 
 817         private void processValue() throws LoadException {
 818             // If this is the root element, update the value
 819             if (parent == null) {
 820                 root = value;
 821 
 822                 // checking version of fx namespace - throw exception if not supported
 823                 String fxNSURI = xmlStreamReader.getNamespaceContext().getNamespaceURI("fx");
 824                 if (fxNSURI != null) {
 825                     String fxVersion = fxNSURI.substring(fxNSURI.lastIndexOf("/") + 1);
 826                     if (compareJFXVersions(FX_NAMESPACE_VERSION, fxVersion) < 0) {
 827                         throw constructLoadException("Loading FXML document of version " +
 828                                 fxVersion + " by JavaFX runtime supporting version " + FX_NAMESPACE_VERSION);
 829                     }
 830                 }
 831 
 832                 // checking the version JavaFX API - print warning if not supported
 833                 String defaultNSURI = xmlStreamReader.getNamespaceContext().getNamespaceURI("");
 834                 if (defaultNSURI != null) {
 835                     String nsVersion = defaultNSURI.substring(defaultNSURI.lastIndexOf("/") + 1);
 836                     if (compareJFXVersions(JAVAFX_VERSION, nsVersion) < 0) {
 837                         Logging.getJavaFXLogger().warning("Loading FXML document with JavaFX API of version " +
 838                                 nsVersion + " by JavaFX runtime of version " + JAVAFX_VERSION);
 839                     }
 840                 }
 841             }
 842 
 843             // Add the value to the namespace
 844             if (fx_id != null) {
 845                 namespace.put(fx_id, value);
 846 
 847                 // If the value defines an ID property, set it
 848                 IDProperty idProperty = value.getClass().getAnnotation(IDProperty.class);
 849 
 850                 if (idProperty != null) {
 851                     Map<String, Object> properties = getProperties();
 852                     // set fx:id property value to Node.id only if Node.id was not
 853                     // already set when processing start element attributes
 854                     if (properties.get(idProperty.value()) == null) {
 855                         properties.put(idProperty.value(), fx_id);
 856                     }
 857                 }
 858 
 859                 // Set the controller field value
 860                 injectFields(fx_id, value);
 861             }
 862         }
 863 
 864         @Override
 865         @SuppressWarnings("unchecked")
 866         public void processCharacters() throws LoadException {
 867             Class<?> type = value.getClass();
 868             DefaultProperty defaultProperty = type.getAnnotation(DefaultProperty.class);
 869 
 870             // If the default property is a read-only list, add the value to it;
 871             // otherwise, set the value as the default property
 872             if (defaultProperty != null) {
 873                 String text = xmlStreamReader.getText();
 874                 text = extraneousWhitespacePattern.matcher(text).replaceAll(" ");
 875 
 876                 String defaultPropertyName = defaultProperty.value();
 877                 BeanAdapter valueAdapter = getValueAdapter();
 878 
 879                 if (valueAdapter.isReadOnly(defaultPropertyName)
 880                     && List.class.isAssignableFrom(valueAdapter.getType(defaultPropertyName))) {
 881                     List<Object> list = (List<Object>)valueAdapter.get(defaultPropertyName);
 882                     list.add(getListValue(this, defaultPropertyName, text));
 883                 } else {
 884                     valueAdapter.put(defaultPropertyName, text.trim());
 885                 }
 886             } else {
 887                 throw constructLoadException(type.getName() + " does not have a default property.");
 888             }
 889         }
 890 
 891         @Override
 892         public void processAttribute(String prefix, String localName, String value)
 893             throws IOException{
 894             if (prefix != null
 895                 && prefix.equals(FX_NAMESPACE_PREFIX)) {
 896                 if (localName.equals(FX_ID_ATTRIBUTE)) {
 897                     // Verify that ID is a valid identifier
 898                     if (value.equals(NULL_KEYWORD)) {
 899                         throw constructLoadException("Invalid identifier.");
 900                     }
 901 
 902                     for (int i = 0, n = value.length(); i < n; i++) {
 903                         if (!Character.isJavaIdentifierPart(value.charAt(i))) {
 904                             throw constructLoadException("Invalid identifier.");
 905                         }
 906                     }
 907 
 908                     fx_id = value;
 909 
 910                 } else if (localName.equals(FX_CONTROLLER_ATTRIBUTE)) {
 911                     if (current.parent != null) {
 912                         throw constructLoadException(FX_NAMESPACE_PREFIX + ":" + FX_CONTROLLER_ATTRIBUTE
 913                             + " can only be applied to root element.");
 914                     }
 915 
 916                     if (controller != null) {
 917                         throw constructLoadException("Controller value already specified.");
 918                     }
 919 
 920                     if (!staticLoad) {
 921                         Class<?> type;
 922                         try {
 923                             type = getClassLoader().loadClass(value);
 924                         } catch (ClassNotFoundException exception) {
 925                             throw constructLoadException(exception);
 926                         }
 927 
 928                         try {
 929                             if (controllerFactory == null) {
 930                                 ReflectUtil.checkPackageAccess(type);
 931                                 setController(type.newInstance());
 932                             } else {
 933                                 setController(controllerFactory.call(type));
 934                             }
 935                         } catch (InstantiationException exception) {
 936                             throw constructLoadException(exception);
 937                         } catch (IllegalAccessException exception) {
 938                             throw constructLoadException(exception);
 939                         }
 940                     }
 941                 } else {
 942                     throw constructLoadException("Invalid attribute.");
 943                 }
 944             } else {
 945                 super.processAttribute(prefix, localName, value);
 946             }
 947         }
 948 
 949         public abstract Object constructValue() throws IOException;
 950     }
 951 
 952     // Element representing a class instance
 953     private class InstanceDeclarationElement extends ValueElement {
 954         public Class<?> type;
 955 
 956         public String constant = null;
 957         public String factory = null;
 958 
 959         public InstanceDeclarationElement(Class<?> type) throws LoadException {
 960             this.type = type;
 961         }
 962 
 963         @Override
 964         public void processAttribute(String prefix, String localName, String value)
 965             throws IOException {
 966             if (prefix != null
 967                 && prefix.equals(FX_NAMESPACE_PREFIX)) {
 968                 if (localName.equals(FX_VALUE_ATTRIBUTE)) {
 969                     this.value = value;
 970                 } else if (localName.equals(FX_CONSTANT_ATTRIBUTE)) {
 971                     constant = value;
 972                 } else if (localName.equals(FX_FACTORY_ATTRIBUTE)) {
 973                     factory = value;
 974                 } else {
 975                     super.processAttribute(prefix, localName, value);
 976                 }
 977             } else {
 978                 super.processAttribute(prefix, localName, value);
 979             }
 980         }
 981 
 982         @Override
 983         public Object constructValue() throws IOException {
 984             Object value;
 985             if (this.value != null) {
 986                 value = BeanAdapter.coerce(this.value, type);
 987             } else if (constant != null) {
 988                 value = BeanAdapter.getConstantValue(type, constant);
 989             } else if (factory != null) {
 990                 Method factoryMethod;
 991                 try {
 992                     factoryMethod = MethodUtil.getMethod(type, factory, new Class[] {});
 993                 } catch (NoSuchMethodException exception) {
 994                     throw constructLoadException(exception);
 995                 }
 996 
 997                 try {
 998                     value = MethodUtil.invoke(factoryMethod, null, new Object [] {});
 999                 } catch (IllegalAccessException exception) {
1000                     throw constructLoadException(exception);
1001                 } catch (InvocationTargetException exception) {
1002                     throw constructLoadException(exception);
1003                 }
1004             } else {
1005                 value = (builderFactory == null) ? null : builderFactory.getBuilder(type);
1006 
1007                 if (value == null) {
1008                     value = DEFAULT_BUILDER_FACTORY.getBuilder(type);
1009                 }
1010 
1011                 if (value == null) {
1012                     try {
1013                         ReflectUtil.checkPackageAccess(type);
1014                         value = type.newInstance();
1015                     } catch (InstantiationException exception) {
1016                         throw constructLoadException(exception);
1017                     } catch (IllegalAccessException exception) {
1018                         throw constructLoadException(exception);
1019                     }
1020                 }
1021             }
1022 
1023             return value;
1024         }
1025     }
1026 
1027     // Element representing an unknown type
1028     private class UnknownTypeElement extends ValueElement {
1029         // Map type representing an unknown value
1030         @DefaultProperty("items")
1031         public class UnknownValueMap extends AbstractMap<String, Object> {
1032             private ArrayList<?> items = new ArrayList<Object>();
1033             private HashMap<String, Object> values = new HashMap<String, Object>();
1034 
1035             @Override
1036             public Object get(Object key) {
1037                 if (key == null) {
1038                     throw new NullPointerException();
1039                 }
1040 
1041                 return (key.equals(getClass().getAnnotation(DefaultProperty.class).value())) ?
1042                     items : values.get(key);
1043             }
1044 
1045             @Override
1046             public Object put(String key, Object value) {
1047                 if (key == null) {
1048                     throw new NullPointerException();
1049                 }
1050 
1051                 if (key.equals(getClass().getAnnotation(DefaultProperty.class).value())) {
1052                     throw new IllegalArgumentException();
1053                 }
1054 
1055                 return values.put(key, value);
1056             }
1057 
1058             @Override
1059             public Set<Entry<String, Object>> entrySet() {
1060                 return Collections.emptySet();
1061             }
1062         }
1063 
1064         @Override
1065         public void processEndElement() throws IOException {
1066             // No-op
1067         }
1068 
1069         @Override
1070         public Object constructValue() throws LoadException {
1071             return new UnknownValueMap();
1072         }
1073     }
1074 
1075     // Element representing an include
1076     private class IncludeElement extends ValueElement {
1077         public String source = null;
1078         public ResourceBundle resources = FXMLLoader.this.resources;
1079         public Charset charset = FXMLLoader.this.charset;
1080 
1081         @Override
1082         public void processAttribute(String prefix, String localName, String value)
1083             throws IOException {
1084             if (prefix == null) {
1085                 if (localName.equals(INCLUDE_SOURCE_ATTRIBUTE)) {
1086                     if (loadListener != null) {
1087                         loadListener.readInternalAttribute(localName, value);
1088                     }
1089 
1090                     source = value;
1091                 } else if (localName.equals(INCLUDE_RESOURCES_ATTRIBUTE)) {
1092                     if (loadListener != null) {
1093                         loadListener.readInternalAttribute(localName, value);
1094                     }
1095 
1096                     resources = ResourceBundle.getBundle(value, Locale.getDefault(),
1097                             FXMLLoader.this.resources.getClass().getClassLoader());
1098                 } else if (localName.equals(INCLUDE_CHARSET_ATTRIBUTE)) {
1099                     if (loadListener != null) {
1100                         loadListener.readInternalAttribute(localName, value);
1101                     }
1102 
1103                     charset = Charset.forName(value);
1104                 } else {
1105                     super.processAttribute(prefix, localName, value);
1106                 }
1107             } else {
1108                 super.processAttribute(prefix, localName, value);
1109             }
1110         }
1111 
1112         @Override
1113         public Object constructValue() throws IOException {
1114             if (source == null) {
1115                 throw constructLoadException(INCLUDE_SOURCE_ATTRIBUTE + " is required.");
1116             }
1117 
1118             URL location;
1119             final ClassLoader cl = getClassLoader();
1120             if (source.charAt(0) == '/') {
1121             // FIXME: JIGSAW -- use Class.getResourceAsStream if resource is in a module
1122                 location = cl.getResource(source.substring(1));
1123                 if (location == null) {
1124                     throw constructLoadException("Cannot resolve path: " + source);
1125                 }
1126             } else {
1127                 if (FXMLLoader.this.location == null) {
1128                     throw constructLoadException("Base location is undefined.");
1129                 }
1130 
1131                 location = new URL(FXMLLoader.this.location, source);
1132             }
1133 
1134             FXMLLoader fxmlLoader = new FXMLLoader(location, resources,
1135                 builderFactory, controllerFactory, charset,
1136                 loaders);
1137             fxmlLoader.parentLoader = FXMLLoader.this;
1138 
1139             if (isCyclic(FXMLLoader.this, fxmlLoader)) {
1140                 throw new IOException(
1141                         String.format(
1142                         "Including \"%s\" in \"%s\" created cyclic reference.",
1143                         fxmlLoader.location.toExternalForm(),
1144                         FXMLLoader.this.location.toExternalForm()));
1145             }
1146             fxmlLoader.setClassLoader(cl);
1147             fxmlLoader.setStaticLoad(staticLoad);
1148 
1149             Object value = fxmlLoader.loadImpl(callerClass);
1150 
1151             if (fx_id != null) {
1152                 String id = this.fx_id + CONTROLLER_SUFFIX;
1153                 Object controller = fxmlLoader.getController();
1154 
1155                 namespace.put(id, controller);
1156                 injectFields(id, controller);
1157             }
1158 
1159             return value;
1160         }
1161     }
1162 
1163     private void injectFields(String fieldName, Object value) throws LoadException {
1164         if (controller != null && fieldName != null) {
1165             List<Field> fields = controllerAccessor.getControllerFields().get(fieldName);
1166             if (fields != null) {
1167                 try {
1168                     for (Field f : fields) {
1169                         f.set(controller, value);
1170                     }
1171                 } catch (IllegalAccessException exception) {
1172                     throw constructLoadException(exception);
1173                 }
1174             }
1175         }
1176     }
1177 
1178     // Element representing a reference
1179     private class ReferenceElement extends ValueElement {
1180         public String source = null;
1181 
1182         @Override
1183         public void processAttribute(String prefix, String localName, String value)
1184             throws IOException {
1185             if (prefix == null) {
1186                 if (localName.equals(REFERENCE_SOURCE_ATTRIBUTE)) {
1187                     if (loadListener != null) {
1188                         loadListener.readInternalAttribute(localName, value);
1189                     }
1190 
1191                     source = value;
1192                 } else {
1193                     super.processAttribute(prefix, localName, value);
1194                 }
1195             } else {
1196                 super.processAttribute(prefix, localName, value);
1197             }
1198         }
1199 
1200         @Override
1201         public Object constructValue() throws LoadException {
1202             if (source == null) {
1203                 throw constructLoadException(REFERENCE_SOURCE_ATTRIBUTE + " is required.");
1204             }
1205 
1206             KeyPath path = KeyPath.parse(source);
1207             if (!Expression.isDefined(namespace, path)) {
1208                 throw constructLoadException("Value \"" + source + "\" does not exist.");
1209             }
1210 
1211             return Expression.get(namespace, path);
1212         }
1213     }
1214 
1215     // Element representing a copy
1216     private class CopyElement extends ValueElement {
1217         public String source = null;
1218 
1219         @Override
1220         public void processAttribute(String prefix, String localName, String value)
1221             throws IOException {
1222             if (prefix == null) {
1223                 if (localName.equals(COPY_SOURCE_ATTRIBUTE)) {
1224                     if (loadListener != null) {
1225                         loadListener.readInternalAttribute(localName, value);
1226                     }
1227 
1228                     source = value;
1229                 } else {
1230                     super.processAttribute(prefix, localName, value);
1231                 }
1232             } else {
1233                 super.processAttribute(prefix, localName, value);
1234             }
1235         }
1236 
1237         @Override
1238         public Object constructValue() throws LoadException {
1239             if (source == null) {
1240                 throw constructLoadException(COPY_SOURCE_ATTRIBUTE + " is required.");
1241             }
1242 
1243             KeyPath path = KeyPath.parse(source);
1244             if (!Expression.isDefined(namespace, path)) {
1245                 throw constructLoadException("Value \"" + source + "\" does not exist.");
1246             }
1247 
1248             Object sourceValue = Expression.get(namespace, path);
1249             Class<?> sourceValueType = sourceValue.getClass();
1250 
1251             Constructor<?> constructor = null;
1252             try {
1253                 constructor = ConstructorUtil.getConstructor(sourceValueType, new Class[] { sourceValueType });
1254             } catch (NoSuchMethodException exception) {
1255                 // No-op
1256             }
1257 
1258             Object value;
1259             if (constructor != null) {
1260                 try {
1261                     ReflectUtil.checkPackageAccess(sourceValueType);
1262                     value = constructor.newInstance(sourceValue);
1263                 } catch (InstantiationException exception) {
1264                     throw constructLoadException(exception);
1265                 } catch (IllegalAccessException exception) {
1266                     throw constructLoadException(exception);
1267                 } catch (InvocationTargetException exception) {
1268                     throw constructLoadException(exception);
1269                 }
1270             } else {
1271                 throw constructLoadException("Can't copy value " + sourceValue + ".");
1272             }
1273 
1274             return value;
1275         }
1276     }
1277 
1278     // Element representing a predefined root value
1279     private class RootElement extends ValueElement {
1280         public String type = null;
1281 
1282         @Override
1283         public void processAttribute(String prefix, String localName, String value)
1284             throws IOException {
1285             if (prefix == null) {
1286                 if (localName.equals(ROOT_TYPE_ATTRIBUTE)) {
1287                     if (loadListener != null) {
1288                         loadListener.readInternalAttribute(localName, value);
1289                     }
1290 
1291                     type = value;
1292                 } else {
1293                     super.processAttribute(prefix, localName, value);
1294                 }
1295             } else {
1296                 super.processAttribute(prefix, localName, value);
1297             }
1298         }
1299 
1300         @Override
1301         public Object constructValue() throws LoadException {
1302             if (type == null) {
1303                 throw constructLoadException(ROOT_TYPE_ATTRIBUTE + " is required.");
1304             }
1305 
1306             Class<?> type = getType(this.type);
1307 
1308             if (type == null) {
1309                 throw constructLoadException(this.type + " is not a valid type.");
1310             }
1311 
1312             Object value;
1313             if (root == null) {
1314                 if (staticLoad) {
1315                     value = (builderFactory == null) ? null : builderFactory.getBuilder(type);
1316 
1317                     if (value == null) {
1318                         value = DEFAULT_BUILDER_FACTORY.getBuilder(type);
1319                     }
1320 
1321                     if (value == null) {
1322                         try {
1323                             ReflectUtil.checkPackageAccess(type);
1324                             value = type.newInstance();
1325                         } catch (InstantiationException exception) {
1326                             throw constructLoadException(exception);
1327                         } catch (IllegalAccessException exception) {
1328                             throw constructLoadException(exception);
1329                         }
1330                     }
1331                     root = value;
1332                 } else {
1333                     throw constructLoadException("Root hasn't been set. Use method setRoot() before load.");
1334                 }
1335             } else {
1336                 if (!type.isAssignableFrom(root.getClass())) {
1337                     throw constructLoadException("Root is not an instance of "
1338                         + type.getName() + ".");
1339                 }
1340 
1341                 value = root;
1342             }
1343 
1344             return value;
1345         }
1346     }
1347 
1348     // Element representing a property
1349     private class PropertyElement extends Element {
1350         public final String name;
1351         public final Class<?> sourceType;
1352         public final boolean readOnly;
1353 
1354         public PropertyElement(String name, Class<?> sourceType) throws LoadException {
1355             if (parent == null) {
1356                 throw constructLoadException("Invalid root element.");
1357             }
1358 
1359             if (parent.value == null) {
1360                 throw constructLoadException("Parent element does not support property elements.");
1361             }
1362 
1363             this.name = name;
1364             this.sourceType = sourceType;
1365 
1366             if (sourceType == null) {
1367                 // The element represents an instance property
1368                 if (name.startsWith(EVENT_HANDLER_PREFIX)) {
1369                     throw constructLoadException("\"" + name + "\" is not a valid element name.");
1370                 }
1371 
1372                 Map<String, Object> parentProperties = parent.getProperties();
1373 
1374                 if (parent.isTyped()) {
1375                     readOnly = parent.getValueAdapter().isReadOnly(name);
1376                 } else {
1377                 // If the map already defines a value for the property, assume
1378                     // that it is read-only
1379                     readOnly = parentProperties.containsKey(name);
1380                 }
1381 
1382                 if (readOnly) {
1383                     Object value = parentProperties.get(name);
1384                     if (value == null) {
1385                         throw constructLoadException("Invalid property.");
1386                     }
1387 
1388                     updateValue(value);
1389                 }
1390             } else {
1391                 // The element represents a static property
1392                 readOnly = false;
1393             }
1394         }
1395 
1396         @Override
1397         public boolean isCollection() {
1398             return (readOnly) ? super.isCollection() : false;
1399         }
1400 
1401         @Override
1402         public void add(Object element) throws LoadException {
1403             // Coerce the element to the list item type
1404             if (parent.isTyped()) {
1405                 Type listType = parent.getValueAdapter().getGenericType(name);
1406                 element = BeanAdapter.coerce(element, BeanAdapter.getListItemType(listType));
1407             }
1408 
1409             // Add the item to the list
1410             super.add(element);
1411         }
1412 
1413         @Override
1414         public void set(Object value) throws LoadException {
1415             // Update the value
1416             updateValue(value);
1417 
1418             if (sourceType == null) {
1419                 // Apply value to parent element's properties
1420                 parent.getProperties().put(name, value);
1421             } else {
1422                 if (parent.value instanceof Builder) {
1423                     // Defer evaluation of the property
1424                     parent.staticPropertyElements.add(this);
1425                 } else {
1426                     // Apply the static property value
1427                     BeanAdapter.put(parent.value, sourceType, name, value);
1428                 }
1429             }
1430         }
1431 
1432         @Override
1433         public void processAttribute(String prefix, String localName, String value)
1434             throws IOException {
1435             if (!readOnly) {
1436                 throw constructLoadException("Attributes are not supported for writable property elements.");
1437             }
1438 
1439             super.processAttribute(prefix, localName, value);
1440         }
1441 
1442         @Override
1443         public void processEndElement() throws IOException {
1444             super.processEndElement();
1445 
1446             if (readOnly) {
1447                 processInstancePropertyAttributes();
1448                 processEventHandlerAttributes();
1449             }
1450         }
1451 
1452         @Override
1453         public void processCharacters() throws IOException {
1454             String text = xmlStreamReader.getText();
1455             text = extraneousWhitespacePattern.matcher(text).replaceAll(" ").trim();
1456 
1457             if (readOnly) {
1458                 if (isCollection()) {
1459                     add(text);
1460                 } else {
1461                     super.processCharacters();
1462                 }
1463             } else {
1464                 set(text);
1465             }
1466         }
1467     }
1468 
1469     // Element representing an unknown static property
1470     private class UnknownStaticPropertyElement extends Element {
1471         public UnknownStaticPropertyElement() throws LoadException {
1472             if (parent == null) {
1473                 throw constructLoadException("Invalid root element.");
1474             }
1475 
1476             if (parent.value == null) {
1477                 throw constructLoadException("Parent element does not support property elements.");
1478             }
1479         }
1480 
1481         @Override
1482         public boolean isCollection() {
1483             return false;
1484         }
1485 
1486         @Override
1487         public void set(Object value) {
1488             updateValue(value);
1489         }
1490 
1491         @Override
1492         public void processCharacters() throws IOException {
1493             String text = xmlStreamReader.getText();
1494             text = extraneousWhitespacePattern.matcher(text).replaceAll(" ");
1495 
1496             updateValue(text.trim());
1497         }
1498     }
1499 
1500     // Element representing a script block
1501     private class ScriptElement extends Element {
1502         public String source = null;
1503         public Charset charset = FXMLLoader.this.charset;
1504 
1505         @Override
1506         public boolean isCollection() {
1507             return false;
1508         }
1509 
1510         @Override
1511         public void processStartElement() throws IOException {
1512             super.processStartElement();
1513 
1514             if (source != null && !staticLoad) {
1515                 int i = source.lastIndexOf(".");
1516                 if (i == -1) {
1517                     throw constructLoadException("Cannot determine type of script \""
1518                         + source + "\".");
1519                 }
1520 
1521                 String extension = source.substring(i + 1);
1522                 ScriptEngine engine;
1523                 final ClassLoader cl = getClassLoader();
1524                 if (scriptEngine != null && scriptEngine.getFactory().getExtensions().contains(extension)) {
1525                     // If we have a page language and it's engine supports the extension, use the same engine
1526                     engine = scriptEngine;
1527                 } else {
1528                     ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
1529                     try {
1530                         Thread.currentThread().setContextClassLoader(cl);
1531                         ScriptEngineManager scriptEngineManager = getScriptEngineManager();
1532                         engine = scriptEngineManager.getEngineByExtension(extension);
1533                     } finally {
1534                         Thread.currentThread().setContextClassLoader(oldLoader);
1535                     }
1536                 }
1537 
1538                 if (engine == null) {
1539                     throw constructLoadException("Unable to locate scripting engine for"
1540                         + " extension " + extension + ".");
1541                 }
1542 
1543                 try {
1544                     URL location;
1545                     if (source.charAt(0) == '/') {
1546                         // FIXME: JIGSAW -- use Class.getResourceAsStream if resource is in a module
1547                         location = cl.getResource(source.substring(1));
1548                     } else {
1549                         if (FXMLLoader.this.location == null) {
1550                             throw constructLoadException("Base location is undefined.");
1551                         }
1552 
1553                         location = new URL(FXMLLoader.this.location, source);
1554                     }
1555 
1556                     InputStreamReader scriptReader = null;
1557                     try {
1558                         scriptReader = new InputStreamReader(location.openStream(), charset);
1559                         engine.eval(scriptReader);
1560                     } catch(ScriptException exception) {
1561                         exception.printStackTrace();
1562                     } finally {
1563                         if (scriptReader != null) {
1564                             scriptReader.close();
1565                         }
1566                     }
1567                 } catch (IOException exception) {
1568                     throw constructLoadException(exception);
1569                 }
1570             }
1571         }
1572 
1573         @Override
1574         public void processEndElement() throws IOException {
1575             super.processEndElement();
1576 
1577             if (value != null && !staticLoad) {
1578                 // Evaluate the script
1579                 try {
1580                     scriptEngine.eval((String)value);
1581                 } catch (ScriptException exception) {
1582                     System.err.println(exception.getMessage());
1583                 }
1584             }
1585         }
1586 
1587         @Override
1588         public void processCharacters() throws LoadException {
1589             if (source != null) {
1590                 throw constructLoadException("Script source already specified.");
1591             }
1592 
1593             if (scriptEngine == null && !staticLoad) {
1594                 throw constructLoadException("Page language not specified.");
1595             }
1596 
1597             updateValue(xmlStreamReader.getText());
1598         }
1599 
1600         @Override
1601         public void processAttribute(String prefix, String localName, String value)
1602             throws IOException {
1603             if (prefix == null
1604                 && localName.equals(SCRIPT_SOURCE_ATTRIBUTE)) {
1605                 if (loadListener != null) {
1606                     loadListener.readInternalAttribute(localName, value);
1607                 }
1608 
1609                 source = value;
1610             } else if (localName.equals(SCRIPT_CHARSET_ATTRIBUTE)) {
1611                 if (loadListener != null) {
1612                     loadListener.readInternalAttribute(localName, value);
1613                 }
1614 
1615                 charset = Charset.forName(value);
1616             } else {
1617                 throw constructLoadException(prefix == null ? localName : prefix + ":" + localName
1618                     + " is not a valid attribute.");
1619             }
1620         }
1621     }
1622 
1623     // Element representing a define block
1624     private class DefineElement extends Element {
1625         @Override
1626         public boolean isCollection() {
1627             return true;
1628         }
1629 
1630         @Override
1631         public void add(Object element) {
1632             // No-op
1633         }
1634 
1635         @Override
1636         public void processAttribute(String prefix, String localName, String value)
1637             throws LoadException{
1638             throw constructLoadException("Element does not support attributes.");
1639         }
1640     }
1641 
1642     // Class representing an attribute of an element
1643     private static class Attribute {
1644         public final String name;
1645         public final Class<?> sourceType;
1646         public final String value;
1647 
1648         public Attribute(String name, Class<?> sourceType, String value) {
1649             this.name = name;
1650             this.sourceType = sourceType;
1651             this.value = value;
1652         }
1653     }
1654 
1655     // Event handler that delegates to a method defined by the controller object
1656     private static class ControllerMethodEventHandler<T extends Event> implements EventHandler<T> {
1657         private final MethodHandler handler;
1658 
1659         public ControllerMethodEventHandler(MethodHandler handler) {
1660             this.handler = handler;
1661         }
1662 
1663         @Override
1664         public void handle(T event) {
1665             handler.invoke(event);
1666         }
1667     }
1668 
1669     // Event handler implemented in script code
1670     private static class ScriptEventHandler implements EventHandler<Event> {
1671         public final String script;
1672         public final ScriptEngine scriptEngine;
1673 
1674         public ScriptEventHandler(String script, ScriptEngine scriptEngine) {
1675             this.script = script;
1676             this.scriptEngine = scriptEngine;
1677         }
1678 
1679         @Override
1680         public void handle(Event event) {
1681             // Don't pollute the page namespace with values defined in the script
1682             Bindings engineBindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
1683             Bindings localBindings = scriptEngine.createBindings();
1684             localBindings.put(EVENT_KEY, event);
1685             localBindings.putAll(engineBindings);
1686             scriptEngine.setBindings(localBindings, ScriptContext.ENGINE_SCOPE);
1687 
1688             // Execute the script
1689             try {
1690                 scriptEngine.eval(script);
1691             } catch (ScriptException exception){
1692                 throw new RuntimeException(exception);
1693             }
1694 
1695             // Restore the original bindings
1696             scriptEngine.setBindings(engineBindings, ScriptContext.ENGINE_SCOPE);
1697         }
1698     }
1699 
1700     // Observable list change listener
1701     private static class ObservableListChangeAdapter implements ListChangeListener {
1702         private final MethodHandler handler;
1703 
1704         public ObservableListChangeAdapter(MethodHandler handler) {
1705             this.handler = handler;
1706         }
1707 
1708         @Override
1709         @SuppressWarnings("unchecked")
1710         public void onChanged(Change change) {
1711             if (handler != null) {
1712                 handler.invoke(change);
1713             }
1714         }
1715     }
1716 
1717     // Observable map change listener
1718     private static class ObservableMapChangeAdapter implements MapChangeListener {
1719         public final MethodHandler handler;
1720 
1721         public ObservableMapChangeAdapter(MethodHandler handler) {
1722             this.handler = handler;
1723         }
1724 
1725         @Override
1726         public void onChanged(Change change) {
1727             if (handler != null) {
1728                 handler.invoke(change);
1729             }
1730         }
1731     }
1732 
1733     // Observable set change listener
1734     private static class ObservableSetChangeAdapter implements SetChangeListener {
1735         public final MethodHandler handler;
1736 
1737         public ObservableSetChangeAdapter(MethodHandler handler) {
1738             this.handler = handler;
1739         }
1740 
1741         @Override
1742         public void onChanged(Change change) {
1743             if (handler != null) {
1744                 handler.invoke(change);
1745             }
1746         }
1747     }
1748 
1749     // Property model change listener
1750     private static class PropertyChangeAdapter implements ChangeListener<Object> {
1751         public final MethodHandler handler;
1752 
1753         public PropertyChangeAdapter(MethodHandler handler) {
1754             this.handler = handler;
1755         }
1756 
1757         @Override
1758         public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) {
1759             handler.invoke(observable, oldValue, newValue);
1760         }
1761     }
1762 
1763     private static class MethodHandler {
1764         private final Object controller;
1765         private final Method method;
1766         private final SupportedType type;
1767 
1768         private MethodHandler(Object controller, Method method, SupportedType type) {
1769             this.method = method;
1770             this.controller = controller;
1771             this.type = type;
1772         }
1773 
1774         public void invoke(Object... params) {
1775             try {
1776                 if (type != SupportedType.PARAMETERLESS) {
1777                     MethodUtil.invoke(method, controller, params);
1778                 } else {
1779                     MethodUtil.invoke(method, controller, new Object[] {});
1780                 }
1781             } catch (InvocationTargetException exception) {
1782                 throw new RuntimeException(exception);
1783             } catch (IllegalAccessException exception) {
1784                 throw new RuntimeException(exception);
1785             }
1786         }
1787     }
1788 
1789     private URL location;
1790     private ResourceBundle resources;
1791 
1792     private ObservableMap<String, Object> namespace = FXCollections.observableHashMap();
1793 
1794     private Object root = null;
1795     private Object controller = null;
1796 
1797     private BuilderFactory builderFactory;
1798     private Callback<Class<?>, Object> controllerFactory;
1799     private Charset charset;
1800 
1801     private final LinkedList<FXMLLoader> loaders;
1802 
1803     private ClassLoader classLoader = null;
1804     private boolean staticLoad = false;
1805     private LoadListener loadListener = null;
1806 
1807     private FXMLLoader parentLoader;
1808 
1809     private XMLStreamReader xmlStreamReader = null;
1810     private Element current = null;
1811 
1812     private ScriptEngine scriptEngine = null;
1813 
1814     private List<String> packages = new LinkedList<String>();
1815     private Map<String, Class<?>> classes = new HashMap<String, Class<?>>();
1816 
1817     private ScriptEngineManager scriptEngineManager = null;
1818 
1819     private static ClassLoader defaultClassLoader = null;
1820 
1821     private static final Pattern extraneousWhitespacePattern = Pattern.compile("\\s+");
1822 
1823     private static BuilderFactory DEFAULT_BUILDER_FACTORY = new JavaFXBuilderFactory();
1824 
1825     /**
1826      * The character set used when character set is not explicitly specified.
1827      */
1828     public static final String DEFAULT_CHARSET_NAME = "UTF-8";
1829 
1830     /**
1831      * The tag name of language processing instruction.
1832      */
1833     public static final String LANGUAGE_PROCESSING_INSTRUCTION = "language";
1834     /**
1835      * The tag name of import processing instruction.
1836      */
1837     public static final String IMPORT_PROCESSING_INSTRUCTION = "import";
1838 
1839     /**
1840      * Prefix of 'fx' namespace.
1841      */
1842     public static final String FX_NAMESPACE_PREFIX = "fx";
1843     /**
1844      * The name of fx:controller attribute of a root.
1845      */
1846     public static final String FX_CONTROLLER_ATTRIBUTE = "controller";
1847     /**
1848      * The name of fx:id attribute.
1849      */
1850     public static final String FX_ID_ATTRIBUTE = "id";
1851     /**
1852      * The name of fx:value attribute.
1853      */
1854     public static final String FX_VALUE_ATTRIBUTE = "value";
1855     /**
1856      * The tag name of 'fx:constant'.
1857      * @since JavaFX 2.2
1858      */
1859     public static final String FX_CONSTANT_ATTRIBUTE = "constant";
1860     /**
1861      * The name of 'fx:factory' attribute.
1862      */
1863     public static final String FX_FACTORY_ATTRIBUTE = "factory";
1864 
1865     /**
1866      * The tag name of {@literal <fx:include>}.
1867      */
1868     public static final String INCLUDE_TAG = "include";
1869     /**
1870      * The {@literal <fx:include>} 'source' attribute.
1871      */
1872     public static final String INCLUDE_SOURCE_ATTRIBUTE = "source";
1873     /**
1874      * The {@literal <fx:include>} 'resources' attribute.
1875      */
1876     public static final String INCLUDE_RESOURCES_ATTRIBUTE = "resources";
1877     /**
1878      * The {@literal <fx:include>} 'charset' attribute.
1879      */
1880     public static final String INCLUDE_CHARSET_ATTRIBUTE = "charset";
1881 
1882     /**
1883      * The tag name of {@literal <fx:script>}.
1884      */
1885     public static final String SCRIPT_TAG = "script";
1886     /**
1887      * The {@literal <fx:script>} 'source' attribute.
1888      */
1889     public static final String SCRIPT_SOURCE_ATTRIBUTE = "source";
1890     /**
1891      * The {@literal <fx:script>} 'charset' attribute.
1892      */
1893     public static final String SCRIPT_CHARSET_ATTRIBUTE = "charset";
1894 
1895     /**
1896      * The tag name of {@literal <fx:define>}.
1897      */
1898     public static final String DEFINE_TAG = "define";
1899 
1900     /**
1901      * The tag name of {@literal <fx:reference>}.
1902      */
1903     public static final String REFERENCE_TAG = "reference";
1904     /**
1905      * The {@literal <fx:reference>} 'source' attribute.
1906      */
1907     public static final String REFERENCE_SOURCE_ATTRIBUTE = "source";
1908 
1909     /**
1910      * The tag name of {@literal <fx:root>}.
1911      * @since JavaFX 2.2
1912      */
1913     public static final String ROOT_TAG = "root";
1914     /**
1915      * The {@literal <fx:root>} 'type' attribute.
1916      * @since JavaFX 2.2
1917      */
1918     public static final String ROOT_TYPE_ATTRIBUTE = "type";
1919 
1920     /**
1921      * The tag name of {@literal <fx:copy>}.
1922      */
1923     public static final String COPY_TAG = "copy";
1924     /**
1925      * The {@literal <fx:copy>} 'source' attribute.
1926      */
1927     public static final String COPY_SOURCE_ATTRIBUTE = "source";
1928 
1929     /**
1930      * The prefix of event handler attributes.
1931      */
1932     public static final String EVENT_HANDLER_PREFIX = "on";
1933     /**
1934      * The name of the Event object in event handler scripts.
1935      */
1936     public static final String EVENT_KEY = "event";
1937     /**
1938      * Suffix for property change/invalidation handlers.
1939      */
1940     public static final String CHANGE_EVENT_HANDLER_SUFFIX = "Change";
1941     private static final String COLLECTION_HANDLER_NAME = EVENT_HANDLER_PREFIX + CHANGE_EVENT_HANDLER_SUFFIX;
1942 
1943     /**
1944      * Value that represents 'null'.
1945      */
1946     public static final String NULL_KEYWORD = "null";
1947 
1948     /**
1949      * Escape prefix for escaping special characters inside attribute values.
1950      * Serves as an escape for {@link #ESCAPE_PREFIX}, {@link #RELATIVE_PATH_PREFIX},
1951      * {@link #RESOURCE_KEY_PREFIX}, {@link #EXPRESSION_PREFIX},
1952      * {@link #BI_DIRECTIONAL_BINDING_PREFIX}
1953      * @since JavaFX 2.1
1954      */
1955     public static final String ESCAPE_PREFIX = "\\";
1956     /**
1957      * Prefix for relative location resolution.
1958      */
1959     public static final String RELATIVE_PATH_PREFIX = "@";
1960     /**
1961      * Prefix for resource resolution.
1962      */
1963     public static final String RESOURCE_KEY_PREFIX = "%";
1964     /**
1965      * Prefix for (variable) expression resolution.
1966      */
1967     public static final String EXPRESSION_PREFIX = "$";
1968     /**
1969      * Prefix for binding expression resolution.
1970      */
1971     public static final String BINDING_EXPRESSION_PREFIX = "${";
1972     /**
1973      * Suffix for binding expression resolution.
1974      */
1975     public static final String BINDING_EXPRESSION_SUFFIX = "}";
1976 
1977     /**
1978      * Prefix for bidirectional-binding expression resolution.
1979      * @since JavaFX 2.1
1980      */
1981     public static final String BI_DIRECTIONAL_BINDING_PREFIX = "#{";
1982     /**
1983      * Suffix for bidirectional-binding expression resolution.
1984      * @since JavaFX 2.1
1985      */
1986     public static final String BI_DIRECTIONAL_BINDING_SUFFIX = "}";
1987 
1988     /**
1989      * Delimiter for arrays as values.
1990      * @since JavaFX 2.1
1991      */
1992     public static final String ARRAY_COMPONENT_DELIMITER = ",";
1993 
1994     /**
1995      * A key for location URL in namespace map.
1996      * @see #getNamespace()
1997      * @since JavaFX 2.2
1998      */
1999     public static final String LOCATION_KEY = "location";
2000     /**
2001      * A key for ResourceBundle in namespace map.
2002      * @see #getNamespace()
2003      * @since JavaFX 2.2
2004      */
2005     public static final String RESOURCES_KEY = "resources";
2006 
2007     /**
2008      * Prefix for controller method resolution.
2009      */
2010     public static final String CONTROLLER_METHOD_PREFIX = "#";
2011     /**
2012      * A key for controller in namespace map.
2013      * @see #getNamespace()
2014      * @since JavaFX 2.1
2015      */
2016     public static final String CONTROLLER_KEYWORD = "controller";
2017     /**
2018      * A suffix for controllers of included fxml files.
2019      * The full key is stored in namespace map.
2020      * @see #getNamespace()
2021      * @since JavaFX 2.2
2022      */
2023     public static final String CONTROLLER_SUFFIX = "Controller";
2024 
2025     /**
2026      * The name of initialize method.
2027      * @since JavaFX 2.2
2028      */
2029     public static final String INITIALIZE_METHOD_NAME = "initialize";
2030 
2031     /**
2032      * Contains the current javafx version.
2033      * @since JavaFX 8.0
2034      */
2035     public static final String JAVAFX_VERSION;
2036 
2037     /**
2038      * Contains the current fx namepsace version.
2039      * @since JavaFX 8.0
2040      */
2041     public static final String FX_NAMESPACE_VERSION = "1";
2042 
2043     static {
2044         JAVAFX_VERSION = AccessController.doPrivileged(new PrivilegedAction<String>() {
2045             @Override
2046             public String run() {
2047                 return System.getProperty("javafx.version");
2048             }
2049         });
2050 
2051         FXMLLoaderHelper.setFXMLLoaderAccessor(new FXMLLoaderHelper.FXMLLoaderAccessor() {
2052             @Override
2053             public void setStaticLoad(FXMLLoader fxmlLoader, boolean staticLoad) {
2054                 fxmlLoader.setStaticLoad(staticLoad);
2055             }
2056         });
2057     }
2058 
2059     /**
2060      * Creates a new FXMLLoader instance.
2061      */
2062     public FXMLLoader() {
2063         this((URL)null);
2064     }
2065 
2066     /**
2067      * Creates a new FXMLLoader instance.
2068      *
2069      * @param location the location used to resolve relative path attribute values
2070      * @since JavaFX 2.1
2071      */
2072     public FXMLLoader(URL location) {
2073         this(location, null);
2074     }
2075 
2076     /**
2077      * Creates a new FXMLLoader instance.
2078      *
2079      * @param location the location used to resolve relative path attribute values
2080      * @param resources the resources used to resolve resource key attribute values
2081      * @since JavaFX 2.1
2082      */
2083     public FXMLLoader(URL location, ResourceBundle resources) {
2084         this(location, resources, null);
2085     }
2086 
2087     /**
2088      * Creates a new FXMLLoader instance.
2089      *
2090      * @param location the location used to resolve relative path attribute values
2091      * @param resources resources used to resolve resource key attribute values
2092      * @param builderFactory the builder factory used by this loader
2093      * @since JavaFX 2.1
2094      */
2095     public FXMLLoader(URL location, ResourceBundle resources, BuilderFactory builderFactory) {
2096         this(location, resources, builderFactory, null);
2097     }
2098 
2099     /**
2100      * Creates a new FXMLLoader instance.
2101      *
2102      * @param location the location used to resolve relative path attribute values
2103      * @param resources resources used to resolve resource key attribute values
2104      * @param builderFactory the builder factory used by this loader
2105      * @param controllerFactory the controller factory used by this loader
2106      * @since JavaFX 2.1
2107      */
2108     public FXMLLoader(URL location, ResourceBundle resources, BuilderFactory builderFactory,
2109         Callback<Class<?>, Object> controllerFactory) {
2110         this(location, resources, builderFactory, controllerFactory, Charset.forName(DEFAULT_CHARSET_NAME));
2111     }
2112 
2113     /**
2114      * Creates a new FXMLLoader instance.
2115      *
2116      * @param charset the character set used by this loader
2117      */
2118     public FXMLLoader(Charset charset) {
2119         this(null, null, null, null, charset);
2120     }
2121 
2122     /**
2123      * Creates a new FXMLLoader instance.
2124      *
2125      * @param location the location used to resolve relative path attribute values
2126      * @param resources resources used to resolve resource key attribute values
2127      * @param builderFactory the builder factory used by this loader
2128      * @param controllerFactory the controller factory used by this loader
2129      * @param charset the character set used by this loader
2130      * @since JavaFX 2.1
2131      */
2132     public FXMLLoader(URL location, ResourceBundle resources, BuilderFactory builderFactory,
2133         Callback<Class<?>, Object> controllerFactory, Charset charset) {
2134         this(location, resources, builderFactory, controllerFactory, charset,
2135             new LinkedList<FXMLLoader>());
2136     }
2137 
2138     /**
2139      * Creates a new FXMLLoader instance.
2140      *
2141      * @param location the location used to resolve relative path attribute values
2142      * @param resources resources used to resolve resource key attribute values
2143      * @param builderFactory the builder factory used by this loader
2144      * @param controllerFactory the controller factory used by this loader
2145      * @param charset the character set used by this loader
2146      * @param loaders list of loaders
2147      * @since JavaFX 2.1
2148      */
2149     public FXMLLoader(URL location, ResourceBundle resources, BuilderFactory builderFactory,
2150         Callback<Class<?>, Object> controllerFactory, Charset charset,
2151         LinkedList<FXMLLoader> loaders) {
2152         setLocation(location);
2153         setResources(resources);
2154         setBuilderFactory(builderFactory);
2155         setControllerFactory(controllerFactory);
2156         setCharset(charset);
2157 
2158         this.loaders = new LinkedList(loaders);
2159     }
2160 
2161     /**
2162      * Returns the location used to resolve relative path attribute values.
2163      * @return the location used to resolve relative path attribute values
2164      */
2165     public URL getLocation() {
2166         return location;
2167     }
2168 
2169     /**
2170      * Sets the location used to resolve relative path attribute values.
2171      *
2172      * @param location the location
2173      */
2174     public void setLocation(URL location) {
2175         this.location = location;
2176     }
2177 
2178     /**
2179      * Returns the resources used to resolve resource key attribute values.
2180      * @return the resources used to resolve resource key attribute values
2181      */
2182     public ResourceBundle getResources() {
2183         return resources;
2184     }
2185 
2186     /**
2187      * Sets the resources used to resolve resource key attribute values.
2188      *
2189      * @param resources the resources
2190      */
2191     public void setResources(ResourceBundle resources) {
2192         this.resources = resources;
2193     }
2194 
2195     /**
2196      * Returns the namespace used by this loader.
2197      * @return the namespace
2198      */
2199     public ObservableMap<String, Object> getNamespace() {
2200         return namespace;
2201     }
2202 
2203     /**
2204      * Returns the root of the object hierarchy.
2205      * @param <T> the type of the root object
2206      * @return the root of the object hierarchy
2207      */
2208     @SuppressWarnings("unchecked")
2209     public <T> T getRoot() {
2210         return (T)root;
2211     }
2212 
2213     /**
2214      * Sets the root of the object hierarchy. The value passed to this method
2215      * is used as the value of the {@code <fx:root>} tag. This method
2216      * must be called prior to loading the document when using
2217      * {@code <fx:root>}.
2218      *
2219      * @param root the root of the object hierarchy
2220      *
2221      * @since JavaFX 2.2
2222      */
2223     public void setRoot(Object root) {
2224         this.root = root;
2225     }
2226 
2227     @Override
2228     public boolean equals(Object obj) {
2229         if (obj instanceof FXMLLoader) {
2230             FXMLLoader loader = (FXMLLoader)obj;
2231             if (location == null || loader.location == null) {
2232                 return loader.location == location;
2233             }
2234             return location.toExternalForm().equals(
2235                     loader.location.toExternalForm());
2236         }
2237         return false;
2238     }
2239 
2240     private boolean isCyclic(
2241                             FXMLLoader currentLoader,
2242                             FXMLLoader node) {
2243         if (currentLoader == null) {
2244             return false;
2245         }
2246         if (currentLoader.equals(node)) {
2247             return true;
2248         }
2249         return isCyclic(currentLoader.parentLoader, node);
2250     }
2251 
2252     /**
2253      * Returns the controller associated with the root object.
2254      * @param <T> the type of the controller
2255      * @return the controller associated with the root object
2256      */
2257     @SuppressWarnings("unchecked")
2258     public <T> T getController() {
2259         return (T)controller;
2260     }
2261 
2262     /**
2263      * Sets the controller associated with the root object. The value passed to
2264      * this method is used as the value of the {@code fx:controller} attribute.
2265      * This method must be called prior to loading the document when using
2266      * controller event handlers when an {@code fx:controller} attribute is not
2267      * specified in the document.
2268      *
2269      * @param controller the controller to associate with the root object
2270      *
2271      * @since JavaFX 2.2
2272      */
2273     public void setController(Object controller) {
2274         this.controller = controller;
2275 
2276         if (controller == null) {
2277             namespace.remove(CONTROLLER_KEYWORD);
2278         } else {
2279             namespace.put(CONTROLLER_KEYWORD, controller);
2280         }
2281 
2282         controllerAccessor.setController(controller);
2283     }
2284 
2285     /**
2286      * Returns the builder factory used by this loader.
2287      * @return the builder factory
2288      */
2289     public BuilderFactory getBuilderFactory() {
2290         return builderFactory;
2291     }
2292 
2293     /**
2294      * Sets the builder factory used by this loader.
2295      *
2296      * @param builderFactory the builder factory
2297      */
2298     public void setBuilderFactory(BuilderFactory builderFactory) {
2299         this.builderFactory = builderFactory;
2300     }
2301 
2302     /**
2303      * Returns the controller factory used by this loader.
2304      * @return the controller factory
2305      * @since JavaFX 2.1
2306      */
2307     public Callback<Class<?>, Object> getControllerFactory() {
2308         return controllerFactory;
2309     }
2310 
2311     /**
2312      * Sets the controller factory used by this loader.
2313      *
2314      * @param controllerFactory the controller factory
2315      * @since JavaFX 2.1
2316      */
2317     public void setControllerFactory(Callback<Class<?>, Object> controllerFactory) {
2318         this.controllerFactory = controllerFactory;
2319     }
2320 
2321     /**
2322      * Returns the character set used by this loader.
2323      * @return the character set
2324      */
2325     public Charset getCharset() {
2326         return charset;
2327     }
2328 
2329     /**
2330      * Sets the character set used by this loader.
2331      *
2332      * @param charset the character set
2333      * @since JavaFX 2.1
2334      */
2335     public void setCharset(Charset charset) {
2336         if (charset == null) {
2337             throw new NullPointerException("charset is null.");
2338         }
2339 
2340         this.charset = charset;
2341     }
2342 
2343     /**
2344      * Returns the classloader used by this loader.
2345      * @return the classloader
2346      * @since JavaFX 2.1
2347      */
2348     public ClassLoader getClassLoader() {
2349         if (classLoader == null) {
2350             final SecurityManager sm = System.getSecurityManager();
2351             final Class caller = (sm != null) ?
2352                     walker.getCallerClass() :
2353                     null;
2354             return getDefaultClassLoader(caller);
2355         }
2356         return classLoader;
2357     }
2358 
2359     /**
2360      * Sets the classloader used by this loader and clears any existing
2361      * imports.
2362      *
2363      * @param classLoader the classloader
2364      * @since JavaFX 2.1
2365      */
2366     public void setClassLoader(ClassLoader classLoader) {
2367         if (classLoader == null) {
2368             throw new IllegalArgumentException();
2369         }
2370 
2371         this.classLoader = classLoader;
2372 
2373         clearImports();
2374     }
2375 
2376     /*
2377      * Returns the static load flag.
2378      */
2379     boolean isStaticLoad() {
2380         // SB-dependency: RT-21226 has been filed to track this
2381         return staticLoad;
2382     }
2383 
2384     /*
2385      * Sets the static load flag.
2386      *
2387      * @param staticLoad
2388      */
2389     void setStaticLoad(boolean staticLoad) {
2390         // SB-dependency: RT-21226 has been filed to track this
2391         this.staticLoad = staticLoad;
2392     }
2393 
2394     /**
2395      * Returns this loader's load listener.
2396      *
2397      * @return the load listener
2398      *
2399      * @since 9
2400      */
2401     public LoadListener getLoadListener() {
2402         // SB-dependency: RT-21228 has been filed to track this
2403         return loadListener;
2404     }
2405 
2406     /**
2407      * Sets this loader's load listener.
2408      *
2409      * @param loadListener the load listener
2410      *
2411      * @since 9
2412      */
2413     public final void setLoadListener(LoadListener loadListener) {
2414         // SB-dependency: RT-21228 has been filed to track this
2415         this.loadListener = loadListener;
2416     }
2417 
2418     /**
2419      * Loads an object hierarchy from a FXML document. The location from which
2420      * the document will be loaded must have been set by a prior call to
2421      * {@link #setLocation(URL)}.
2422      *
2423      * @param <T> the type of the root object
2424      * @throws IOException if an error occurs during loading
2425      * @return the loaded object hierarchy
2426      *
2427      * @since JavaFX 2.1
2428      */
2429     public <T> T load() throws IOException {
2430         return loadImpl((System.getSecurityManager() != null)
2431                             ? walker.getCallerClass()
2432                             : null);
2433     }
2434 
2435     /**
2436      * Loads an object hierarchy from a FXML document.
2437      *
2438      * @param <T> the type of the root object
2439      * @param inputStream an input stream containing the FXML data to load
2440      *
2441      * @throws IOException if an error occurs during loading
2442      * @return the loaded object hierarchy
2443      */
2444     public <T> T load(InputStream inputStream) throws IOException {
2445         return loadImpl(inputStream, (System.getSecurityManager() != null)
2446                                          ? walker.getCallerClass()
2447                                          : null);
2448     }
2449 
2450     private Class<?> callerClass;
2451 
2452     private <T> T loadImpl(final Class<?> callerClass) throws IOException {
2453         if (location == null) {
2454             throw new IllegalStateException("Location is not set.");
2455         }
2456 
2457         InputStream inputStream = null;
2458         T value;
2459         try {
2460             inputStream = location.openStream();
2461             value = loadImpl(inputStream, callerClass);
2462         } finally {
2463             if (inputStream != null) {
2464                 inputStream.close();
2465             }
2466         }
2467 
2468         return value;
2469     }
2470 
2471     @SuppressWarnings({ "dep-ann", "unchecked" })
2472     private <T> T loadImpl(InputStream inputStream,
2473                            Class<?> callerClass) throws IOException {
2474         if (inputStream == null) {
2475             throw new NullPointerException("inputStream is null.");
2476         }
2477 
2478         this.callerClass = callerClass;
2479         controllerAccessor.setCallerClass(callerClass);
2480         try {
2481             clearImports();
2482 
2483             // Initialize the namespace
2484             namespace.put(LOCATION_KEY, location);
2485             namespace.put(RESOURCES_KEY, resources);
2486 
2487             // Clear the script engine
2488             scriptEngine = null;
2489 
2490             // Create the parser
2491             try {
2492                 XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
2493                 xmlInputFactory.setProperty("javax.xml.stream.isCoalescing", true);
2494 
2495                 // Some stream readers incorrectly report an empty string as the prefix
2496                 // for the default namespace; correct this as needed
2497                 InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charset);
2498                 xmlStreamReader = new StreamReaderDelegate(xmlInputFactory.createXMLStreamReader(inputStreamReader)) {
2499                     @Override
2500                     public String getPrefix() {
2501                         String prefix = super.getPrefix();
2502 
2503                         if (prefix != null
2504                             && prefix.length() == 0) {
2505                             prefix = null;
2506                         }
2507 
2508                         return prefix;
2509                     }
2510 
2511                     @Override
2512                     public String getAttributePrefix(int index) {
2513                         String attributePrefix = super.getAttributePrefix(index);
2514 
2515                         if (attributePrefix != null
2516                             && attributePrefix.length() == 0) {
2517                             attributePrefix = null;
2518                         }
2519 
2520                         return attributePrefix;
2521                     }
2522                 };
2523             } catch (XMLStreamException exception) {
2524                 throw constructLoadException(exception);
2525             }
2526 
2527             // Push this loader onto the stack
2528             loaders.push(this);
2529 
2530             // Parse the XML stream
2531             try {
2532                 while (xmlStreamReader.hasNext()) {
2533                     int event = xmlStreamReader.next();
2534 
2535                     switch (event) {
2536                         case XMLStreamConstants.PROCESSING_INSTRUCTION: {
2537                             processProcessingInstruction();
2538                             break;
2539                         }
2540 
2541                         case XMLStreamConstants.COMMENT: {
2542                             processComment();
2543                             break;
2544                         }
2545 
2546                         case XMLStreamConstants.START_ELEMENT: {
2547                             processStartElement();
2548                             break;
2549                         }
2550 
2551                         case XMLStreamConstants.END_ELEMENT: {
2552                             processEndElement();
2553                             break;
2554                         }
2555 
2556                         case XMLStreamConstants.CHARACTERS: {
2557                             processCharacters();
2558                             break;
2559                         }
2560                     }
2561                 }
2562             } catch (XMLStreamException exception) {
2563                 throw constructLoadException(exception);
2564             }
2565 
2566             if (controller != null) {
2567                 if (controller instanceof Initializable) {
2568                     ((Initializable)controller).initialize(location, resources);
2569                 } else {
2570                     // Inject controller fields
2571                     Map<String, List<Field>> controllerFields =
2572                             controllerAccessor.getControllerFields();
2573 
2574                     injectFields(LOCATION_KEY, location);
2575 
2576                     injectFields(RESOURCES_KEY, resources);
2577 
2578                     // Initialize the controller
2579                     Method initializeMethod = controllerAccessor
2580                                                   .getControllerMethods()
2581                                                   .get(SupportedType.PARAMETERLESS)
2582                                                   .get(INITIALIZE_METHOD_NAME);
2583 
2584                     if (initializeMethod != null) {
2585                         try {
2586                             MethodUtil.invoke(initializeMethod, controller, new Object [] {});
2587                         } catch (IllegalAccessException exception) {
2588                             // TODO Throw when Initializable is deprecated/removed
2589                             // throw constructLoadException(exception);
2590                         } catch (InvocationTargetException exception) {
2591                             throw constructLoadException(exception);
2592                         }
2593                     }
2594                 }
2595             }
2596         } catch (final LoadException exception) {
2597             throw exception;
2598         } catch (final Exception exception) {
2599             throw constructLoadException(exception);
2600         } finally {
2601             controllerAccessor.setCallerClass(null);
2602             // Clear controller accessor caches
2603             controllerAccessor.reset();
2604             // Clear the parser
2605             xmlStreamReader = null;
2606         }
2607 
2608         return (T)root;
2609     }
2610 
2611     private void clearImports() {
2612         packages.clear();
2613         classes.clear();
2614     }
2615 
2616     private LoadException constructLoadException(String message){
2617         return new LoadException(message + constructFXMLTrace());
2618     }
2619 
2620     private LoadException constructLoadException(Throwable cause) {
2621         return new LoadException(constructFXMLTrace(), cause);
2622     }
2623 
2624     private LoadException constructLoadException(String message, Throwable cause){
2625         return new LoadException(message + constructFXMLTrace(), cause);
2626     }
2627 
2628     private String constructFXMLTrace() {
2629         StringBuilder messageBuilder = new StringBuilder("\n");
2630 
2631         for (FXMLLoader loader : loaders) {
2632             messageBuilder.append(loader.location != null ? loader.location.getPath() : "unknown path");
2633 
2634             if (loader.current != null) {
2635                 messageBuilder.append(":");
2636                 messageBuilder.append(loader.getLineNumber());
2637             }
2638 
2639             messageBuilder.append("\n");
2640         }
2641         return messageBuilder.toString();
2642     }
2643 
2644     /**
2645      * Returns the current line number.
2646      */
2647     int getLineNumber() {
2648         return xmlStreamReader.getLocation().getLineNumber();
2649     }
2650 
2651     /**
2652      * Returns the current parse trace.
2653      */
2654     ParseTraceElement[] getParseTrace() {
2655         ParseTraceElement[] parseTrace = new ParseTraceElement[loaders.size()];
2656 
2657         int i = 0;
2658         for (FXMLLoader loader : loaders) {
2659             parseTrace[i++] = new ParseTraceElement(loader.location, (loader.current != null) ?
2660                 loader.getLineNumber() : -1);
2661         }
2662 
2663         return parseTrace;
2664     }
2665 
2666     private void processProcessingInstruction() throws LoadException {
2667         String piTarget = xmlStreamReader.getPITarget().trim();
2668 
2669         if (piTarget.equals(LANGUAGE_PROCESSING_INSTRUCTION)) {
2670             processLanguage();
2671         } else if (piTarget.equals(IMPORT_PROCESSING_INSTRUCTION)) {
2672             processImport();
2673         }
2674     }
2675 
2676     private void processLanguage() throws LoadException {
2677         if (scriptEngine != null) {
2678             throw constructLoadException("Page language already set.");
2679         }
2680 
2681         String language = xmlStreamReader.getPIData();
2682 
2683         if (loadListener != null) {
2684             loadListener.readLanguageProcessingInstruction(language);
2685         }
2686 
2687         if (!staticLoad) {
2688             ScriptEngineManager scriptEngineManager = getScriptEngineManager();
2689             scriptEngine = scriptEngineManager.getEngineByName(language);
2690         }
2691     }
2692 
2693     private void processImport() throws LoadException {
2694         String target = xmlStreamReader.getPIData().trim();
2695 
2696         if (loadListener != null) {
2697             loadListener.readImportProcessingInstruction(target);
2698         }
2699 
2700         if (target.endsWith(".*")) {
2701             importPackage(target.substring(0, target.length() - 2));
2702         } else {
2703             importClass(target);
2704         }
2705     }
2706 
2707     private void processComment() throws LoadException {
2708         if (loadListener != null) {
2709             loadListener.readComment(xmlStreamReader.getText());
2710         }
2711     }
2712 
2713     private void processStartElement() throws IOException {
2714         // Create the element
2715         createElement();
2716 
2717         // Process the start tag
2718         current.processStartElement();
2719 
2720         // Set the root value
2721         if (root == null) {
2722             root = current.value;
2723         }
2724     }
2725 
2726     private void createElement() throws IOException {
2727         String prefix = xmlStreamReader.getPrefix();
2728         String localName = xmlStreamReader.getLocalName();
2729 
2730         if (prefix == null) {
2731             int i = localName.lastIndexOf('.');
2732 
2733             if (Character.isLowerCase(localName.charAt(i + 1))) {
2734                 String name = localName.substring(i + 1);
2735 
2736                 if (i == -1) {
2737                     // This is an instance property
2738                     if (loadListener != null) {
2739                         loadListener.beginPropertyElement(name, null);
2740                     }
2741 
2742                     current = new PropertyElement(name, null);
2743                 } else {
2744                     // This is a static property
2745                     Class<?> sourceType = getType(localName.substring(0, i));
2746 
2747                     if (sourceType != null) {
2748                         if (loadListener != null) {
2749                             loadListener.beginPropertyElement(name, sourceType);
2750                         }
2751 
2752                         current = new PropertyElement(name, sourceType);
2753                     } else if (staticLoad) {
2754                         // The source type was not recognized
2755                         if (loadListener != null) {
2756                             loadListener.beginUnknownStaticPropertyElement(localName);
2757                         }
2758 
2759                         current = new UnknownStaticPropertyElement();
2760                     } else {
2761                         throw constructLoadException(localName + " is not a valid property.");
2762                     }
2763                 }
2764             } else {
2765                 if (current == null && root != null) {
2766                     throw constructLoadException("Root value already specified.");
2767                 }
2768 
2769                 Class<?> type = getType(localName);
2770 
2771                 if (type != null) {
2772                     if (loadListener != null) {
2773                         loadListener.beginInstanceDeclarationElement(type);
2774                     }
2775 
2776                     current = new InstanceDeclarationElement(type);
2777                 } else if (staticLoad) {
2778                     // The type was not recognized
2779                     if (loadListener != null) {
2780                         loadListener.beginUnknownTypeElement(localName);
2781                     }
2782 
2783                     current = new UnknownTypeElement();
2784                 } else {
2785                     throw constructLoadException(localName + " is not a valid type.");
2786                 }
2787             }
2788         } else if (prefix.equals(FX_NAMESPACE_PREFIX)) {
2789             if (localName.equals(INCLUDE_TAG)) {
2790                 if (loadListener != null) {
2791                     loadListener.beginIncludeElement();
2792                 }
2793 
2794                 current = new IncludeElement();
2795             } else if (localName.equals(REFERENCE_TAG)) {
2796                 if (loadListener != null) {
2797                     loadListener.beginReferenceElement();
2798                 }
2799 
2800                 current = new ReferenceElement();
2801             } else if (localName.equals(COPY_TAG)) {
2802                 if (loadListener != null) {
2803                     loadListener.beginCopyElement();
2804                 }
2805 
2806                 current = new CopyElement();
2807             } else if (localName.equals(ROOT_TAG)) {
2808                 if (loadListener != null) {
2809                     loadListener.beginRootElement();
2810                 }
2811 
2812                 current = new RootElement();
2813             } else if (localName.equals(SCRIPT_TAG)) {
2814                 if (loadListener != null) {
2815                     loadListener.beginScriptElement();
2816                 }
2817 
2818                 current = new ScriptElement();
2819             } else if (localName.equals(DEFINE_TAG)) {
2820                 if (loadListener != null) {
2821                     loadListener.beginDefineElement();
2822                 }
2823 
2824                 current = new DefineElement();
2825             } else {
2826                 throw constructLoadException(prefix + ":" + localName + " is not a valid element.");
2827             }
2828         } else {
2829             throw constructLoadException("Unexpected namespace prefix: " + prefix + ".");
2830         }
2831     }
2832 
2833     private void processEndElement() throws IOException {
2834         current.processEndElement();
2835 
2836         if (loadListener != null) {
2837             loadListener.endElement(current.value);
2838         }
2839 
2840         // Move up the stack
2841         current = current.parent;
2842     }
2843 
2844     private void processCharacters() throws IOException {
2845         // Process the characters
2846         if (!xmlStreamReader.isWhiteSpace()) {
2847             current.processCharacters();
2848         }
2849     }
2850 
2851     private void importPackage(String name) throws LoadException {
2852         packages.add(name);
2853     }
2854 
2855     private void importClass(String name) throws LoadException {
2856         try {
2857             loadType(name, true);
2858         } catch (ClassNotFoundException exception) {
2859             throw constructLoadException(exception);
2860         }
2861     }
2862 
2863     private Class<?> getType(String name) throws LoadException {
2864         Class<?> type = null;
2865 
2866         if (Character.isLowerCase(name.charAt(0))) {
2867             // This is a fully-qualified class name
2868             try {
2869                 type = loadType(name, false);
2870             } catch (ClassNotFoundException exception) {
2871                 // No-op
2872             }
2873         } else {
2874             // This is an unqualified class name
2875             type = classes.get(name);
2876 
2877             if (type == null) {
2878                 // The class has not been loaded yet; look it up
2879                 for (String packageName : packages) {
2880                     try {
2881                         type = loadTypeForPackage(packageName, name);
2882                     } catch (ClassNotFoundException exception) {
2883                         // No-op
2884                     }
2885 
2886                     if (type != null) {
2887                         break;
2888                     }
2889                 }
2890 
2891                 if (type != null) {
2892                     classes.put(name, type);
2893                 }
2894             }
2895         }
2896 
2897         return type;
2898     }
2899 
2900     private Class<?> loadType(String name, boolean cache) throws ClassNotFoundException {
2901         int i = name.indexOf('.');
2902         int n = name.length();
2903         while (i != -1
2904             && i < n
2905             && Character.isLowerCase(name.charAt(i + 1))) {
2906             i = name.indexOf('.', i + 1);
2907         }
2908 
2909         if (i == -1 || i == n) {
2910             throw new ClassNotFoundException();
2911         }
2912 
2913         String packageName = name.substring(0, i);
2914         String className = name.substring(i + 1);
2915 
2916         Class<?> type = loadTypeForPackage(packageName, className);
2917 
2918         if (cache) {
2919             classes.put(className, type);
2920         }
2921 
2922         return type;
2923     }
2924 
2925     // TODO Rename to loadType() when deprecated static version is removed
2926     private Class<?> loadTypeForPackage(String packageName, String className) throws ClassNotFoundException {
2927         return getClassLoader().loadClass(packageName + "." + className.replace('.', '$'));
2928     }
2929 
2930     private static enum SupportedType {
2931         PARAMETERLESS {
2932 
2933             @Override
2934             protected boolean methodIsOfType(Method m) {
2935                 return m.getParameterTypes().length == 0;
2936             }
2937 
2938         },
2939         EVENT {
2940 
2941             @Override
2942             protected boolean methodIsOfType(Method m) {
2943                 return m.getParameterTypes().length == 1 &&
2944                         Event.class.isAssignableFrom(m.getParameterTypes()[0]);
2945             }
2946 
2947         },
2948         LIST_CHANGE_LISTENER {
2949 
2950             @Override
2951             protected boolean methodIsOfType(Method m) {
2952                 return m.getParameterTypes().length == 1 &&
2953                         m.getParameterTypes()[0].equals(ListChangeListener.Change.class);
2954             }
2955 
2956         },
2957         MAP_CHANGE_LISTENER {
2958 
2959             @Override
2960             protected boolean methodIsOfType(Method m) {
2961                 return m.getParameterTypes().length == 1 &&
2962                         m.getParameterTypes()[0].equals(MapChangeListener.Change.class);
2963             }
2964 
2965         },
2966         SET_CHANGE_LISTENER {
2967 
2968             @Override
2969             protected boolean methodIsOfType(Method m) {
2970                 return m.getParameterTypes().length == 1 &&
2971                         m.getParameterTypes()[0].equals(SetChangeListener.Change.class);
2972             }
2973 
2974         },
2975         PROPERTY_CHANGE_LISTENER {
2976 
2977             @Override
2978             protected boolean methodIsOfType(Method m) {
2979                 return m.getParameterTypes().length == 3 &&
2980                         ObservableValue.class.isAssignableFrom(m.getParameterTypes()[0])
2981                         && m.getParameterTypes()[1].equals(m.getParameterTypes()[2]);
2982             }
2983 
2984         };
2985 
2986         protected abstract boolean methodIsOfType(Method m);
2987     }
2988 
2989     private static SupportedType toSupportedType(Method m) {
2990         for (SupportedType t : SupportedType.values()) {
2991             if (t.methodIsOfType(m)) {
2992                 return t;
2993             }
2994         }
2995         return null;
2996     }
2997 
2998     private ScriptEngineManager getScriptEngineManager() {
2999         if (scriptEngineManager == null) {
3000             scriptEngineManager = new javax.script.ScriptEngineManager();
3001             scriptEngineManager.setBindings(new SimpleBindings(namespace));
3002         }
3003 
3004         return scriptEngineManager;
3005     }
3006 
3007     /**
3008      * Loads a type using the default class loader.
3009      *
3010      * @param packageName the package name of the class to load
3011      * @param className the name of the class to load
3012      *
3013      * @throws ClassNotFoundException if the specified class cannot be found
3014      * @return the class
3015      *
3016      * @deprecated
3017      * This method now delegates to {@link #getDefaultClassLoader()}.
3018      */
3019     @Deprecated
3020     public static Class<?> loadType(String packageName, String className) throws ClassNotFoundException {
3021         return loadType(packageName + "." + className.replace('.', '$'));
3022     }
3023 
3024     /**
3025      * Loads a type using the default class loader.
3026      *
3027      * @param className the name of the class to load
3028      * @throws ClassNotFoundException if the specified class cannot be found
3029      * @return the class
3030      *
3031      * @deprecated
3032      * This method now delegates to {@link #getDefaultClassLoader()}.
3033      */
3034     @Deprecated
3035     public static Class<?> loadType(String className) throws ClassNotFoundException {
3036         ReflectUtil.checkPackageAccess(className);
3037         return Class.forName(className, true, getDefaultClassLoader());
3038     }
3039 
3040     private static boolean needsClassLoaderPermissionCheck(ClassLoader from, ClassLoader to) {
3041         if (from == to) {
3042             return false;
3043         }
3044         if (from == null) {
3045             return false;
3046         }
3047         if (to == null) {
3048             return true;
3049         }
3050         ClassLoader acl = to;
3051         do {
3052             acl = acl.getParent();
3053             if (from == acl) {
3054                 return false;
3055             }
3056         } while (acl != null);
3057         return true;
3058     }
3059 
3060     private static ClassLoader getDefaultClassLoader(Class caller) {
3061         if (defaultClassLoader == null) {
3062             final SecurityManager sm = System.getSecurityManager();
3063             if (sm != null) {
3064                 final ClassLoader callerClassLoader = (caller != null) ?
3065                         caller.getClassLoader() :
3066                         null;
3067                 if (needsClassLoaderPermissionCheck(callerClassLoader, FXMLLoader.class.getClassLoader())) {
3068                     sm.checkPermission(GET_CLASSLOADER_PERMISSION);
3069                 }
3070             }
3071             return Thread.currentThread().getContextClassLoader();
3072         }
3073         return defaultClassLoader;
3074     }
3075 
3076     /**
3077      * Returns the default class loader.
3078      * @return the default class loader
3079      * @since JavaFX 2.1
3080      */
3081     public static ClassLoader getDefaultClassLoader() {
3082         final SecurityManager sm = System.getSecurityManager();
3083         final Class caller = (sm != null) ?
3084                 walker.getCallerClass() :
3085                 null;
3086         return getDefaultClassLoader(caller);
3087     }
3088 
3089     /**
3090      * Sets the default class loader.
3091      *
3092      * @param defaultClassLoader
3093      * The default class loader to use when loading classes.
3094      * @since JavaFX 2.1
3095      */
3096     public static void setDefaultClassLoader(ClassLoader defaultClassLoader) {
3097         if (defaultClassLoader == null) {
3098             throw new NullPointerException();
3099         }
3100         final SecurityManager sm = System.getSecurityManager();
3101         if (sm != null) {
3102             sm.checkPermission(MODIFY_FXML_CLASS_LOADER_PERMISSION);
3103         }
3104 
3105         FXMLLoader.defaultClassLoader = defaultClassLoader;
3106     }
3107 
3108     /**
3109      * Loads an object hierarchy from a FXML document.
3110      *
3111      * @param <T> the type of the root object
3112      * @param location the location used to resolve relative path attribute values
3113      *
3114      * @throws IOException if an error occurs during loading
3115      * @return the loaded object hierarchy
3116      */
3117     public static <T> T load(URL location) throws IOException {
3118         return loadImpl(location, (System.getSecurityManager() != null)
3119                                       ? walker.getCallerClass()
3120                                       : null);
3121     }
3122 
3123     private static <T> T loadImpl(URL location, Class<?> callerClass)
3124             throws IOException {
3125         return loadImpl(location, null, callerClass);
3126     }
3127 
3128     /**
3129      * Loads an object hierarchy from a FXML document.
3130      *
3131      * @param <T> the type of the root object
3132      * @param location the location used to resolve relative path attribute values
3133      * @param resources the resources used to resolve resource key attribute values
3134      *
3135      * @throws IOException if an error occurs during loading
3136      * @return the loaded object hierarchy
3137      */
3138     public static <T> T load(URL location, ResourceBundle resources)
3139                                      throws IOException {
3140         return loadImpl(location, resources,
3141                         (System.getSecurityManager() != null)
3142                             ? walker.getCallerClass()
3143                             : null);
3144     }
3145 
3146     private static <T> T loadImpl(URL location, ResourceBundle resources,
3147                                   Class<?> callerClass) throws IOException {
3148         return loadImpl(location, resources,  null,
3149                         callerClass);
3150     }
3151 
3152     /**
3153      * Loads an object hierarchy from a FXML document.
3154      *
3155      * @param <T> the type of the root object
3156      * @param location the location used to resolve relative path attribute values
3157      * @param resources the resources used to resolve resource key attribute values
3158      * @param builderFactory the builder factory used to load the document
3159      *
3160      * @throws IOException if an error occurs during loading
3161      * @return the loaded object hierarchy
3162      */
3163     public static <T> T load(URL location, ResourceBundle resources,
3164                              BuilderFactory builderFactory)
3165                                      throws IOException {
3166         return loadImpl(location, resources, builderFactory,
3167                         (System.getSecurityManager() != null)
3168                             ? walker.getCallerClass()
3169                             : null);
3170     }
3171 
3172     private static <T> T loadImpl(URL location, ResourceBundle resources,
3173                                   BuilderFactory builderFactory,
3174                                   Class<?> callerClass) throws IOException {
3175         return loadImpl(location, resources, builderFactory, null, callerClass);
3176     }
3177 
3178     /**
3179      * Loads an object hierarchy from a FXML document.
3180      *
3181      * @param <T> the type of the root object
3182      * @param location the location used to resolve relative path attribute values
3183      * @param resources the resources used to resolve resource key attribute values
3184      * @param builderFactory the builder factory used when loading the document
3185      * @param controllerFactory the controller factory used when loading the document
3186      *
3187      * @throws IOException if an error occurs during loading
3188      * @return the loaded object hierarchy
3189      *
3190      * @since JavaFX 2.1
3191      */
3192     public static <T> T load(URL location, ResourceBundle resources,
3193                              BuilderFactory builderFactory,
3194                              Callback<Class<?>, Object> controllerFactory)
3195                                      throws IOException {
3196         return loadImpl(location, resources, builderFactory, controllerFactory,
3197                         (System.getSecurityManager() != null)
3198                             ? walker.getCallerClass()
3199                             : null);
3200     }
3201 
3202     private static <T> T loadImpl(URL location, ResourceBundle resources,
3203                                   BuilderFactory builderFactory,
3204                                   Callback<Class<?>, Object> controllerFactory,
3205                                   Class<?> callerClass) throws IOException {
3206         return loadImpl(location, resources, builderFactory, controllerFactory,
3207                         Charset.forName(DEFAULT_CHARSET_NAME), callerClass);
3208     }
3209 
3210     /**
3211      * Loads an object hierarchy from a FXML document.
3212      *
3213      * @param <T> the type of the root object
3214      * @param location the location used to resolve relative path attribute values
3215      * @param resources the resources used to resolve resource key attribute values
3216      * @param builderFactory the builder factory used when loading the document
3217      * @param controllerFactory the controller factory used when loading the document
3218      * @param charset the character set used when loading the document
3219      *
3220      * @throws IOException if an error occurs during loading
3221      * @return the loaded object hierarchy
3222      *
3223      * @since JavaFX 2.1
3224      */
3225     public static <T> T load(URL location, ResourceBundle resources,
3226                              BuilderFactory builderFactory,
3227                              Callback<Class<?>, Object> controllerFactory,
3228                              Charset charset) throws IOException {
3229         return loadImpl(location, resources, builderFactory, controllerFactory,
3230                         charset,
3231                         (System.getSecurityManager() != null)
3232                             ? walker.getCallerClass()
3233                             : null);
3234     }
3235 
3236     private static <T> T loadImpl(URL location, ResourceBundle resources,
3237                                   BuilderFactory builderFactory,
3238                                   Callback<Class<?>, Object> controllerFactory,
3239                                   Charset charset, Class<?> callerClass)
3240                                           throws IOException {
3241         if (location == null) {
3242             throw new NullPointerException("Location is required.");
3243         }
3244 
3245         FXMLLoader fxmlLoader =
3246                 new FXMLLoader(location, resources, builderFactory,
3247                                controllerFactory, charset);
3248 
3249         return fxmlLoader.<T>loadImpl(callerClass);
3250     }
3251 
3252     /**
3253      * Utility method for comparing two JavaFX version strings (such as 2.2.5, 8.0.0-ea)
3254      * @param rtVer String representation of JavaFX runtime version, including - or _ appendix
3255      * @param nsVer String representation of JavaFX version to compare against runtime version
3256      * @return number &lt; 0 if runtime version is lower, 0 when both versions are the same,
3257      *          number &gt; 0 if runtime is higher version
3258      */
3259     static int compareJFXVersions(String rtVer, String nsVer) {
3260 
3261         int retVal = 0;
3262 
3263         if (rtVer == null || "".equals(rtVer) ||
3264             nsVer == null || "".equals(nsVer)) {
3265             return retVal;
3266         }
3267 
3268         if (rtVer.equals(nsVer)) {
3269             return retVal;
3270         }
3271 
3272         // version string can contain '-'
3273         int dashIndex = rtVer.indexOf("-");
3274         if (dashIndex > 0) {
3275             rtVer = rtVer.substring(0, dashIndex);
3276         }
3277 
3278         // or "_"
3279         int underIndex = rtVer.indexOf("_");
3280         if (underIndex > 0) {
3281             rtVer = rtVer.substring(0, underIndex);
3282         }
3283 
3284         // do not try to compare if the string is not valid version format
3285         if (!Pattern.matches("^(\\d+)(\\.\\d+)*$", rtVer) ||
3286             !Pattern.matches("^(\\d+)(\\.\\d+)*$", nsVer)) {
3287             return retVal;
3288         }
3289 
3290         StringTokenizer nsVerTokenizer = new StringTokenizer(nsVer, ".");
3291         StringTokenizer rtVerTokenizer = new StringTokenizer(rtVer, ".");
3292         int nsDigit = 0, rtDigit = 0;
3293         boolean rtVerEnd = false;
3294 
3295         while (nsVerTokenizer.hasMoreTokens() && retVal == 0) {
3296             nsDigit = Integer.parseInt(nsVerTokenizer.nextToken());
3297             if (rtVerTokenizer.hasMoreTokens()) {
3298                 rtDigit = Integer.parseInt(rtVerTokenizer.nextToken());
3299                 retVal = rtDigit - nsDigit;
3300             } else {
3301                 rtVerEnd = true;
3302                 break;
3303             }
3304         }
3305 
3306         if (rtVerTokenizer.hasMoreTokens() && retVal == 0) {
3307             rtDigit = Integer.parseInt(rtVerTokenizer.nextToken());
3308             if (rtDigit > 0) {
3309                 retVal = 1;
3310             }
3311         }
3312 
3313         if (rtVerEnd) {
3314             if (nsDigit > 0) {
3315                 retVal = -1;
3316             } else {
3317                 while (nsVerTokenizer.hasMoreTokens()) {
3318                     nsDigit = Integer.parseInt(nsVerTokenizer.nextToken());
3319                     if (nsDigit > 0) {
3320                         retVal = -1;
3321                         break;
3322                     }
3323                 }
3324             }
3325         }
3326 
3327         return retVal;
3328     }
3329 
3330     private static void checkClassLoaderPermission() {
3331         final SecurityManager securityManager = System.getSecurityManager();
3332         if (securityManager != null) {
3333             securityManager.checkPermission(MODIFY_FXML_CLASS_LOADER_PERMISSION);
3334         }
3335     }
3336 
3337     private final ControllerAccessor controllerAccessor =
3338             new ControllerAccessor();
3339 
3340     private static final class ControllerAccessor {
3341         private static final int PUBLIC = 1;
3342         private static final int PROTECTED = 2;
3343         private static final int PACKAGE = 4;
3344         private static final int PRIVATE = 8;
3345         private static final int INITIAL_CLASS_ACCESS =
3346                 PUBLIC | PROTECTED | PACKAGE | PRIVATE;
3347         private static final int INITIAL_MEMBER_ACCESS =
3348                 PUBLIC | PROTECTED | PACKAGE | PRIVATE;
3349 
3350         private static final int METHODS = 0;
3351         private static final int FIELDS = 1;
3352 
3353         private Object controller;
3354         private ClassLoader callerClassLoader;
3355 
3356         private Map<String, List<Field>> controllerFields;
3357         private Map<SupportedType, Map<String, Method>> controllerMethods;
3358 
3359         void setController(final Object controller) {
3360             if (this.controller != controller) {
3361                 this.controller = controller;
3362                 reset();
3363             }
3364         }
3365 
3366         void setCallerClass(final Class<?> callerClass) {
3367             final ClassLoader newCallerClassLoader =
3368                     (callerClass != null) ? callerClass.getClassLoader()
3369                                           : null;
3370             if (callerClassLoader != newCallerClassLoader) {
3371                 callerClassLoader = newCallerClassLoader;
3372                 reset();
3373             }
3374         }
3375 
3376         void reset() {
3377             controllerFields = null;
3378             controllerMethods = null;
3379         }
3380 
3381         Map<String, List<Field>> getControllerFields() {
3382             if (controllerFields == null) {
3383                 controllerFields = new HashMap<>();
3384 
3385                 if (callerClassLoader == null) {
3386                     // allow null class loader only with permission check
3387                     checkClassLoaderPermission();
3388                 }
3389 
3390                 addAccessibleMembers(controller.getClass(),
3391                                      INITIAL_CLASS_ACCESS,
3392                                      INITIAL_MEMBER_ACCESS,
3393                                      FIELDS);
3394             }
3395 
3396             return controllerFields;
3397         }
3398 
3399         Map<SupportedType, Map<String, Method>> getControllerMethods() {
3400             if (controllerMethods == null) {
3401                 controllerMethods = new EnumMap<>(SupportedType.class);
3402                 for (SupportedType t: SupportedType.values()) {
3403                     controllerMethods.put(t, new HashMap<String, Method>());
3404                 }
3405 
3406                 if (callerClassLoader == null) {
3407                     // allow null class loader only with permission check
3408                     checkClassLoaderPermission();
3409                 }
3410 
3411                 addAccessibleMembers(controller.getClass(),
3412                                      INITIAL_CLASS_ACCESS,
3413                                      INITIAL_MEMBER_ACCESS,
3414                                      METHODS);
3415             }
3416 
3417             return controllerMethods;
3418         }
3419 
3420         private void addAccessibleMembers(final Class<?> type,
3421                                           final int prevAllowedClassAccess,
3422                                           final int prevAllowedMemberAccess,
3423                                           final int membersType) {
3424             if (type == Object.class) {
3425                 return;
3426             }
3427 
3428             int allowedClassAccess = prevAllowedClassAccess;
3429             int allowedMemberAccess = prevAllowedMemberAccess;
3430             if ((callerClassLoader != null)
3431                     && (type.getClassLoader() != callerClassLoader)) {
3432                 // restrict further access
3433                 allowedClassAccess &= PUBLIC;
3434                 allowedMemberAccess &= PUBLIC;
3435             }
3436 
3437             final int classAccess = getAccess(type.getModifiers());
3438             if ((classAccess & allowedClassAccess) == 0) {
3439                 // we are done
3440                 return;
3441             }
3442 
3443             ReflectUtil.checkPackageAccess(type);
3444 
3445             addAccessibleMembers(type.getSuperclass(),
3446                                  allowedClassAccess,
3447                                  allowedMemberAccess,
3448                                  membersType);
3449 
3450             final int finalAllowedMemberAccess = allowedMemberAccess;
3451             AccessController.doPrivileged(
3452                     new PrivilegedAction<Void>() {
3453                         @Override
3454                         public Void run() {
3455                             if (membersType == FIELDS) {
3456                                 addAccessibleFields(type,
3457                                                     finalAllowedMemberAccess);
3458                             } else {
3459                                 addAccessibleMethods(type,
3460                                                      finalAllowedMemberAccess);
3461                             }
3462 
3463                             return null;
3464                         }
3465                     });
3466         }
3467 
3468         private void addAccessibleFields(final Class<?> type,
3469                                          final int allowedMemberAccess) {
3470             final boolean isPublicType = Modifier.isPublic(type.getModifiers());
3471 
3472             final Field[] fields = type.getDeclaredFields();
3473             for (int i = 0; i < fields.length; ++i) {
3474                 final Field field = fields[i];
3475                 final int memberModifiers = field.getModifiers();
3476 
3477                 if (((memberModifiers & (Modifier.STATIC
3478                                              | Modifier.FINAL)) != 0)
3479                         || ((getAccess(memberModifiers) & allowedMemberAccess)
3480                                 == 0)) {
3481                     continue;
3482                 }
3483 
3484                 if (!isPublicType || !Modifier.isPublic(memberModifiers)) {
3485                     if (field.getAnnotation(FXML.class) == null) {
3486                         // no fxml annotation on a non-public field
3487                         continue;
3488                     }
3489 
3490                     // Ensure that the field is accessible
3491                     field.setAccessible(true);
3492                 }
3493 
3494                 List<Field> list = controllerFields.get(field.getName());
3495                 if (list == null) {
3496                     list = new ArrayList<>(1);
3497                     controllerFields.put(field.getName(), list);
3498                 }
3499                 list.add(field);
3500 
3501             }
3502         }
3503 
3504         private void addAccessibleMethods(final Class<?> type,
3505                                           final int allowedMemberAccess) {
3506             final boolean isPublicType = Modifier.isPublic(type.getModifiers());
3507 
3508             final Method[] methods = type.getDeclaredMethods();
3509             for (int i = 0; i < methods.length; ++i) {
3510                 final Method method = methods[i];
3511                 final int memberModifiers = method.getModifiers();
3512 
3513                 if (((memberModifiers & (Modifier.STATIC
3514                                              | Modifier.NATIVE)) != 0)
3515                         || ((getAccess(memberModifiers) & allowedMemberAccess)
3516                                 == 0)) {
3517                     continue;
3518                 }
3519 
3520                 if (!isPublicType || !Modifier.isPublic(memberModifiers)) {
3521                     if (method.getAnnotation(FXML.class) == null) {
3522                         // no fxml annotation on a non-public method
3523                         continue;
3524                     }
3525 
3526                     // Ensure that the method is accessible
3527                     method.setAccessible(true);
3528                 }
3529 
3530                 // Add this method to the map if:
3531                 // a) it is the initialize() method, or
3532                 // b) it takes a single event argument, or
3533                 // c) it takes no arguments and a handler with this
3534                 //    name has not already been defined
3535                 final String methodName = method.getName();
3536                 final SupportedType convertedType;
3537 
3538                 if ((convertedType = toSupportedType(method)) != null) {
3539                     controllerMethods.get(convertedType)
3540                                      .put(methodName, method);
3541                 }
3542             }
3543         }
3544 
3545         private static int getAccess(final int fullModifiers) {
3546             final int untransformedAccess =
3547                     fullModifiers & (Modifier.PRIVATE | Modifier.PROTECTED
3548                                                       | Modifier.PUBLIC);
3549 
3550             switch (untransformedAccess) {
3551                 case Modifier.PUBLIC:
3552                     return PUBLIC;
3553 
3554                 case Modifier.PROTECTED:
3555                     return PROTECTED;
3556 
3557                 case Modifier.PRIVATE:
3558                     return PRIVATE;
3559 
3560                 default:
3561                     return PACKAGE;
3562             }
3563         }
3564     }
3565 }