All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javafx.fxml.FXMLLoader Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javafx.fxml;

import com.sun.javafx.Logging;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.regex.Pattern;

import javafx.beans.DefaultProperty;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.util.Builder;
import javafx.util.BuilderFactory;
import javafx.util.Callback;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.util.StreamReaderDelegate;

import com.sun.javafx.beans.IDProperty;
import com.sun.javafx.fxml.BeanAdapter;
import com.sun.javafx.fxml.LoadListener;
import com.sun.javafx.fxml.ObservableListChangeEvent;
import com.sun.javafx.fxml.ObservableMapChangeEvent;
import com.sun.javafx.fxml.PropertyChangeEvent;
import com.sun.javafx.fxml.PropertyNotFoundException;
import com.sun.javafx.fxml.expression.Expression;
import com.sun.javafx.fxml.expression.ExpressionValue;
import com.sun.javafx.fxml.expression.KeyPath;
import java.net.MalformedURLException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Locale;
import java.util.StringTokenizer;
import sun.reflect.misc.ConstructorUtil;
import sun.reflect.misc.FieldUtil;
import sun.reflect.misc.MethodUtil;
import sun.reflect.misc.ReflectUtil;

/**
 * Loads an object hierarchy from an XML document.
 * @since JavaFX 2.0
 */
public class FXMLLoader {
    // Abstract base class for elements
    private abstract class Element {
        public final Element parent;
        public final int lineNumber;

        public Object value = null;
        private BeanAdapter valueAdapter = null;

        public final LinkedList eventHandlerAttributes = new LinkedList();
        public final LinkedList instancePropertyAttributes = new LinkedList();
        public final LinkedList staticPropertyAttributes = new LinkedList();
        public final LinkedList staticPropertyElements = new LinkedList();

        public Element() {
            parent = current;
            lineNumber = getLineNumber();
        }

        public boolean isCollection() {
            // Return true if value is a list, or if the value's type defines
            // a default property that is a list
            boolean collection;
            if (value instanceof List) {
                collection = true;
            } else {
                Class type = value.getClass();
                DefaultProperty defaultProperty = type.getAnnotation(DefaultProperty.class);

                if (defaultProperty != null) {
                    collection = getProperties().get(defaultProperty.value()) instanceof List;
                } else {
                    collection = false;
                }
            }

            return collection;
        }

        @SuppressWarnings("unchecked")
        public void add(Object element) throws LoadException {
            // If value is a list, add element to it; otherwise, get the value
            // of the default property, which is assumed to be a list and add
            // to that (coerce to the appropriate type)
            List list;
            if (value instanceof List) {
                list = (List)value;
            } else {
                Class type = value.getClass();
                DefaultProperty defaultProperty = type.getAnnotation(DefaultProperty.class);
                String defaultPropertyName = defaultProperty.value();

                // Get the list value
                list = (List)getProperties().get(defaultPropertyName);

                // Coerce the element to the list item type
                if (!Map.class.isAssignableFrom(type)) {
                    Type listType = getValueAdapter().getGenericType(defaultPropertyName);
                    element = BeanAdapter.coerce(element, BeanAdapter.getListItemType(listType));
                }
            }

            list.add(element);
        }

        public void set(Object value) throws LoadException {
            if (this.value == null) {
                throw new LoadException("Cannot set value on this element.");
            }

            // Apply value to this element's properties
            Class type = this.value.getClass();
            DefaultProperty defaultProperty = type.getAnnotation(DefaultProperty.class);
            if (defaultProperty == null) {
                throw new LoadException("Element does not define a default property.");
            }

            getProperties().put(defaultProperty.value(), value);
        }

        public void updateValue(Object value) {
            this.value = value;
            valueAdapter = null;
        }

        public boolean isTyped() {
            return !(value instanceof Map);
        }

        public BeanAdapter getValueAdapter() {
            if (valueAdapter == null) {
                valueAdapter = new BeanAdapter(value);
            }

            return valueAdapter;
        }

        @SuppressWarnings("unchecked")
        public Map getProperties() {
            return (isTyped()) ? getValueAdapter() : (Map)value;
        }

        public void processStartElement() throws IOException {
            for (int i = 0, n = xmlStreamReader.getAttributeCount(); i < n; i++) {
                String prefix = xmlStreamReader.getAttributePrefix(i);
                String localName = xmlStreamReader.getAttributeLocalName(i);
                String value = xmlStreamReader.getAttributeValue(i);

                if (loadListener != null
                    && prefix != null
                    && prefix.equals(FX_NAMESPACE_PREFIX)) {
                    loadListener.readInternalAttribute(prefix + ":" + localName, value);
                }

                processAttribute(prefix, localName, value);
            }
        }

        public void processEndElement() throws IOException {
            // No-op
        }

        public void processCharacters() throws IOException {
            throw new LoadException("Unexpected characters in input stream.");
        }

        public void processInstancePropertyAttributes() throws IOException {
            if (instancePropertyAttributes.size() > 0) {
                for (Attribute attribute : instancePropertyAttributes) {
                    processPropertyAttribute(attribute);
                }
            }
        }

        public void processAttribute(String prefix, String localName, String value)
            throws IOException{
            if (prefix == null) {
                // Add the attribute to the appropriate list
                if (localName.startsWith(EVENT_HANDLER_PREFIX)) {
                    if (loadListener != null) {
                        loadListener.readEventHandlerAttribute(localName, value);
                    }

                    eventHandlerAttributes.add(new Attribute(localName, null, value));
                } else {
                    int i = localName.lastIndexOf('.');

                    if (i == -1) {
                        // The attribute represents an instance property
                        if (loadListener != null) {
                            loadListener.readPropertyAttribute(localName, null, value);
                        }

                        instancePropertyAttributes.add(new Attribute(localName, null, value));
                    } else {
                        // The attribute represents a static property
                        String name = localName.substring(i + 1);
                        Class sourceType = getType(localName.substring(0, i));

                        if (sourceType != null) {
                            if (loadListener != null) {
                                loadListener.readPropertyAttribute(name, sourceType, value);
                            }

                            staticPropertyAttributes.add(new Attribute(name, sourceType, value));
                        } else if (staticLoad) {
                            if (loadListener != null) {
                                loadListener.readUnknownStaticPropertyAttribute(localName, value);
                            }
                        } else {
                            throw new LoadException(localName + " is not a valid attribute.");
                        }
                    }

                }
            } else {
                throw new LoadException(prefix + ":" + localName
                    + " is not a valid attribute.");
            }
        }

        @SuppressWarnings("unchecked")
        public void processPropertyAttribute(Attribute attribute) throws IOException {
            String value = attribute.value;
            if (isBindingExpression(value)) {
                // Resolve the expression
                Expression expression;

                if (attribute.sourceType != null) {
                    throw new LoadException("Cannot bind to static property.");
                }

                if (!isTyped()) {
                    throw new LoadException("Cannot bind to untyped object.");
                }

                // TODO We may want to identify binding properties in processAttribute()
                // and apply them after build() has been called
                if (this.value instanceof Builder) {
                    throw new LoadException("Cannot bind to builder property.");
                }

                value = value.substring(BINDING_EXPRESSION_PREFIX.length(),
                        value.length() - 1);
                expression = Expression.valueOf(value);

                // Create the binding
                BeanAdapter targetAdapter = new BeanAdapter(this.value);
                ObservableValue propertyModel = targetAdapter.getPropertyModel(attribute.name);
                Class type = targetAdapter.getType(attribute.name);

                if (propertyModel instanceof Property) {
                    ((Property)propertyModel).bind(new ExpressionValue(namespace, expression, type));
                }
            } else if (isBidirectionalBindingExpression(value)) {
                throw new UnsupportedOperationException("This feature is not currently enabled.");
            } else {
                processValue(attribute.sourceType, attribute.name, value);
            }
        }

        private boolean isBindingExpression(String aValue) {
            return aValue.startsWith(BINDING_EXPRESSION_PREFIX)
                   && aValue.endsWith(BINDING_EXPRESSION_SUFFIX);
        }

        private boolean isBidirectionalBindingExpression(String aValue) {
            return aValue.startsWith(BI_DIRECTIONAL_BINDING_PREFIX);
        }

        private boolean processValue(Class sourceType, String propertyName, String aValue)
            throws LoadException {

            boolean processed = false;
                //process list or array first
                if (sourceType == null && isTyped()) {
                    BeanAdapter valueAdapter = getValueAdapter();
                    Class type = valueAdapter.getType(propertyName);

                    if (type == null) {
                        throw new PropertyNotFoundException("Property \"" + propertyName
                            + "\" does not exist" + " or is read-only.");
                    }

                    if (List.class.isAssignableFrom(type)
                        && valueAdapter.isReadOnly(propertyName)) {
                        populateListFromString(valueAdapter, propertyName, aValue);
                        processed = true;
                    } else if (type.isArray()) {
                        applyProperty(propertyName, sourceType,
                                populateArrayFromString(type, aValue));
                        processed = true;
                    }
                }
                if (!processed) {
                    applyProperty(propertyName, sourceType, resolvePrefixedValue(aValue));
                    processed = true;
                }
                return processed;
        }

        /**
         * Resolves value prefixed with RELATIVE_PATH_PREFIX and RESOURCE_KEY_PREFIX.
         */
        private Object resolvePrefixedValue(String aValue) throws LoadException {
            if (aValue.startsWith(ESCAPE_PREFIX)) {
                aValue = aValue.substring(ESCAPE_PREFIX.length());

                if (aValue.length() == 0
                    || !(aValue.startsWith(ESCAPE_PREFIX)
                        || aValue.startsWith(RELATIVE_PATH_PREFIX)
                        || aValue.startsWith(RESOURCE_KEY_PREFIX)
                        || aValue.startsWith(EXPRESSION_PREFIX)
                        || aValue.startsWith(BI_DIRECTIONAL_BINDING_PREFIX))) {
                    throw new LoadException("Invalid escape sequence.");
                }
                return aValue;
            } else if (aValue.startsWith(RELATIVE_PATH_PREFIX)) {
                aValue = aValue.substring(RELATIVE_PATH_PREFIX.length());
                if (aValue.length() == 0) {
                    throw new LoadException("Missing relative path.");
                }
                if (aValue.startsWith(RELATIVE_PATH_PREFIX)) {
                    // The prefix was escaped
                    warnDeprecatedEscapeSequence(RELATIVE_PATH_PREFIX);
                    return aValue;
                } else {
                    try {
                        return (aValue.charAt(0) == '/') ?
                                classLoader.getResource(aValue.substring(1)).toString() :
                                new URL(FXMLLoader.this.location, aValue).toString();
                    } catch (MalformedURLException e) {
                        System.err.println(FXMLLoader.this.location + "/" + aValue);
                    }
                }
            } else if (aValue.startsWith(RESOURCE_KEY_PREFIX)) {
                aValue = aValue.substring(RESOURCE_KEY_PREFIX.length());
                if (aValue.length() == 0) {
                    throw new LoadException("Missing resource key.");
                }
                if (aValue.startsWith(RESOURCE_KEY_PREFIX)) {
                    // The prefix was escaped
                    warnDeprecatedEscapeSequence(RESOURCE_KEY_PREFIX);
                    return aValue;
                } else {
                    // Resolve the resource value
                    if (resources == null) {
                        throw new LoadException("No resources specified.");
                    }
                    if (!resources.containsKey(aValue)) {
                        throw new LoadException("Resource \"" + aValue + "\" not found.");
                    }

                    return resources.getString(aValue);
                }
            } else if (aValue.startsWith(EXPRESSION_PREFIX)) {
                aValue = aValue.substring(EXPRESSION_PREFIX.length());
                if (aValue.length() == 0) {
                    throw new LoadException("Missing expression.");
                }
                if (aValue.startsWith(EXPRESSION_PREFIX)) {
                    // The prefix was escaped
                    warnDeprecatedEscapeSequence(EXPRESSION_PREFIX);
                    return aValue;
                } else if (aValue.equals(NULL_KEYWORD)) {
                    // The attribute value is null
                    return null;
                }
                return Expression.get(namespace, KeyPath.parse(aValue));
            }
            return aValue;
        }

        /**
         * Creates an array of given type and populates it with values from
         * a string where tokens are separated by ARRAY_COMPONENT_DELIMITER.
         * If token is prefixed with RELATIVE_PATH_PREFIX a value added to
         * the array becomes relative to document location.
         */
        private Object populateArrayFromString(
                Classtype,
                String stringValue) throws LoadException {

            Object propertyValue = null;
            // Split the string and set the values as an array
            Class componentType = type.getComponentType();

            if (stringValue.length() > 0) {
                String[] values = stringValue.split(ARRAY_COMPONENT_DELIMITER);
                propertyValue = Array.newInstance(componentType, values.length);
                for (int i = 0; i < values.length; i++) {
                    Array.set(propertyValue, i,
                            BeanAdapter.coerce(resolvePrefixedValue(values[i].trim()),
                            type.getComponentType()));
                }
            } else {
                propertyValue = Array.newInstance(componentType, 0);
            }
            return propertyValue;
        }

        /**
         * Populates list with values from a string where tokens are separated
         * by ARRAY_COMPONENT_DELIMITER. If token is prefixed with RELATIVE_PATH_PREFIX
         * a value added to the list becomes relative to document location.
         */
        private void populateListFromString(
                BeanAdapter valueAdapter,
                String listPropertyName,
                String stringValue) throws LoadException {
            // Split the string and add the values to the list
            List list = (List)valueAdapter.get(listPropertyName);
            Type listType = valueAdapter.getGenericType(listPropertyName);
            Type itemType = (Class)BeanAdapter.getGenericListItemType(listType);

            if (itemType instanceof ParameterizedType) {
                itemType = ((ParameterizedType)itemType).getRawType();
            }

            if (stringValue.length() > 0) {
                String[] values = stringValue.split(ARRAY_COMPONENT_DELIMITER);

                for (String aValue: values) {
                    aValue = aValue.trim();
                    list.add(
                            BeanAdapter.coerce(resolvePrefixedValue(aValue),
                                               (Class)itemType));
                }
            }
        }

        public void warnDeprecatedEscapeSequence(String prefix) {
            System.err.println(prefix + prefix + " is a deprecated escape sequence. "
                + "Please use \\" + prefix + " instead.");
        }

        public void applyProperty(String name, Class sourceType, Object value) {
            if (sourceType == null) {
                getProperties().put(name, value);
            } else {
                BeanAdapter.put(this.value, sourceType, name, value);
            }
        }

        public void processEventHandlerAttributes() throws LoadException {
            if (eventHandlerAttributes.size() > 0 && !staticLoad) {
                for (Attribute attribute : eventHandlerAttributes) {
                    EventHandler eventHandler = null;

                    String attrValue = attribute.value;

                    if (attrValue.startsWith(CONTROLLER_METHOD_PREFIX)) {
                        attrValue = attrValue.substring(CONTROLLER_METHOD_PREFIX.length());

                        if (!attrValue.startsWith(CONTROLLER_METHOD_PREFIX)) {
                            if (attrValue.length() == 0) {
                                throw new LoadException("Missing controller method.");
                            }

                            if (controller == null) {
                                throw new LoadException("No controller specified.");
                            }

                            Method method = getControllerMethods().get(attrValue);

                            if (method == null) {
                                throw new LoadException("Controller method \"" + attrValue + "\" not found.");
                            }

                            eventHandler = new ControllerMethodEventHandler(controller, method);
                        }

                    } else if (attrValue.startsWith(EXPRESSION_PREFIX)) {
                        attrValue = attrValue.substring(EXPRESSION_PREFIX.length());

                        if (attrValue.length() == 0) {
                            throw new LoadException("Missing expression reference.");
                        }

                        Object expression = Expression.get(namespace, KeyPath.parse(attrValue));
                        if (expression instanceof EventHandler) {
                            eventHandler = (EventHandler) expression;
                        }

                    }

                    if (eventHandler == null) {
                        if (attrValue.length() == 0 || scriptEngine == null) {
                            throw new LoadException("Error resolving "  + attribute.name + "='" + attribute.value
                                + "', either the event handler is not in the Namespace or there is an error in the script.");
                        }

                        eventHandler = new ScriptEventHandler(attrValue, scriptEngine);
                    }

                    // Add the handler
                    if (eventHandler != null){
                        addEventHandler(attribute, eventHandler);
                    }
                }
            }
        }

        @SuppressWarnings("unchecked")
        private void addEventHandler(Attribute attribute, EventHandler eventHandler)
            throws LoadException {
            if (attribute.name.endsWith(CHANGE_EVENT_HANDLER_SUFFIX)) {
                int i = EVENT_HANDLER_PREFIX.length();
                int j = attribute.name.length() - CHANGE_EVENT_HANDLER_SUFFIX.length();

                if (i == j) {
                    if (value instanceof ObservableList) {
                        ObservableList list = (ObservableList)value;
                        list.addListener(new ObservableListChangeAdapter(list,
                            (EventHandler>)eventHandler));
                    } else if (value instanceof ObservableMap) {
                        ObservableMap map = (ObservableMap)value;
                        map.addListener(new ObservableMapChangeAdapter(map,
                            (EventHandler>)eventHandler));
                    } else {
                        throw new LoadException("Invalid event source.");
                    }
                } else {
                    String key = Character.toLowerCase(attribute.name.charAt(i))
                        + attribute.name.substring(i + 1, j);

                    ObservableValue propertyModel = getValueAdapter().getPropertyModel(key);
                    if (propertyModel == null) {
                        throw new LoadException(value.getClass().getName() + " does not define"
                                + " a property model for \"" + key + "\".");
                    }

                    propertyModel.addListener(new PropertyChangeAdapter(value,
                        (EventHandler>)eventHandler));
                }
            } else {
                getValueAdapter().put(attribute.name, eventHandler);
            }
        }
    }

    // Element representing a value
    private abstract class ValueElement extends Element {
        public String fx_id = null;

        @Override
        public void processStartElement() throws IOException {
            super.processStartElement();

            updateValue(constructValue());

            if (value instanceof Builder) {
                processInstancePropertyAttributes();
            } else {
                processValue();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void processEndElement() throws IOException {
            super.processEndElement();

            // Build the value, if necessary
            if (value instanceof Builder) {
                Builder builder = (Builder)value;
                updateValue(builder.build());

                processValue();
            } else {
                processInstancePropertyAttributes();
            }

            processEventHandlerAttributes();

            // Process static property attributes
            if (staticPropertyAttributes.size() > 0) {
                for (Attribute attribute : staticPropertyAttributes) {
                    processPropertyAttribute(attribute);
                }
            }

            // Process static property elements
            if (staticPropertyElements.size() > 0) {
                for (PropertyElement element : staticPropertyElements) {
                    BeanAdapter.put(value, element.sourceType, element.name, element.value);
                }
            }

            if (parent != null) {
                if (parent.isCollection()) {
                    parent.add(value);
                } else {
                    parent.set(value);
                }
            }
        }

        private Object getListValue(Element parent, String listPropertyName, Object value) {
            // If possible, coerce the value to the list item type
            if (parent.isTyped()) {
                Type listType = parent.getValueAdapter().getGenericType(listPropertyName);

                if (listType != null) {
                    Type itemType = BeanAdapter.getGenericListItemType(listType);

                    if (itemType instanceof ParameterizedType) {
                        itemType = ((ParameterizedType)itemType).getRawType();
                    }

                    value = BeanAdapter.coerce(value, (Class)itemType);
                }
            }

            return value;
        }

        private void processValue() throws LoadException {
            // If this is the root element, update the value
            if (parent == null) {
                root = value;

                // checking version of fx namespace - throw exception if not supported
                String fxNSURI = xmlStreamReader.getNamespaceContext().getNamespaceURI("fx");
                if (fxNSURI != null) {
                    String fxVersion = fxNSURI.substring(fxNSURI.lastIndexOf("/") + 1);
                    if (compareJFXVersions(FX_NAMESPACE_VERSION, fxVersion) < 0) {
                        throw new LoadException("Loading FXML document of version " +
                                fxVersion + " by JavaFX runtime supporting version " + FX_NAMESPACE_VERSION);
                    }
                }

                // checking the version JavaFX API - print warning if not supported
                String defaultNSURI = xmlStreamReader.getNamespaceContext().getNamespaceURI("");
                if (defaultNSURI != null) {
                    String nsVersion = defaultNSURI.substring(defaultNSURI.lastIndexOf("/") + 1);
                    if (compareJFXVersions(JAVAFX_VERSION, nsVersion) < 0) {
                        Logging.getJavaFXLogger().warning("Loading FXML document with JavaFX API of version " +
                                nsVersion + " by JavaFX runtime of version " + JAVAFX_VERSION);
                    }
                }
            }

            // Add the value to the namespace
            if (fx_id != null) {
                namespace.put(fx_id, value);

                // If the value defines an ID property, set it
                IDProperty idProperty = value.getClass().getAnnotation(IDProperty.class);

                if (idProperty != null) {
                    Map properties = getProperties();
                    // set fx:id property value to Node.id only if Node.id was not
                    // already set when processing start element attributes
                    if (properties.get(idProperty.value()) == null) {
                        properties.put(idProperty.value(), fx_id);
                    }
                }

                // Set the controller field value
                if (controller != null) {
                    Field field = getControllerFields().get(fx_id);

                    if (field != null) {
                        try {
                            field.set(controller, value);
                        } catch (IllegalAccessException exception) {
                            throw new RuntimeException(exception);
                        }
                    }
                }
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void processCharacters() throws LoadException {
            Class type = value.getClass();
            DefaultProperty defaultProperty = type.getAnnotation(DefaultProperty.class);

            // If the default property is a read-only list, add the value to it;
            // otherwise, set the value as the default property
            if (defaultProperty != null) {
                String text = xmlStreamReader.getText();
                text = extraneousWhitespacePattern.matcher(text).replaceAll(" ");

                String defaultPropertyName = defaultProperty.value();
                BeanAdapter valueAdapter = getValueAdapter();

                if (valueAdapter.isReadOnly(defaultPropertyName)
                    && List.class.isAssignableFrom(valueAdapter.getType(defaultPropertyName))) {
                    List list = (List)valueAdapter.get(defaultPropertyName);
                    list.add(getListValue(this, defaultPropertyName, text));
                } else {
                    valueAdapter.put(defaultPropertyName, text.trim());
                }
            } else {
                throw new LoadException(type.getName() + " does not have a default property.");
            }
        }

        @Override
        public void processAttribute(String prefix, String localName, String value)
            throws IOException{
            if (prefix != null
                && prefix.equals(FX_NAMESPACE_PREFIX)) {
                if (localName.equals(FX_ID_ATTRIBUTE)) {
                    // Verify that ID is a valid identifier
                    if (value.equals(NULL_KEYWORD)) {
                        throw new LoadException("Invalid identifier.");
                    }

                    for (int i = 0, n = value.length(); i < n; i++) {
                        if (!Character.isJavaIdentifierPart(value.charAt(i))) {
                            throw new LoadException("Invalid identifier.");
                        }
                    }

                    fx_id = value;

                } else if (localName.equals(FX_CONTROLLER_ATTRIBUTE)) {
                    if (current.parent != null) {
                        throw new LoadException(FX_NAMESPACE_PREFIX + ":" + FX_CONTROLLER_ATTRIBUTE
                            + " can only be applied to root element.");
                    }

                    if (controller != null) {
                        throw new LoadException("Controller value already specified.");
                    }

                    if (!staticLoad) {
                        Class type;
                        try {
                            type = classLoader.loadClass(value);
                        } catch (ClassNotFoundException exception) {
                            throw new LoadException(exception);
                        }

                        try {
                            if (controllerFactory == null) {
                                setController(ReflectUtil.newInstance(type));
                            } else {
                                setController(controllerFactory.call(type));
                            }
                        } catch (InstantiationException exception) {
                            throw new LoadException(exception);
                        } catch (IllegalAccessException exception) {
                            throw new LoadException(exception);
                        }
                    }
                } else {
                    throw new LoadException("Invalid attribute.");
                }
            } else {
                super.processAttribute(prefix, localName, value);
            }
        }

        public abstract Object constructValue() throws IOException;
    }

    // Element representing a class instance
    private class InstanceDeclarationElement extends ValueElement {
        public Class type;

        public String constant = null;
        public String factory = null;

        public InstanceDeclarationElement(Class type) throws LoadException {
            this.type = type;
        }

        @Override
        public void processAttribute(String prefix, String localName, String value)
            throws IOException {
            if (prefix != null
                && prefix.equals(FX_NAMESPACE_PREFIX)) {
                if (localName.equals(FX_VALUE_ATTRIBUTE)) {
                    this.value = value;
                } else if (localName.equals(FX_CONSTANT_ATTRIBUTE)) {
                    constant = value;
                } else if (localName.equals(FX_FACTORY_ATTRIBUTE)) {
                    factory = value;
                } else {
                    super.processAttribute(prefix, localName, value);
                }
            } else {
                super.processAttribute(prefix, localName, value);
            }
        }

        @Override
        public Object constructValue() throws IOException {
            Object value;
            if (this.value != null) {
                value = BeanAdapter.coerce(this.value, type);
            } else if (constant != null) {
                value = BeanAdapter.getConstantValue(type, constant);
            } else if (factory != null) {
                Method factoryMethod;
                try {
                    factoryMethod = MethodUtil.getMethod(type, factory, new Class[] {});
                } catch (NoSuchMethodException exception) {
                    throw new LoadException(exception);
                }

                try {
                    value = MethodUtil.invoke(factoryMethod, null, new Object [] {});
                } catch (IllegalAccessException exception) {
                    throw new LoadException(exception);
                } catch (InvocationTargetException exception) {
                    throw new LoadException(exception);
                }
            } else {
                value = (builderFactory == null) ? null : builderFactory.getBuilder(type);

                if (value == null) {
                    try {
                        value = ReflectUtil.newInstance(type);
                    } catch (InstantiationException exception) {
                        throw new LoadException(exception);
                    } catch (IllegalAccessException exception) {
                        throw new LoadException(exception);
                    }
                }
            }

            return value;
        }
    }

    // Element representing an unknown type
    private class UnknownTypeElement extends ValueElement {
        // Map type representing an unknown value
        @DefaultProperty("items")
        public class UnknownValueMap extends AbstractMap {
            private ArrayList items = new ArrayList();
            private HashMap values = new HashMap();

            @Override
            public Object get(Object key) {
                if (key == null) {
                    throw new NullPointerException();
                }

                return (key.equals(getClass().getAnnotation(DefaultProperty.class).value())) ?
                    items : values.get(key);
            }

            @Override
            public Object put(String key, Object value) {
                if (key == null) {
                    throw new NullPointerException();
                }

                if (key.equals(getClass().getAnnotation(DefaultProperty.class).value())) {
                    throw new IllegalArgumentException();
                }

                return values.put(key, value);
            }

            @Override
            public Set> entrySet() {
                return Collections.emptySet();
            }
        }

        @Override
        public void processEndElement() throws IOException {
            // No-op
        }

        @Override
        public Object constructValue() throws LoadException {
            return new UnknownValueMap();
        }
    }

    // Element representing an include
    private class IncludeElement extends ValueElement {
        public String source = null;
        public ResourceBundle resources = FXMLLoader.this.resources;
        public Charset charset = FXMLLoader.this.charset;

        @Override
        public void processAttribute(String prefix, String localName, String value)
            throws IOException {
            if (prefix == null) {
                if (localName.equals(INCLUDE_SOURCE_ATTRIBUTE)) {
                    if (loadListener != null) {
                        loadListener.readInternalAttribute(localName, value);
                    }

                    source = value;
                } else if (localName.equals(INCLUDE_RESOURCES_ATTRIBUTE)) {
                    if (loadListener != null) {
                        loadListener.readInternalAttribute(localName, value);
                    }

                    resources = ResourceBundle.getBundle(value, Locale.getDefault(), 
                            FXMLLoader.this.resources.getClass().getClassLoader());
                } else if (localName.equals(INCLUDE_CHARSET_ATTRIBUTE)) {
                    if (loadListener != null) {
                        loadListener.readInternalAttribute(localName, value);
                    }

                    charset = Charset.forName(value);
                } else {
                    super.processAttribute(prefix, localName, value);
                }
            } else {
                super.processAttribute(prefix, localName, value);
            }
        }

        @Override
        public Object constructValue() throws IOException {
            if (source == null) {
                throw new LoadException(INCLUDE_SOURCE_ATTRIBUTE + " is required.");
            }

            URL location;
            if (source.charAt(0) == '/') {
                location = classLoader.getResource(source.substring(1));
            } else {
                if (FXMLLoader.this.location == null) {
                    throw new LoadException("Base location is undefined.");
                }

                location = new URL(FXMLLoader.this.location, source);
            }

            FXMLLoader fxmlLoader = new FXMLLoader(location, resources,
                builderFactory, controllerFactory, charset,
                loaders);
            fxmlLoader.parentLoader = FXMLLoader.this;

            if (isCyclic(FXMLLoader.this, fxmlLoader)) {
                throw new IOException(
                        String.format(
                        "Including \"%s\" in \"%s\" created cyclic reference.",
                        fxmlLoader.location.toExternalForm(),
                        FXMLLoader.this.location.toExternalForm()));
            }
            fxmlLoader.setClassLoader(classLoader);
            fxmlLoader.setStaticLoad(staticLoad);

            Object value = fxmlLoader.load();

            if (fx_id != null) {
                String id = this.fx_id + CONTROLLER_SUFFIX;
                Object controller = fxmlLoader.getController();

                namespace.put(id, controller);

                if (FXMLLoader.this.controller != null) {
                    Field field = getControllerFields().get(id);

                    if (field != null) {
                        try {
                            field.set(FXMLLoader.this.controller, controller);
                        } catch (IllegalAccessException exception) {
                            throw new LoadException(exception);
                        }
                    }
                }
            }

            return value;
        }
    }

    // Element representing a reference
    private class ReferenceElement extends ValueElement {
        public String source = null;

        @Override
        public void processAttribute(String prefix, String localName, String value)
            throws IOException {
            if (prefix == null) {
                if (localName.equals(REFERENCE_SOURCE_ATTRIBUTE)) {
                    if (loadListener != null) {
                        loadListener.readInternalAttribute(localName, value);
                    }

                    source = value;
                } else {
                    super.processAttribute(prefix, localName, value);
                }
            } else {
                super.processAttribute(prefix, localName, value);
            }
        }

        @Override
        public Object constructValue() throws LoadException {
            if (source == null) {
                throw new LoadException(REFERENCE_SOURCE_ATTRIBUTE + " is required.");
            }

            KeyPath path = KeyPath.parse(source);
            if (!Expression.isDefined(namespace, path)) {
                throw new LoadException("Value \"" + source + "\" does not exist.");
            }

            return Expression.get(namespace, path);
        }
    }

    // Element representing a copy
    private class CopyElement extends ValueElement {
        public String source = null;

        @Override
        public void processAttribute(String prefix, String localName, String value)
            throws IOException {
            if (prefix == null) {
                if (localName.equals(COPY_SOURCE_ATTRIBUTE)) {
                    if (loadListener != null) {
                        loadListener.readInternalAttribute(localName, value);
                    }

                    source = value;
                } else {
                    super.processAttribute(prefix, localName, value);
                }
            } else {
                super.processAttribute(prefix, localName, value);
            }
        }

        @Override
        public Object constructValue() throws LoadException {
            if (source == null) {
                throw new LoadException(COPY_SOURCE_ATTRIBUTE + " is required.");
            }

            KeyPath path = KeyPath.parse(source);
            if (!Expression.isDefined(namespace, path)) {
                throw new LoadException("Value \"" + source + "\" does not exist.");
            }

            Object sourceValue = Expression.get(namespace, path);
            Class sourceValueType = sourceValue.getClass();

            Constructor constructor = null;
            try {
                constructor = ConstructorUtil.getConstructor(sourceValueType, new Class[] { sourceValueType });
            } catch (NoSuchMethodException exception) {
                // No-op
            }

            Object value;
            if (constructor != null) {
                try {
                    ReflectUtil.checkPackageAccess(sourceValueType);
                    value = constructor.newInstance(sourceValue);
                } catch (InstantiationException exception) {
                    throw new LoadException(exception);
                } catch (IllegalAccessException exception) {
                    throw new LoadException(exception);
                } catch (InvocationTargetException exception) {
                    throw new LoadException(exception);
                }
            } else {
                throw new LoadException("Can't copy value " + sourceValue + ".");
            }

            return value;
        }
    }

    // Element representing a predefined root value
    private class RootElement extends ValueElement {
        public String type = null;

        @Override
        public void processAttribute(String prefix, String localName, String value)
            throws IOException {
            if (prefix == null) {
                if (localName.equals(ROOT_TYPE_ATTRIBUTE)) {
                    if (loadListener != null) {
                        loadListener.readInternalAttribute(localName, value);
                    }

                    type = value;
                } else {
                    super.processAttribute(prefix, localName, value);
                }
            } else {
                super.processAttribute(prefix, localName, value);
            }
        }

        @Override
        public Object constructValue() throws LoadException {
            if (type == null) {
                throw new LoadException(ROOT_TYPE_ATTRIBUTE + " is required.");
            }

            Class type = getType(this.type);

            if (type == null) {
                throw new LoadException(this.type + " is not a valid type.");
            }

            Object value;
            if (root == null) {
                throw new LoadException("Root hasn't been set. Use method setRoot() before load.");
            } else {
                if (!type.isAssignableFrom(root.getClass())) {
                    throw new LoadException("Root is not an instance of "
                        + type.getName() + ".");
                }

                value = root;
            }

            return value;
        }
    }

    // Element representing a property
    private class PropertyElement extends Element {
        public final String name;
        public final Class sourceType;
        public final boolean readOnly;

        public PropertyElement(String name, Class sourceType) throws LoadException {
            if (parent == null) {
                throw new LoadException("Invalid root element.");
            }

            if (parent.value == null) {
                throw new LoadException("Parent element does not support property elements.");
            }

            this.name = name;
            this.sourceType = sourceType;

            if (sourceType == null) {
                // The element represents an instance property
                if (name.startsWith(EVENT_HANDLER_PREFIX)) {
                    throw new LoadException("\"" + name + "\" is not a valid element name.");
                }

            Map parentProperties = parent.getProperties();

            if (parent.isTyped()) {
                readOnly = parent.getValueAdapter().isReadOnly(name);
            } else {
                // If the map already defines a value for the property, assume
                // that it is read-only
                readOnly = parentProperties.containsKey(name);
            }

            if (readOnly) {
                Object value = parentProperties.get(name);
                if (value == null) {
                    throw new LoadException("Invalid property.");
                }

                updateValue(value);
            }
            } else {
                // The element represents a static property
                readOnly = false;
            }
        }

        @Override
        public boolean isCollection() {
            return (readOnly) ? super.isCollection() : false;
        }

        @Override
        public void add(Object element) throws LoadException {
            // Coerce the element to the list item type
            if (parent.isTyped()) {
                Type listType = parent.getValueAdapter().getGenericType(name);
                element = BeanAdapter.coerce(element, BeanAdapter.getListItemType(listType));
            }

            // Add the item to the list
            super.add(element);
        }

        @Override
        public void set(Object value) throws LoadException {
            // Update the value
            updateValue(value);

            if (sourceType == null) {
                // Apply value to parent element's properties
                parent.getProperties().put(name, value);
            } else {
                if (parent.value instanceof Builder) {
                    // Defer evaluation of the property
                    parent.staticPropertyElements.add(this);
                } else {
                    // Apply the static property value
                    BeanAdapter.put(parent.value, sourceType, name, value);
                }
            }
        }

        @Override
        public void processAttribute(String prefix, String localName, String value)
            throws IOException {
            if (!readOnly) {
                throw new LoadException("Attributes are not supported for writable property elements.");
            }

            super.processAttribute(prefix, localName, value);
        }

        @Override
        public void processEndElement() throws IOException {
            super.processEndElement();

            if (readOnly) {
                processInstancePropertyAttributes();
                processEventHandlerAttributes();
            }
        }

        @Override
        public void processCharacters() throws IOException {
            if (!readOnly) {
                String text = xmlStreamReader.getText();
                text = extraneousWhitespacePattern.matcher(text).replaceAll(" ");

                set(text.trim());
            } else {
                super.processCharacters();
            }
        }
    }

    // Element representing an unknown static property
    private class UnknownStaticPropertyElement extends Element {
        public UnknownStaticPropertyElement() throws LoadException {
            if (parent == null) {
                throw new LoadException("Invalid root element.");
            }

            if (parent.value == null) {
                throw new LoadException("Parent element does not support property elements.");
            }
        }

        @Override
        public boolean isCollection() {
            return false;
        }

        @Override
        public void set(Object value) {
            updateValue(value);
        }

        @Override
        public void processCharacters() throws IOException {
            String text = xmlStreamReader.getText();
            text = extraneousWhitespacePattern.matcher(text).replaceAll(" ");

            updateValue(text.trim());
        }
    }

    // Element representing a script block
    private class ScriptElement extends Element {
        public String source = null;
        public Charset charset = FXMLLoader.this.charset;

        @Override
        public boolean isCollection() {
            return false;
        }

        @Override
        public void processStartElement() throws IOException {
            super.processStartElement();

            if (source != null && !staticLoad) {
                int i = source.lastIndexOf(".");
                if (i == -1) {
                    throw new LoadException("Cannot determine type of script \""
                        + source + "\".");
                }

                String extension = source.substring(i + 1);
                ScriptEngine scriptEngine;
                ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
                try {
                    Thread.currentThread().setContextClassLoader(classLoader);
                    ScriptEngineManager scriptEngineManager = getScriptEngineManager();
                    scriptEngine = scriptEngineManager.getEngineByExtension(extension);
                } finally {
                    Thread.currentThread().setContextClassLoader(oldLoader);
                }

                if (scriptEngine == null) {
                    throw new LoadException("Unable to locate scripting engine for"
                        + " extension " + extension + ".");
                }

                scriptEngine.setBindings(scriptEngineManager.getBindings(), ScriptContext.ENGINE_SCOPE);

                try {
                    URL location;
                    if (source.charAt(0) == '/') {
                        location = classLoader.getResource(source.substring(1));
                    } else {
                        if (FXMLLoader.this.location == null) {
                            throw new LoadException("Base location is undefined.");
                        }

                        location = new URL(FXMLLoader.this.location, source);
                    }

                    InputStreamReader scriptReader = null;
                    try {
                        scriptReader = new InputStreamReader(location.openStream(), charset);
                        scriptEngine.eval(scriptReader);
                    } catch(ScriptException exception) {
                        exception.printStackTrace();
                    } finally {
                        if (scriptReader != null) {
                            scriptReader.close();
                        }
                    }
                } catch (IOException exception) {
                    throw new LoadException(exception);
                }
            }
        }

        @Override
        public void processEndElement() throws IOException {
            super.processEndElement();

            if (value != null && !staticLoad) {
                // Evaluate the script
                try {
                    scriptEngine.eval((String)value);
                } catch (ScriptException exception) {
                    System.err.println(exception.getMessage());
                }
            }
        }

        @Override
        public void processCharacters() throws LoadException {
            if (source != null) {
                throw new LoadException("Script source already specified.");
            }

            if (scriptEngine == null && !staticLoad) {
                throw new LoadException("Page language not specified.");
            }

            updateValue(xmlStreamReader.getText());
        }

        @Override
        public void processAttribute(String prefix, String localName, String value)
            throws IOException {
            if (prefix == null
                && localName.equals(SCRIPT_SOURCE_ATTRIBUTE)) {
                if (loadListener != null) {
                    loadListener.readInternalAttribute(localName, value);
                }

                source = value;
            } else if (localName.equals(SCRIPT_CHARSET_ATTRIBUTE)) {
                if (loadListener != null) {
                    loadListener.readInternalAttribute(localName, value);
                }

                charset = Charset.forName(value);
            } else {
                throw new LoadException(prefix == null ? localName : prefix + ":" + localName
                    + " is not a valid attribute.");
            }
        }
    }

    // Element representing a define block
    private class DefineElement extends Element {
        @Override
        public boolean isCollection() {
            return true;
        }

        @Override
        public void add(Object element) {
            // No-op
        }

        @Override
        public void processAttribute(String prefix, String localName, String value)
            throws LoadException{
            throw new LoadException("Element does not support attributes.");
        }
    }

    // Class representing an attribute of an element
    private static class Attribute {
        public final String name;
        public final Class sourceType;
        public final String value;

        public Attribute(String name, Class sourceType, String value) {
            this.name = name;
            this.sourceType = sourceType;
            this.value = value;
        }
    }

    // Event handler that delegates to a method defined by the controller object
    private static class ControllerMethodEventHandler implements EventHandler {
        public final Object controller;
        public final Method method;
        public final boolean typed;

        public ControllerMethodEventHandler(Object controller, Method method) {
            this.controller = controller;
            this.method = method;
            this.typed = (method.getParameterTypes().length == 1);
        }

        @Override
        public void handle(Event event) {
            try {
                if (typed) {
                    MethodUtil.invoke(method, controller, new Object[] { event });
                } else {
                    MethodUtil.invoke(method, controller, new Object[] {});
                }
            } catch (InvocationTargetException exception) {
                throw new RuntimeException(exception);
            } catch (IllegalAccessException exception) {
                throw new RuntimeException(exception);
            }
        }
    }

    // Event handler implemented in script code
    private static class ScriptEventHandler implements EventHandler {
        public final String script;
        public final ScriptEngine scriptEngine;

        public ScriptEventHandler(String script, ScriptEngine scriptEngine) {
            this.script = script;
            this.scriptEngine = scriptEngine;
        }

        @Override
        public void handle(Event event) {
            // Don't pollute the page namespace with values defined in the script
            Bindings engineBindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
            Bindings localBindings = scriptEngine.createBindings();
            localBindings.put(EVENT_KEY, event);
            scriptEngine.setBindings(localBindings, ScriptContext.ENGINE_SCOPE);

            // Execute the script
            try {
                scriptEngine.eval(script);
            } catch (ScriptException exception){
                throw new RuntimeException(exception);
            }

            // Restore the original bindings
            scriptEngine.setBindings(engineBindings, ScriptContext.ENGINE_SCOPE);
        }
    }

    // Observable list change listener
    private static class ObservableListChangeAdapter implements ListChangeListener {
        public final ObservableList source;
        public final EventHandler> handler;

        public ObservableListChangeAdapter(ObservableList source,
            EventHandler> handler) {
            this.source = source;
            this.handler = handler;
        }

        @Override
        @SuppressWarnings("unchecked")
        public void onChanged(Change change) {
            while (change.next()) {
                EventType> eventType;
                List removed = (List)change.getRemoved();

                if (change.wasPermutated()) {
                    eventType = ObservableListChangeEvent.UPDATE;
                    removed = null;
                } else if (change.wasAdded() && change.wasRemoved()) {
                    eventType = ObservableListChangeEvent.UPDATE;
                } else if (change.wasAdded()) {
                    eventType = ObservableListChangeEvent.ADD;
                } else if (change.wasRemoved()) {
                    eventType = ObservableListChangeEvent.REMOVE;
                } else {
                    throw new UnsupportedOperationException();
                }

                handler.handle(new ObservableListChangeEvent(source,
                    eventType, change.getFrom(), change.getTo(),
                    removed));
            }
        }
    }

    // Observable map change listener
    private static class ObservableMapChangeAdapter implements MapChangeListener {
        public final ObservableMap source;
        public final EventHandler> handler;

        public ObservableMapChangeAdapter(ObservableMap source,
            EventHandler> handler) {
            this.source = source;
            this.handler = handler;
        }

        @Override
        public void onChanged(Change change) {
            EventType> eventType;
            if (change.wasAdded() && change.wasRemoved()) {
                eventType = ObservableMapChangeEvent.UPDATE;
            } else if (change.wasAdded()) {
                eventType = ObservableMapChangeEvent.ADD;
            } else if (change.wasRemoved()) {
                eventType = ObservableMapChangeEvent.REMOVE;
            } else {
                throw new UnsupportedOperationException();
            }

            handler.handle(new ObservableMapChangeEvent(source,
                eventType, change.getKey(), change.getValueRemoved()));
        }
    }

    // Property model change listener
    private static class PropertyChangeAdapter implements ChangeListener {
        public final Object source;
        public final EventHandler> handler;

        public PropertyChangeAdapter(Object source, EventHandler> handler) {
            if (source == null) {
                throw new NullPointerException();
            }

            if (handler == null) {
                throw new NullPointerException();
            }

            this.source = source;
            this.handler = handler;
        }

        @Override
        public void changed(ObservableValue observable, Object oldValue, Object newValue) {
            handler.handle(new PropertyChangeEvent(source, oldValue));
        }
    }

    /**
     * @since JavaFX 8.0
     */
    protected URL location;
    /**
     * @since JavaFX 8.0
     */
    protected ResourceBundle resources;

    private ObservableMap namespace = FXCollections.observableHashMap();

    /**
     * @since JavaFX 8.0
     */
    protected Object root = null;
    /**
     * @since JavaFX 8.0
     */
    protected Object controller = null;

    private BuilderFactory builderFactory;
    private Callback, Object> controllerFactory;
    private Charset charset;

    private LinkedList loaders;

    private ClassLoader classLoader = defaultClassLoader;
    private boolean staticLoad = false;
    private LoadListener loadListener = null;

    private FXMLLoader parentLoader;

    private XMLStreamReader xmlStreamReader = null;
    private Element current = null;

    private ScriptEngine scriptEngine = null;

    private boolean template = false;

    private LinkedList packages = new LinkedList();
    private HashMap> classes = new HashMap>();

    private HashMap controllerFields = null;
    private HashMap controllerMethods = null;

    private ScriptEngineManager scriptEngineManager = null;

    private static ClassLoader defaultClassLoader;

    private static final Pattern extraneousWhitespacePattern = Pattern.compile("\\s+");

    public static final String DEFAULT_CHARSET_NAME = "UTF-8";

    public static final String LANGUAGE_PROCESSING_INSTRUCTION = "language";
    public static final String IMPORT_PROCESSING_INSTRUCTION = "import";

    public static final String FX_NAMESPACE_PREFIX = "fx";
    public static final String FX_CONTROLLER_ATTRIBUTE = "controller";
    public static final String FX_ID_ATTRIBUTE = "id";
    public static final String FX_VALUE_ATTRIBUTE = "value";
    /**
     * @since JavaFX 2.2
     */
    public static final String FX_CONSTANT_ATTRIBUTE = "constant";
    public static final String FX_FACTORY_ATTRIBUTE = "factory";

    public static final String INCLUDE_TAG = "include";
    public static final String INCLUDE_SOURCE_ATTRIBUTE = "source";
    public static final String INCLUDE_RESOURCES_ATTRIBUTE = "resources";
    public static final String INCLUDE_CHARSET_ATTRIBUTE = "charset";

    public static final String SCRIPT_TAG = "script";
    public static final String SCRIPT_SOURCE_ATTRIBUTE = "source";
    public static final String SCRIPT_CHARSET_ATTRIBUTE = "charset";

    public static final String DEFINE_TAG = "define";

    public static final String REFERENCE_TAG = "reference";
    public static final String REFERENCE_SOURCE_ATTRIBUTE = "source";

    /**
     * @since JavaFX 2.2
     */
    public static final String ROOT_TAG = "root";
    /**
     * @since JavaFX 2.2
     */
    public static final String ROOT_TYPE_ATTRIBUTE = "type";

    public static final String COPY_TAG = "copy";
    public static final String COPY_SOURCE_ATTRIBUTE = "source";

    public static final String EVENT_HANDLER_PREFIX = "on";
    public static final String EVENT_KEY = "event";
    public static final String CHANGE_EVENT_HANDLER_SUFFIX = "Change";

    public static final String NULL_KEYWORD = "null";

    /**
     * @since JavaFX 2.1
     */
    public static final String ESCAPE_PREFIX = "\\";
    public static final String RELATIVE_PATH_PREFIX = "@";
    public static final String RESOURCE_KEY_PREFIX = "%";
    public static final String EXPRESSION_PREFIX = "$";
    public static final String BINDING_EXPRESSION_PREFIX = "${";
    public static final String BINDING_EXPRESSION_SUFFIX = "}";

    /**
     * @since JavaFX 2.1
     */
    public static final String BI_DIRECTIONAL_BINDING_PREFIX = "#{";
    /**
     * @since JavaFX 2.1
     */
    public static final String BI_DIRECTIONAL_BINDING_SUFFIX = "}";

    /**
     * @since JavaFX 2.1
     */
    public static final String ARRAY_COMPONENT_DELIMITER = ",";

    /**
     * @since JavaFX 2.2
     */
    public static final String LOCATION_KEY = "location";
    /**
     * @since JavaFX 2.2
     */
    public static final String RESOURCES_KEY = "resources";

    public static final String CONTROLLER_METHOD_PREFIX = "#";
    /**
     * @since JavaFX 2.1
     */
    public static final String CONTROLLER_KEYWORD = "controller";
    /**
     * @since JavaFX 2.2
     */
    public static final String CONTROLLER_SUFFIX = "Controller";

    /**
     * @since JavaFX 2.2
     */
    public static final String INITIALIZE_METHOD_NAME = "initialize";

    /**
     * @since JavaFX 8.0
     */
    public static final String JAVAFX_VERSION;

    /**
     * @since JavaFX 8.0
     */
    public static final String FX_NAMESPACE_VERSION = "1";

    static {
        defaultClassLoader = Thread.currentThread().getContextClassLoader();

        if (defaultClassLoader == null) {
            throw new NullPointerException();
        }

        JAVAFX_VERSION = AccessController.doPrivileged(new PrivilegedAction() {
            @Override
            public String run() {
                return System.getProperty("javafx.version");
            }
        });
    }

    /**
     * Creates a new FXMLLoader instance.
     */
    public FXMLLoader() {
        this((URL)null);
    }

    /**
     * Creates a new FXMLLoader instance.
     *
     * @param location
     * @since JavaFX 2.1
     */
    public FXMLLoader(URL location) {
        this(location, null);
    }

    /**
     * Creates a new FXMLLoader instance.
     *
     * @param location
     * @param resources
     * @since JavaFX 2.1
     */
    public FXMLLoader(URL location, ResourceBundle resources) {
        this(location, resources, new JavaFXBuilderFactory());
    }

    /**
     * Creates a new FXMLLoader instance.
     *
     * @param location
     * @param resources
     * @param builderFactory
     * @since JavaFX 2.1
     */
    public FXMLLoader(URL location, ResourceBundle resources, BuilderFactory builderFactory) {
        this(location, resources, builderFactory, null);
    }

    /**
     * Creates a new FXMLLoader instance.
     *
     * @param location
     * @param resources
     * @param builderFactory
     * @param controllerFactory
     * @since JavaFX 2.1
     */
    public FXMLLoader(URL location, ResourceBundle resources, BuilderFactory builderFactory,
        Callback, Object> controllerFactory) {
        this(location, resources, builderFactory, controllerFactory, Charset.forName(DEFAULT_CHARSET_NAME));
    }

    /**
     * Creates a new FXMLLoader instance.
     *
     * @param charset
     */
    public FXMLLoader(Charset charset) {
        this(null, null, null, null, charset);
    }

    /**
     * Creates a new FXMLLoader instance.
     *
     * @param location
     * @param resources
     * @param builderFactory
     * @param controllerFactory
     * @param charset
     * @since JavaFX 2.1
     */
    public FXMLLoader(URL location, ResourceBundle resources, BuilderFactory builderFactory,
        Callback, Object> controllerFactory, Charset charset) {
        this(location, resources, builderFactory, controllerFactory, charset,
            new LinkedList());
    }

    /**
     * Creates a new FXMLLoader instance.
     *
     * @param location
     * @param resources
     * @param builderFactory
     * @param controllerFactory
     * @param charset
     * @param loaders
     * @since JavaFX 2.1
     */
    public FXMLLoader(URL location, ResourceBundle resources, BuilderFactory builderFactory,
        Callback, Object> controllerFactory, Charset charset,
        LinkedList loaders) {
        setLocation(location);
        setResources(resources);
        setBuilderFactory(builderFactory);
        setControllerFactory(controllerFactory);
        setCharset(charset);

        this.loaders = loaders;
    }

    /**
     * Returns the location used to resolve relative path attribute values.
     */
    public URL getLocation() {
        return location;
    }

    /**
     * Sets the location used to resolve relative path attribute values.
     *
     * @param location
     */
    public void setLocation(URL location) {
        this.location = location;
    }

    /**
     * Returns the resources used to resolve resource key attribute values.
     */
    public ResourceBundle getResources() {
        return resources;
    }

    /**
     * Sets the resources used to resolve resource key attribute values.
     *
     * @param resources
     */
    public void setResources(ResourceBundle resources) {
        this.resources = resources;
    }

    /**
     * Returns the namespace used by this loader.
     */
    public ObservableMap getNamespace() {
        return namespace;
    }

    /**
     * Returns the root of the object hierarchy.
     */
    @SuppressWarnings("unchecked")
    public  T getRoot() {
        return (T)root;
    }

    /**
     * Sets the root of the object hierarchy. The value passed to this method
     * is used as the value of the <fx:root> tag. This method
     * must be called prior to loading the document when using
     * <fx:root>.
     *
     * @param root
     * The root of the object hierarchy.
     * @since JavaFX 2.2
     */
    public void setRoot(Object root) {
        this.root = root;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof FXMLLoader) {
            FXMLLoader loader = (FXMLLoader)obj;
            return loader.location.toExternalForm().equals(
                    location.toExternalForm());
        }
        return false;
    }

    private boolean isCyclic(
                            FXMLLoader currentLoader,
                            FXMLLoader node) {
        if (currentLoader == null) {
            return false;
        }
        if (currentLoader.equals(node)) {
            return true;
        }
        return isCyclic(currentLoader.parentLoader, node);
    }

    /**
     * Returns the controller associated with the root object.
     */
    @SuppressWarnings("unchecked")
    public  T getController() {
        return (T)controller;
    }

    /**
     * Sets the controller associated with the root object. The value passed to
     * this method is used as the value of the fx:controller attribute.
     * This method must be called prior to loading the document when using
     * controller event handlers when an fx:controller attribute is not
     * specified in the document.
     *
     * @param controller
     * The controller to associate with the root object.
     * @since JavaFX 2.2
     */
    public void setController(Object controller) {
        this.controller = controller;

        if (controller == null) {
            namespace.remove(CONTROLLER_KEYWORD);
        } else {
            namespace.put(CONTROLLER_KEYWORD, controller);
        }

        controllerFields = null;
        controllerMethods = null;
    }

    /**
     * Returns the template flag.
     * @since JavaFX 8.0
     */
    public boolean isTemplate() {
        return template;
    }

    /**
     * Sets the template flag. Setting this value to true can improve
     * performance when using a single loader instance to reload the same FXML
     * document multiple times. See the documentation for the {@link #load()}
     * method for more information.
     *
     * @param template
     * The template flag.
     * @since JavaFX 8.0
     */
    public void setTemplate(boolean template) {
        this.template = template;
    }

    /**
     * Returns the builder factory used by this loader.
     */
    public BuilderFactory getBuilderFactory() {
        return builderFactory;
    }

    /**
     * Sets the builder factory used by this loader.
     *
     * @param builderFactory
     */
    public void setBuilderFactory(BuilderFactory builderFactory) {
        this.builderFactory = builderFactory;
    }

    /**
     * Returns the controller factory used by this serializer.
     * @since JavaFX 2.1
     */
    public Callback, Object> getControllerFactory() {
        return controllerFactory;
    }

    /**
     * Sets the controller factory used by this serializer.
     *
     * @param controllerFactory
     * @since JavaFX 2.1
     */
    public void setControllerFactory(Callback, Object> controllerFactory) {
        this.controllerFactory = controllerFactory;
    }

    /**
     * Returns the character set used by this loader.
     */
    public Charset getCharset() {
        return charset;
    }

    /**
     * Sets the charset used by this loader.
     *
     * @param charset
     * @since JavaFX 2.1
     */
    public void setCharset(Charset charset) {
        if (charset == null) {
            throw new NullPointerException("charset is null.");
        }

        this.charset = charset;
    }

    /**
     * Returns the classloader used by this serializer.
     * @since JavaFX 2.1
     */
    public ClassLoader getClassLoader() {
        return classLoader;
    }

    /**
     * Sets the classloader used by this serializer and clears any existing
     * imports (see {@link #setTemplate(boolean)}).
     *
     * @param classLoader
     * @since JavaFX 2.1
     */
    public void setClassLoader(ClassLoader classLoader) {
        if (classLoader == null) {
            throw new IllegalArgumentException();
        }

        this.classLoader = classLoader;

        clearImports();
    }

    /**
     * Returns the static load flag.
     *
     * @treatAsPrivate
     * @deprecated
     */
    public boolean isStaticLoad() {
        // SB-dependency: RT-21226 has been filed to track this
        return staticLoad;
    }

    /**
     * Sets the static load flag.
     *
     * @param staticLoad
     *
     * @treatAsPrivate
     * @deprecated
     */
    public void setStaticLoad(boolean staticLoad) {
        // SB-dependency: RT-21226 has been filed to track this
        this.staticLoad = staticLoad;
    }

    /**
     * Returns this loader's load listener.
     *
     * @treatAsPrivate
     * @deprecated
     */
    public LoadListener getLoadListener() {
        // SB-dependency: RT-21228 has been filed to track this
        return loadListener;
    }

    /**
     * Sets this loader's load listener.
     *
     * @param loadListener
     *
     * @treatAsPrivate
     * @deprecated
     */
    public void setLoadListener(LoadListener loadListener) {
        // SB-dependency: RT-21228 has been filed to track this
        this.loadListener = loadListener;
    }

    /**
     * Loads an object hierarchy from a FXML document. The location from which
     * the document will be loaded must have been set by a prior call to
     * {@link #setLocation(URL)}.
     * 

* When the "template" flag is set to false (the default), this * method will clear the imports before loading the document's content. * When "template" is true, the imports will not be cleared, and * the root value will be set to null before the content is * loaded. This helps improve performance on subsequent loads by * eliminating the overhead of loading the classes referred to by the * document. * * @return * The loaded object hierarchy. * @since JavaFX 2.1 */ public Object load() throws IOException { if (location == null) { throw new IllegalStateException("Location is not set."); } InputStream inputStream = null; Object value; try { inputStream = location.openStream(); value = load(inputStream); } catch (IOException exception) { logException(exception); throw exception; } catch (RuntimeException exception) { logException(exception); throw exception; } finally { if (inputStream != null) { inputStream.close(); } } return value; } /** * Loads an object hierarchy from a FXML document. * * @param inputStream * An input stream containing the FXML data to load. * * @return * The loaded object hierarchy. */ @SuppressWarnings("dep-ann") public Object load(InputStream inputStream) throws IOException { if (inputStream == null) { throw new NullPointerException("inputStream is null."); } if (template) { setRoot(null); } else { clearImports(); } // Initialize the namespace namespace.put(LOCATION_KEY, location); namespace.put(RESOURCES_KEY, resources); // Clear the script engine scriptEngine = null; // Create the parser try { XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); xmlInputFactory.setProperty("javax.xml.stream.isCoalescing", true); // Some stream readers incorrectly report an empty string as the prefix // for the default namespace; correct this as needed InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charset); xmlStreamReader = new StreamReaderDelegate(xmlInputFactory.createXMLStreamReader(inputStreamReader)) { @Override public String getPrefix() { String prefix = super.getPrefix(); if (prefix != null && prefix.length() == 0) { prefix = null; } return prefix; } @Override public String getAttributePrefix(int index) { String attributePrefix = super.getAttributePrefix(index); if (attributePrefix != null && attributePrefix.length() == 0) { attributePrefix = null; } return attributePrefix; } }; } catch (XMLStreamException exception) { throw new LoadException(exception); } // Push this loader onto the stack loaders.push(this); // Parse the XML stream try { while (xmlStreamReader.hasNext()) { int event = xmlStreamReader.next(); switch (event) { case XMLStreamConstants.PROCESSING_INSTRUCTION: { processProcessingInstruction(); break; } case XMLStreamConstants.COMMENT: { processComment(); break; } case XMLStreamConstants.START_ELEMENT: { processStartElement(); break; } case XMLStreamConstants.END_ELEMENT: { processEndElement(); break; } case XMLStreamConstants.CHARACTERS: { processCharacters(); break; } } } } catch (XMLStreamException exception) { throw new LoadException(exception); } if (controller != null) { if (controller instanceof Initializable) { ((Initializable)controller).initialize(location, resources); } else { // Inject controller fields HashMap controllerFields = getControllerFields(); Field locationField = controllerFields.get(LOCATION_KEY); if (locationField != null) { try { locationField.set(controller, location); } catch (IllegalAccessException exception) { // TODO Throw when Initializable is deprecated/removed // throw new LoadException(exception); } } Field resourcesField = controllerFields.get(RESOURCES_KEY); if (resourcesField != null) { try { resourcesField.set(controller, resources); } catch (IllegalAccessException exception) { // TODO Throw when Initializable is deprecated/removed // throw new LoadException(exception); } } // Initialize the controller Method initializeMethod = getControllerMethods().get(INITIALIZE_METHOD_NAME); if (initializeMethod != null) { try { MethodUtil.invoke(initializeMethod, controller, new Object [] {}); } catch (IllegalAccessException exception) { // TODO Throw when Initializable is deprecated/removed // throw new LoadException(exception); } catch (InvocationTargetException exception) { throw new LoadException(exception); } } } } // Pop this loader off of the stack loaders.pop(); // Clear the parser xmlStreamReader = null; return root; } private void clearImports() { packages.clear(); classes.clear(); } private void logException(Exception exception) { String message = exception.getMessage(); if (message == null) { message = exception.getClass().getName(); } StringBuilder messageBuilder = new StringBuilder(message); messageBuilder.append("\n"); for (FXMLLoader loader : loaders) { messageBuilder.append(loader.location.getPath()); if (loader.current != null) { messageBuilder.append(":"); messageBuilder.append(loader.current.lineNumber); } messageBuilder.append("\n"); } StackTraceElement[] stackTrace = exception.getStackTrace(); if (stackTrace != null) { for (int i = 0; i < stackTrace.length; i++) { messageBuilder.append(" at "); messageBuilder.append(stackTrace[i].toString()); messageBuilder.append("\n"); } } System.err.println(messageBuilder.toString()); } /** * Returns the current line number. * * @treatAsPrivate * @deprecated * @since JavaFX 2.2 */ public int getLineNumber() { return xmlStreamReader.getLocation().getLineNumber(); } /** * Returns the current parse trace. * * @treatAsPrivate * @deprecated * @since JavaFX 2.1 */ public ParseTraceElement[] getParseTrace() { ParseTraceElement[] parseTrace = new ParseTraceElement[loaders.size()]; int i = 0; for (FXMLLoader loader : loaders) { parseTrace[i++] = new ParseTraceElement(loader.location, (loader.current != null) ? loader.current.lineNumber : -1); } return parseTrace; } private void processProcessingInstruction() throws LoadException { String piTarget = xmlStreamReader.getPITarget().trim(); if (piTarget.equals(LANGUAGE_PROCESSING_INSTRUCTION)) { processLanguage(); } else if (piTarget.equals(IMPORT_PROCESSING_INSTRUCTION)) { processImport(); } } private void processLanguage() throws LoadException { if (scriptEngine != null) { throw new LoadException("Page language already set."); } String language = xmlStreamReader.getPIData(); if (loadListener != null) { loadListener.readLanguageProcessingInstruction(language); } if (!staticLoad) { ScriptEngineManager scriptEngineManager = getScriptEngineManager(); scriptEngine = scriptEngineManager.getEngineByName(language); scriptEngine.setBindings(scriptEngineManager.getBindings(), ScriptContext.ENGINE_SCOPE); } } private void processImport() throws LoadException { String target = xmlStreamReader.getPIData().trim(); if (loadListener != null) { loadListener.readImportProcessingInstruction(target); } if (target.endsWith(".*")) { importPackage(target.substring(0, target.length() - 2)); } else { importClass(target); } } private void processComment() throws LoadException { if (loadListener != null) { loadListener.readComment(xmlStreamReader.getText()); } } private void processStartElement() throws IOException { // Create the element createElement(); // Process the start tag current.processStartElement(); // Set the root value if (root == null) { root = current.value; } } private void createElement() throws IOException { String prefix = xmlStreamReader.getPrefix(); String localName = xmlStreamReader.getLocalName(); if (prefix == null) { int i = localName.lastIndexOf('.'); if (Character.isLowerCase(localName.charAt(i + 1))) { String name = localName.substring(i + 1); if (i == -1) { // This is an instance property if (loadListener != null) { loadListener.beginPropertyElement(name, null); } current = new PropertyElement(name, null); } else { // This is a static property Class sourceType = getType(localName.substring(0, i)); if (sourceType != null) { if (loadListener != null) { loadListener.beginPropertyElement(name, sourceType); } current = new PropertyElement(name, sourceType); } else if (staticLoad) { // The source type was not recognized if (loadListener != null) { loadListener.beginUnknownStaticPropertyElement(localName); } current = new UnknownStaticPropertyElement(); } else { throw new LoadException(localName + " is not a valid property."); } } } else { if (current == null && root != null) { throw new LoadException("Root value already specified."); } Class type = getType(localName); if (type != null) { if (loadListener != null) { loadListener.beginInstanceDeclarationElement(type); } current = new InstanceDeclarationElement(type); } else if (staticLoad) { // The type was not recognized if (loadListener != null) { loadListener.beginUnknownTypeElement(localName); } current = new UnknownTypeElement(); } else { throw new LoadException(localName + " is not a valid type."); } } } else if (prefix.equals(FX_NAMESPACE_PREFIX)) { if (localName.equals(INCLUDE_TAG)) { if (loadListener != null) { loadListener.beginIncludeElement(); } current = new IncludeElement(); } else if (localName.equals(REFERENCE_TAG)) { if (loadListener != null) { loadListener.beginReferenceElement(); } current = new ReferenceElement(); } else if (localName.equals(COPY_TAG)) { if (loadListener != null) { loadListener.beginCopyElement(); } current = new CopyElement(); } else if (localName.equals(ROOT_TAG)) { if (loadListener != null) { loadListener.beginRootElement(); } current = new RootElement(); } else if (localName.equals(SCRIPT_TAG)) { if (loadListener != null) { loadListener.beginScriptElement(); } current = new ScriptElement(); } else if (localName.equals(DEFINE_TAG)) { if (loadListener != null) { loadListener.beginDefineElement(); } current = new DefineElement(); } else { throw new LoadException(prefix + ":" + localName + " is not a valid element."); } } else { throw new LoadException("Unexpected namespace prefix: " + prefix + "."); } } private void processEndElement() throws IOException { current.processEndElement(); if (loadListener != null) { loadListener.endElement(current.value); } // Move up the stack current = current.parent; } private void processCharacters() throws IOException { // Process the characters if (!xmlStreamReader.isWhiteSpace()) { current.processCharacters(); } } private void importPackage(String name) throws LoadException { packages.add(name); } private void importClass(String name) throws LoadException { try { loadType(name, true); } catch (ClassNotFoundException exception) { throw new LoadException(exception); } } private Class getType(String name) throws LoadException { Class type = null; if (Character.isLowerCase(name.charAt(0))) { // This is a fully-qualified class name try { type = loadType(name, false); } catch (ClassNotFoundException exception) { // No-op } } else { // This is an unqualified class name type = classes.get(name); if (type == null) { // The class has not been loaded yet; look it up for (String packageName : packages) { try { type = loadTypeForPackage(packageName, name); } catch (ClassNotFoundException exception) { // No-op } if (type != null) { break; } } if (type != null) { classes.put(name, type); } } } return type; } private Class loadType(String name, boolean cache) throws ClassNotFoundException { int i = name.indexOf('.'); int n = name.length(); while (i != -1 && i < n && Character.isLowerCase(name.charAt(i + 1))) { i = name.indexOf('.', i + 1); } if (i == -1 || i == n) { throw new ClassNotFoundException(); } String packageName = name.substring(0, i); String className = name.substring(i + 1); Class type = loadTypeForPackage(packageName, className); if (cache) { classes.put(className, type); } return type; } // TODO Rename to loadType() when deprecated static version is removed private Class loadTypeForPackage(String packageName, String className) throws ClassNotFoundException { return classLoader.loadClass(packageName + "." + className.replace('.', '$')); } private HashMap getControllerFields() throws LoadException { if (controllerFields == null) { controllerFields = new HashMap(); Class controllerType = controller.getClass(); Class type = controllerType; while (type != Object.class) { Field[] fields = FieldUtil.getDeclaredFields(type); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; int modifiers = field.getModifiers(); // Only add fields that are visible to this controller type if (type == controllerType || (modifiers & Modifier.PRIVATE) == 0) { // Ensure that the field is accessible if ((modifiers & Modifier.PUBLIC) == 0 && field.getAnnotation(FXML.class) != null) { try { field.setAccessible(true); } catch (SecurityException exception) { throw new LoadException(exception); } } controllerFields.put(field.getName(), field); } } type = type.getSuperclass(); } } return controllerFields; } private HashMap getControllerMethods() throws LoadException { if (controllerMethods == null) { controllerMethods = new HashMap(); Class controllerType = controller.getClass(); Class type = controllerType; while (type != Object.class) { ReflectUtil.checkPackageAccess(type); Method[] methods = type.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; int modifiers = method.getModifiers(); // Only add methods that are visible to this controller type if (type == controllerType || (modifiers & Modifier.PRIVATE) == 0) { // Ensure that the method is accessible if ((modifiers & Modifier.PUBLIC) == 0 && method.getAnnotation(FXML.class) != null) { try { method.setAccessible(true); } catch (SecurityException exception) { throw new LoadException(exception); } } // Add this method to the map if: // a) it is the initialize() method, or // b) it takes a single event argument, or // c) it takes no arguments and a handler with this // name has not already been defined String methodName = method.getName(); Class[] parameterTypes = method.getParameterTypes(); if (methodName.equals(INITIALIZE_METHOD_NAME)) { if (parameterTypes.length == 0) { controllerMethods.put(method.getName(), method); } } else if ((parameterTypes.length == 1 && Event.class.isAssignableFrom(parameterTypes[0])) || (parameterTypes.length == 0 && !controllerMethods.containsKey(methodName))) { controllerMethods.put(method.getName(), method); } } } type = type.getSuperclass(); } } return controllerMethods; } private ScriptEngineManager getScriptEngineManager() { if (scriptEngineManager == null) { scriptEngineManager = new javax.script.ScriptEngineManager(); scriptEngineManager.setBindings(new SimpleBindings(namespace)); } return scriptEngineManager; } /** * Loads a type using the default class loader. * * @param packageName * @param className * * @deprecated * This method now delegates to {@link #getDefaultClassLoader()}. */ public static Class loadType(String packageName, String className) throws ClassNotFoundException { return loadType(packageName + "." + className.replace('.', '$')); } /** * Loads a type using the default class loader. * * @param className * * @deprecated * This method now delegates to {@link #getDefaultClassLoader()}. */ public static Class loadType(String className) throws ClassNotFoundException { ReflectUtil.checkPackageAccess(className); return Class.forName(className, true, defaultClassLoader); } /** * Returns the default class loader. * @since JavaFX 2.1 */ public static ClassLoader getDefaultClassLoader() { return defaultClassLoader; } /** * Sets the default class loader. * * @param defaultClassLoader * The default class loader to use when loading classes. * @since JavaFX 2.1 */ public static void setDefaultClassLoader(ClassLoader defaultClassLoader) { if (defaultClassLoader == null) { throw new NullPointerException(); } FXMLLoader.defaultClassLoader = defaultClassLoader; } /** * Loads an object hierarchy from a FXML document. * * @param location */ @SuppressWarnings("unchecked") public static T load(URL location) throws IOException { return (T)load(location, null); } /** * Loads an object hierarchy from a FXML document. * * @param location * @param resources */ @SuppressWarnings("unchecked") public static T load(URL location, ResourceBundle resources) throws IOException { return (T)load(location, resources, new JavaFXBuilderFactory()); } /** * Loads an object hierarchy from a FXML document. * * @param location * @param resources * @param builderFactory */ @SuppressWarnings("unchecked") public static T load(URL location, ResourceBundle resources, BuilderFactory builderFactory) throws IOException { return (T)load(location, resources, builderFactory, null); } /** * Loads an object hierarchy from a FXML document. * * @param location * @param resources * @param builderFactory * @param controllerFactory * @since JavaFX 2.1 */ @SuppressWarnings("unchecked") public static T load(URL location, ResourceBundle resources, BuilderFactory builderFactory, Callback, Object> controllerFactory) throws IOException { return (T)load(location, resources, builderFactory, controllerFactory, Charset.forName(DEFAULT_CHARSET_NAME)); } /** * Loads an object hierarchy from a FXML document. * * @param location * @param resources * @param builderFactory * @param controllerFactory * @param charset * @since JavaFX 2.1 */ @SuppressWarnings("unchecked") public static T load(URL location, ResourceBundle resources, BuilderFactory builderFactory, Callback, Object> controllerFactory, Charset charset) throws IOException { if (location == null) { throw new NullPointerException("Location is required."); } FXMLLoader fxmlLoader = new FXMLLoader(location, resources, builderFactory, controllerFactory, charset); return (T)fxmlLoader.load(); } /** * Utility method for comparing two JavaFX version strings (such as 2.2.5, 8.0.0-ea) * @param rtVer String representation of JavaFX runtime version, including - or _ appendix * @param nsVer String representation of JavaFX version to compare against runtime version * @return number < 0 if runtime version is lower, 0 when both versions are the same, * number > 0 if runtime is higher version */ static int compareJFXVersions(String rtVer, String nsVer) { int retVal = 0; if (rtVer == null || "".equals(rtVer) || nsVer == null || "".equals(nsVer)) { return retVal; } if (rtVer.equals(nsVer)) { return retVal; } // version string can contain '-' int dashIndex = rtVer.indexOf("-"); if (dashIndex > 0) { rtVer = rtVer.substring(0, dashIndex); } // or "_" int underIndex = rtVer.indexOf("_"); if (underIndex > 0) { rtVer = rtVer.substring(0, underIndex); } // do not try to compare if the string is not valid version format if (!Pattern.matches("^(\\d+)(\\.\\d+)*$", rtVer) || !Pattern.matches("^(\\d+)(\\.\\d+)*$", nsVer)) { return retVal; } StringTokenizer nsVerTokenizer = new StringTokenizer(nsVer, "."); StringTokenizer rtVerTokenizer = new StringTokenizer(rtVer, "."); int nsDigit = 0, rtDigit = 0; boolean rtVerEnd = false; while (nsVerTokenizer.hasMoreTokens() && retVal == 0) { nsDigit = Integer.parseInt(nsVerTokenizer.nextToken()); if (rtVerTokenizer.hasMoreTokens()) { rtDigit = Integer.parseInt(rtVerTokenizer.nextToken()); retVal = rtDigit - nsDigit; } else { rtVerEnd = true; break; } } if (rtVerTokenizer.hasMoreTokens() && retVal == 0) { rtDigit = Integer.parseInt(rtVerTokenizer.nextToken()); if (rtDigit > 0) { retVal = 1; } } if (rtVerEnd) { if (nsDigit > 0) { retVal = -1; } else { while (nsVerTokenizer.hasMoreTokens()) { nsDigit = Integer.parseInt(nsVerTokenizer.nextToken()); if (nsDigit > 0) { retVal = -1; break; } } } } return retVal; } }