/*
* Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javafx.fxml;
import com.sun.javafx.util.
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.security.
AllPermission;
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.
InvalidationListener;
import javafx.beans.property.
Property;
import javafx.beans.value.
ChangeListener;
import javafx.beans.value.
ObservableValue;
import javafx.collections.*;
import javafx.event.
Event;
import javafx.event.
EventHandler;
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.
ParseTraceElement;
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.
AllPermission;
import java.security.
PrivilegedAction;
import java.util.
EnumMap;
import java.util.
Locale;
import java.util.
StringTokenizer;
import sun.reflect.
CallerSensitive;
import sun.reflect.
Reflection;
import sun.reflect.misc.
ConstructorUtil;
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 {
// Indicates permission to get the ClassLoader
private static final
RuntimePermission GET_CLASSLOADER_PERMISSION =
new
RuntimePermission("getClassLoader");
// Abstract base class for elements
private abstract class
Element {
public final
Element parent;
public
Object value = null;
private
BeanAdapter valueAdapter = null;
public final
LinkedList<
Attribute>
eventHandlerAttributes = new
LinkedList<
Attribute>();
public final
LinkedList<
Attribute>
instancePropertyAttributes = new
LinkedList<
Attribute>();
public final
LinkedList<
Attribute>
staticPropertyAttributes = new
LinkedList<
Attribute>();
public final
LinkedList<
PropertyElement>
staticPropertyElements = new
LinkedList<
PropertyElement>();
public
Element() {
parent =
current;
}
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<
Object>
list;
if (
value instanceof
List<?>) {
list = (
List<
Object>)
value;
} else {
Class<?>
type =
value.
getClass();
DefaultProperty defaultProperty =
type.
getAnnotation(
DefaultProperty.class);
String defaultPropertyName =
defaultProperty.
value();
// Get the list value
list = (
List<
Object>)
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
constructLoadException("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
constructLoadException("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<
String,
Object>
getProperties() {
return (
isTyped()) ?
getValueAdapter() : (
Map<
String,
Object>)
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
constructLoadException("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
constructLoadException(
localName + " is not a valid attribute.");
}
}
}
} else {
throw
constructLoadException(
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
constructLoadException("Cannot bind to static property.");
}
if (!
isTyped()) {
throw
constructLoadException("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
constructLoadException("Cannot bind to builder property.");
}
if (!
impl_isStaticLoad()) {
value =
value.
substring(
BINDING_EXPRESSION_PREFIX.
length(),
value.
length() - 1);
expression =
Expression.
valueOf(
value);
// Create the binding
BeanAdapter targetAdapter = new
BeanAdapter(this.
value);
ObservableValue<
Object>
propertyModel =
targetAdapter.
getPropertyModel(
attribute.
name);
Class<?>
type =
targetAdapter.
getType(
attribute.
name);
if (
propertyModel instanceof
Property<?>) {
((
Property<
Object>)
propertyModel).
bind(new
ExpressionValue(
namespace,
expression,
type));
}
}
} else if (
isBidirectionalBindingExpression(
value)) {
throw
constructLoadException(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
constructLoadException("Invalid escape sequence.");
}
return
aValue;
} else if (
aValue.
startsWith(
RELATIVE_PATH_PREFIX)) {
aValue =
aValue.
substring(
RELATIVE_PATH_PREFIX.
length());
if (
aValue.
length() == 0) {
throw
constructLoadException("Missing relative path.");
}
if (
aValue.
startsWith(
RELATIVE_PATH_PREFIX)) {
// The prefix was escaped
warnDeprecatedEscapeSequence(
RELATIVE_PATH_PREFIX);
return
aValue;
} else {
if (
aValue.
charAt(0) == '/') {
final
URL res =
getClassLoader().
getResource(
aValue.
substring(1));
if (
res == null) {
throw
constructLoadException("Invalid resource: " +
aValue + " not found on the classpath");
}
return
res.
toString();
} else {
try {
return 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
constructLoadException("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
constructLoadException("No resources specified.");
}
if (!
resources.
containsKey(
aValue)) {
throw
constructLoadException("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
constructLoadException("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(
Class<?>
type,
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<
Object>
list = (
List<
Object>)
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);
}
}
private
Object getExpressionObject(
String handlerValue) throws
LoadException{
if (
handlerValue.
startsWith(
EXPRESSION_PREFIX)) {
handlerValue =
handlerValue.
substring(
EXPRESSION_PREFIX.
length());
if (
handlerValue.
length() == 0) {
throw
constructLoadException("Missing expression reference.");
}
Object expression =
Expression.
get(
namespace,
KeyPath.
parse(
handlerValue));
if (
expression == null) {
throw
constructLoadException("Unable to resolve expression : $" +
handlerValue);
}
return
expression;
}
return null;
}
private <T> T
getExpressionObjectOfType(
String handlerValue,
Class<T>
type) throws
LoadException{
Object expression =
getExpressionObject(
handlerValue);
if (
expression != null) {
if (
type.
isInstance(
expression)) {
return (T)
expression;
}
throw
constructLoadException("Error resolving \"" +
handlerValue +"\" expression."
+ "Does not point to a " +
type.
getName());
}
return null;
}
private
MethodHandler getControllerMethodHandle(
String handlerName,
SupportedType...
types) throws
LoadException {
if (
handlerName.
startsWith(
CONTROLLER_METHOD_PREFIX)) {
handlerName =
handlerName.
substring(
CONTROLLER_METHOD_PREFIX.
length());
if (!
handlerName.
startsWith(
CONTROLLER_METHOD_PREFIX)) {
if (
handlerName.
length() == 0) {
throw
constructLoadException("Missing controller method.");
}
if (
controller == null) {
throw
constructLoadException("No controller specified.");
}
for (
SupportedType t :
types) {
Method method =
controllerAccessor
.
getControllerMethods()
.
get(
t)
.
get(
handlerName);
if (
method != null) {
return new
MethodHandler(
controller,
method,
t);
}
}
Method method =
controllerAccessor
.
getControllerMethods()
.
get(
SupportedType.
PARAMETERLESS)
.
get(
handlerName);
if (
method != null) {
return new
MethodHandler(
controller,
method,
SupportedType.
PARAMETERLESS);
}
return null;
}
}
return null;
}
public void
processEventHandlerAttributes() throws
LoadException {
if (
eventHandlerAttributes.
size() > 0 && !
staticLoad) {
for (
Attribute attribute :
eventHandlerAttributes) {
String handlerName =
attribute.
value;
if (
value instanceof
ObservableList &&
attribute.
name.
equals(
COLLECTION_HANDLER_NAME)) {
processObservableListHandler(
handlerName);
} else if (
value instanceof
ObservableMap &&
attribute.
name.
equals(
COLLECTION_HANDLER_NAME)) {
processObservableMapHandler(
handlerName);
} else if (
value instanceof
ObservableSet &&
attribute.
name.
equals(
COLLECTION_HANDLER_NAME)) {
processObservableSetHandler(
handlerName);
} else if (
attribute.
name.
endsWith(
CHANGE_EVENT_HANDLER_SUFFIX)) {
processPropertyHandler(
attribute.
name,
handlerName);
} else {
EventHandler<? extends
Event>
eventHandler = null;
MethodHandler handler =
getControllerMethodHandle(
handlerName,
SupportedType.
EVENT);
if (
handler != null) {
eventHandler = new
ControllerMethodEventHandler<>(
handler);
}
if (
eventHandler == null) {
eventHandler =
getExpressionObjectOfType(
handlerName,
EventHandler.class);
}
if (
eventHandler == null) {
if (
handlerName.
length() == 0 ||
scriptEngine == null) {
throw
constructLoadException("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(
handlerName,
scriptEngine);
}
// Add the handler
getValueAdapter().
put(
attribute.
name,
eventHandler);
}
}
}
}
private void
processObservableListHandler(
String handlerValue) throws
LoadException {
ObservableList list = (
ObservableList)
value;
if (
handlerValue.
startsWith(
CONTROLLER_METHOD_PREFIX)) {
MethodHandler handler =
getControllerMethodHandle(
handlerValue,
SupportedType.
LIST_CHANGE_LISTENER);
if (
handler != null) {
list.
addListener(new
ObservableListChangeAdapter(
handler));
} else {
throw
constructLoadException("Controller method \"" +
handlerValue + "\" not found.");
}
} else if (
handlerValue.
startsWith(
EXPRESSION_PREFIX)) {
Object listener =
getExpressionObject(
handlerValue);
if (
listener instanceof
ListChangeListener) {
list.
addListener((
ListChangeListener)
listener);
} else if (
listener instanceof
InvalidationListener) {
list.
addListener((
InvalidationListener)
listener);
} else {
throw
constructLoadException("Error resolving \"" +
handlerValue + "\" expression."
+ "Must be either ListChangeListener or InvalidationListener");
}
}
}
private void
processObservableMapHandler(
String handlerValue) throws
LoadException {
ObservableMap map = (
ObservableMap)
value;
if (
handlerValue.
startsWith(
CONTROLLER_METHOD_PREFIX)) {
MethodHandler handler =
getControllerMethodHandle(
handlerValue,
SupportedType.
MAP_CHANGE_LISTENER);
if (
handler != null) {
map.
addListener(new
ObservableMapChangeAdapter(
handler));
} else {
throw
constructLoadException("Controller method \"" +
handlerValue + "\" not found.");
}
} else if (
handlerValue.
startsWith(
EXPRESSION_PREFIX)) {
Object listener =
getExpressionObject(
handlerValue);
if (
listener instanceof
MapChangeListener) {
map.
addListener((
MapChangeListener)
listener);
} else if (
listener instanceof
InvalidationListener) {
map.
addListener((
InvalidationListener)
listener);
} else {
throw
constructLoadException("Error resolving \"" +
handlerValue + "\" expression."
+ "Must be either MapChangeListener or InvalidationListener");
}
}
}
private void
processObservableSetHandler(
String handlerValue) throws
LoadException {
ObservableSet set = (
ObservableSet)
value;
if (
handlerValue.
startsWith(
CONTROLLER_METHOD_PREFIX)) {
MethodHandler handler =
getControllerMethodHandle(
handlerValue,
SupportedType.
SET_CHANGE_LISTENER);
if (
handler != null) {
set.
addListener(new
ObservableSetChangeAdapter(
handler));
} else {
throw
constructLoadException("Controller method \"" +
handlerValue + "\" not found.");
}
} else if (
handlerValue.
startsWith(
EXPRESSION_PREFIX)) {
Object listener =
getExpressionObject(
handlerValue);
if (
listener instanceof
SetChangeListener) {
set.
addListener((
SetChangeListener)
listener);
} else if (
listener instanceof
InvalidationListener) {
set.
addListener((
InvalidationListener)
listener);
} else {
throw
constructLoadException("Error resolving \"" +
handlerValue + "\" expression."
+ "Must be either SetChangeListener or InvalidationListener");
}
}
}
private void
processPropertyHandler(
String attributeName,
String handlerValue) throws
LoadException {
int
i =
EVENT_HANDLER_PREFIX.
length();
int
j =
attributeName.
length() -
CHANGE_EVENT_HANDLER_SUFFIX.
length();
if (
i !=
j) {
String key =
Character.
toLowerCase(
attributeName.
charAt(
i))
+
attributeName.
substring(
i + 1,
j);
ObservableValue<
Object>
propertyModel =
getValueAdapter().
getPropertyModel(
key);
if (
propertyModel == null) {
throw
constructLoadException(
value.
getClass().
getName() + " does not define"
+ " a property model for \"" +
key + "\".");
}
if (
handlerValue.
startsWith(
CONTROLLER_METHOD_PREFIX)) {
final
MethodHandler handler =
getControllerMethodHandle(
handlerValue,
SupportedType.
PROPERTY_CHANGE_LISTENER,
SupportedType.
EVENT);
if (
handler != null) {
if (
handler.
type ==
SupportedType.
EVENT) {
// Note: this part is solely for purpose of 2.2 backward compatibility where an Event object
// has been used instead of usual property change parameters
propertyModel.
addListener(new
ChangeListener<
Object>() {
@
Override
public void
changed(
ObservableValue<?>
observable,
Object oldValue,
Object newValue) {
handler.
invoke(new
Event(
value, null,
Event.
ANY));
}
});
} else {
propertyModel.
addListener(new
PropertyChangeAdapter(
handler));
}
} else {
throw
constructLoadException("Controller method \"" +
handlerValue + "\" not found.");
}
} else if (
handlerValue.
startsWith(
EXPRESSION_PREFIX)) {
Object listener =
getExpressionObject(
handlerValue);
if (
listener instanceof
ChangeListener) {
propertyModel.
addListener((
ChangeListener)
listener);
} else if (
listener instanceof
InvalidationListener) {
propertyModel.
addListener((
InvalidationListener)
listener);
} else {
throw
constructLoadException("Error resolving \"" +
handlerValue + "\" expression."
+ "Must be either ChangeListener or InvalidationListener");
}
}
}
}
}
// 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<
Object>
builder = (
Builder<
Object>)
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
constructLoadException("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<
String,
Object>
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
injectFields(
fx_id,
value);
}
}
@
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<
Object>
list = (
List<
Object>)
valueAdapter.
get(
defaultPropertyName);
list.
add(
getListValue(this,
defaultPropertyName,
text));
} else {
valueAdapter.
put(
defaultPropertyName,
text.
trim());
}
} else {
throw
constructLoadException(
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
constructLoadException("Invalid identifier.");
}
for (int
i = 0,
n =
value.
length();
i <
n;
i++) {
if (!
Character.
isJavaIdentifierPart(
value.
charAt(
i))) {
throw
constructLoadException("Invalid identifier.");
}
}
fx_id =
value;
} else if (
localName.
equals(
FX_CONTROLLER_ATTRIBUTE)) {
if (
current.
parent != null) {
throw
constructLoadException(
FX_NAMESPACE_PREFIX + ":" +
FX_CONTROLLER_ATTRIBUTE
+ " can only be applied to root element.");
}
if (
controller != null) {
throw
constructLoadException("Controller value already specified.");
}
if (!
staticLoad) {
Class<?>
type;
try {
type =
getClassLoader().
loadClass(
value);
} catch (
ClassNotFoundException exception) {
throw
constructLoadException(
exception);
}
try {
if (
controllerFactory == null) {
setController(
ReflectUtil.
newInstance(
type));
} else {
setController(
controllerFactory.
call(
type));
}
} catch (
InstantiationException exception) {
throw
constructLoadException(
exception);
} catch (
IllegalAccessException exception) {
throw
constructLoadException(
exception);
}
}
} else {
throw
constructLoadException("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
constructLoadException(
exception);
}
try {
value =
MethodUtil.
invoke(
factoryMethod, null, new
Object [] {});
} catch (
IllegalAccessException exception) {
throw
constructLoadException(
exception);
} catch (
InvocationTargetException exception) {
throw
constructLoadException(
exception);
}
} else {
value = (
builderFactory == null) ? null :
builderFactory.
getBuilder(
type);
if (
value == null) {
value =
DEFAULT_BUILDER_FACTORY.
getBuilder(
type);
}
if (
value == null) {
try {
value =
ReflectUtil.
newInstance(
type);
} catch (
InstantiationException exception) {
throw
constructLoadException(
exception);
} catch (
IllegalAccessException exception) {
throw
constructLoadException(
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<
String,
Object> {
private
ArrayList<?>
items = new
ArrayList<
Object>();
private
HashMap<
String,
Object>
values = new
HashMap<
String,
Object>();
@
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<
Entry<
String,
Object>>
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
constructLoadException(
INCLUDE_SOURCE_ATTRIBUTE + " is required.");
}
URL location;
final
ClassLoader cl =
getClassLoader();
if (
source.
charAt(0) == '/') {
location =
cl.
getResource(
source.
substring(1));
if (
location == null) {
throw
constructLoadException("Cannot resolve path: " +
source);
}
} else {
if (
FXMLLoader.this.
location == null) {
throw
constructLoadException("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(
cl);
fxmlLoader.
impl_setStaticLoad(
staticLoad);
Object value =
fxmlLoader.
loadImpl(
callerClass);
if (
fx_id != null) {
String id = this.
fx_id +
CONTROLLER_SUFFIX;
Object controller =
fxmlLoader.
getController();
namespace.
put(
id,
controller);
injectFields(
id,
controller);
}
return
value;
}
}
private void
injectFields(
String fieldName,
Object value) throws
LoadException {
if (
controller != null &&
fieldName != null) {
List<
Field>
fields =
controllerAccessor.
getControllerFields().
get(
fieldName);
if (
fields != null) {
try {
for (
Field f :
fields) {
f.
set(
controller,
value);
}
} catch (
IllegalAccessException exception) {
throw
constructLoadException(
exception);
}
}
}
}
// 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
constructLoadException(
REFERENCE_SOURCE_ATTRIBUTE + " is required.");
}
KeyPath path =
KeyPath.
parse(
source);
if (!
Expression.
isDefined(
namespace,
path)) {
throw
constructLoadException("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
constructLoadException(
COPY_SOURCE_ATTRIBUTE + " is required.");
}
KeyPath path =
KeyPath.
parse(
source);
if (!
Expression.
isDefined(
namespace,
path)) {
throw
constructLoadException("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
constructLoadException(
exception);
} catch (
IllegalAccessException exception) {
throw
constructLoadException(
exception);
} catch (
InvocationTargetException exception) {
throw
constructLoadException(
exception);
}
} else {
throw
constructLoadException("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
constructLoadException(
ROOT_TYPE_ATTRIBUTE + " is required.");
}
Class<?>
type =
getType(this.
type);
if (
type == null) {
throw
constructLoadException(this.
type + " is not a valid type.");
}
Object value;
if (
root == null) {
if (
staticLoad) {
value = (
builderFactory == null) ? null :
builderFactory.
getBuilder(
type);
if (
value == null) {
value =
DEFAULT_BUILDER_FACTORY.
getBuilder(
type);
}
if (
value == null) {
try {
value =
ReflectUtil.
newInstance(
type);
} catch (
InstantiationException exception) {
throw
constructLoadException(
exception);
} catch (
IllegalAccessException exception) {
throw
constructLoadException(
exception);
}
}
root =
value;
} else {
throw
constructLoadException("Root hasn't been set. Use method setRoot() before load.");
}
} else {
if (!
type.
isAssignableFrom(
root.
getClass())) {
throw
constructLoadException("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
constructLoadException("Invalid root element.");
}
if (
parent.
value == null) {
throw
constructLoadException("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
constructLoadException("\"" +
name + "\" is not a valid element name.");
}
Map<
String,
Object>
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
constructLoadException("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
constructLoadException("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 {
String text =
xmlStreamReader.
getText();
text =
extraneousWhitespacePattern.
matcher(
text).
replaceAll(" ").
trim();
if (
readOnly) {
if (
isCollection()) {
add(
text);
} else {
super.processCharacters();
}
} else {
set(
text);
}
}
}
// Element representing an unknown static property
private class
UnknownStaticPropertyElement extends
Element {
public
UnknownStaticPropertyElement() throws
LoadException {
if (
parent == null) {
throw
constructLoadException("Invalid root element.");
}
if (
parent.
value == null) {
throw
constructLoadException("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
constructLoadException("Cannot determine type of script \""
+
source + "\".");
}
String extension =
source.
substring(
i + 1);
ScriptEngine engine;
final
ClassLoader cl =
getClassLoader();
if (
scriptEngine != null &&
scriptEngine.
getFactory().
getExtensions().
contains(
extension)) {
// If we have a page language and it's engine supports the extension, use the same engine
engine =
scriptEngine;
} else {
ClassLoader oldLoader =
Thread.
currentThread().
getContextClassLoader();
try {
Thread.
currentThread().
setContextClassLoader(
cl);
ScriptEngineManager scriptEngineManager =
getScriptEngineManager();
engine =
scriptEngineManager.
getEngineByExtension(
extension);
} finally {
Thread.
currentThread().
setContextClassLoader(
oldLoader);
}
}
if (
engine == null) {
throw
constructLoadException("Unable to locate scripting engine for"
+ " extension " +
extension + ".");
}
try {
URL location;
if (
source.
charAt(0) == '/') {
location =
cl.
getResource(
source.
substring(1));
} else {
if (
FXMLLoader.this.
location == null) {
throw
constructLoadException("Base location is undefined.");
}
location = new
URL(
FXMLLoader.this.
location,
source);
}
InputStreamReader scriptReader = null;
try {
scriptReader = new
InputStreamReader(
location.
openStream(),
charset);
engine.
eval(
scriptReader);
} catch(
ScriptException exception) {
exception.
printStackTrace();
} finally {
if (
scriptReader != null) {
scriptReader.
close();
}
}
} catch (
IOException exception) {
throw
constructLoadException(
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
constructLoadException("Script source already specified.");
}
if (
scriptEngine == null && !
staticLoad) {
throw
constructLoadException("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
constructLoadException(
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
constructLoadException("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<T extends
Event> implements
EventHandler<T> {
private final
MethodHandler handler;
public
ControllerMethodEventHandler(
MethodHandler handler) {
this.
handler =
handler;
}
@
Override
public void
handle(T
event) {
handler.
invoke(
event);
}
}
// Event handler implemented in script code
private static class
ScriptEventHandler implements
EventHandler<
Event> {
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);
localBindings.
putAll(
engineBindings);
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 {
private final
MethodHandler handler;
public
ObservableListChangeAdapter(
MethodHandler handler) {
this.
handler =
handler;
}
@
Override
@
SuppressWarnings("unchecked")
public void
onChanged(
Change change) {
if (
handler != null) {
handler.
invoke(
change);
}
}
}
// Observable map change listener
private static class
ObservableMapChangeAdapter implements
MapChangeListener {
public final
MethodHandler handler;
public
ObservableMapChangeAdapter(
MethodHandler handler) {
this.
handler =
handler;
}
@
Override
public void
onChanged(
Change change) {
if (
handler != null) {
handler.
invoke(
change);
}
}
}
// Observable set change listener
private static class
ObservableSetChangeAdapter implements
SetChangeListener {
public final
MethodHandler handler;
public
ObservableSetChangeAdapter(
MethodHandler handler) {
this.
handler =
handler;
}
@
Override
public void
onChanged(
Change change) {
if (
handler != null) {
handler.
invoke(
change);
}
}
}
// Property model change listener
private static class
PropertyChangeAdapter implements
ChangeListener<
Object> {
public final
MethodHandler handler;
public
PropertyChangeAdapter(
MethodHandler handler) {
this.
handler =
handler;
}
@
Override
public void
changed(
ObservableValue<? extends
Object>
observable,
Object oldValue,
Object newValue) {
handler.
invoke(
observable,
oldValue,
newValue);
}
}
private static class
MethodHandler {
private final
Object controller;
private final
Method method;
private final
SupportedType type;
private
MethodHandler(
Object controller,
Method method,
SupportedType type) {
this.
method =
method;
this.
controller =
controller;
this.
type =
type;
}
public void
invoke(
Object...
params) {
try {
if (
type !=
SupportedType.
PARAMETERLESS) {
MethodUtil.
invoke(
method,
controller,
params);
} else {
MethodUtil.
invoke(
method,
controller, new
Object[] {});
}
} catch (
InvocationTargetException exception) {
throw new
RuntimeException(
exception);
} catch (
IllegalAccessException exception) {
throw new
RuntimeException(
exception);
}
}
}
private
URL location;
private
ResourceBundle resources;
private
ObservableMap<
String,
Object>
namespace =
FXCollections.
observableHashMap();
private
Object root = null;
private
Object controller = null;
private
BuilderFactory builderFactory;
private
Callback<
Class<?>,
Object>
controllerFactory;
private
Charset charset;
private final
LinkedList<
FXMLLoader>
loaders;
private
ClassLoader classLoader = null;
private boolean
staticLoad = false;
private
LoadListener loadListener = null;
private
FXMLLoader parentLoader;
private
XMLStreamReader xmlStreamReader = null;
private
Element current = null;
private
ScriptEngine scriptEngine = null;
private
List<
String>
packages = new
LinkedList<
String>();
private
Map<
String,
Class<?>>
classes = new
HashMap<
String,
Class<?>>();
private
ScriptEngineManager scriptEngineManager = null;
private static
ClassLoader defaultClassLoader = null;
private static final
Pattern extraneousWhitespacePattern =
Pattern.
compile("\\s+");
private static
BuilderFactory DEFAULT_BUILDER_FACTORY = new
JavaFXBuilderFactory();
/**
* The character set used when character set is not explicitly specified
*/
public static final
String DEFAULT_CHARSET_NAME = "UTF-8";
/**
* The tag name of language processing instruction
*/
public static final
String LANGUAGE_PROCESSING_INSTRUCTION = "language";
/**
* The tag name of import processing instruction
*/
public static final
String IMPORT_PROCESSING_INSTRUCTION = "import";
/**
* Prefix of 'fx' namespace
*/
public static final
String FX_NAMESPACE_PREFIX = "fx";
/**
* The name of fx:controller attribute of a root
*/
public static final
String FX_CONTROLLER_ATTRIBUTE = "controller";
/**
* The name of fx:id attribute
*/
public static final
String FX_ID_ATTRIBUTE = "id";
/**
* The name of fx:value attribute
*/
public static final
String FX_VALUE_ATTRIBUTE = "value";
/**
* The tag name of 'fx:constant'
* @since JavaFX 2.2
*/
public static final
String FX_CONSTANT_ATTRIBUTE = "constant";
/**
* The name of 'fx:factory' attribute
*/
public static final
String FX_FACTORY_ATTRIBUTE = "factory";
/**
* The tag name of <fx:include>
*/
public static final
String INCLUDE_TAG = "include";
/**
* <fx:include> 'source' attribute
*/
public static final
String INCLUDE_SOURCE_ATTRIBUTE = "source";
/**
* <fx:include> 'resources' attribute
*/
public static final
String INCLUDE_RESOURCES_ATTRIBUTE = "resources";
/**
* <fx:include> 'charset' attribute
*/
public static final
String INCLUDE_CHARSET_ATTRIBUTE = "charset";
/**
* The tag name of <fx:script>
*/
public static final
String SCRIPT_TAG = "script";
/**
* <fx:script> 'source' attribute
*/
public static final
String SCRIPT_SOURCE_ATTRIBUTE = "source";
/**
* <fx:script> 'charset' attribute
*/
public static final
String SCRIPT_CHARSET_ATTRIBUTE = "charset";
/**
* The tag name of <fx:define>
*/
public static final
String DEFINE_TAG = "define";
/**
* The tag name of <fx:reference>
*/
public static final
String REFERENCE_TAG = "reference";
/**
* <fx:reference> 'source' attribute
*/
public static final
String REFERENCE_SOURCE_ATTRIBUTE = "source";
/**
* The tag name of <fx:root>
* @since JavaFX 2.2
*/
public static final
String ROOT_TAG = "root";
/**
* <fx:root> 'type' attribute
* @since JavaFX 2.2
*/
public static final
String ROOT_TYPE_ATTRIBUTE = "type";
/**
* The tag name of <fx:copy>
*/
public static final
String COPY_TAG = "copy";
/**
* <fx:copy> 'source' attribute
*/
public static final
String COPY_SOURCE_ATTRIBUTE = "source";
/**
* The prefix of event handler attributes
*/
public static final
String EVENT_HANDLER_PREFIX = "on";
/**
* The name of the Event object in event handler scripts
*/
public static final
String EVENT_KEY = "event";
/**
* Suffix for property change/invalidation handlers
*/
public static final
String CHANGE_EVENT_HANDLER_SUFFIX = "Change";
private static final
String COLLECTION_HANDLER_NAME =
EVENT_HANDLER_PREFIX +
CHANGE_EVENT_HANDLER_SUFFIX;
/**
* Value that represents 'null'
*/
public static final
String NULL_KEYWORD = "null";
/**
* Escape prefix for escaping special characters inside attribute values.
* Serves as an escape for {@link #ESCAPE_PREFIX}, {@link #RELATIVE_PATH_PREFIX},
* {@link #RESOURCE_KEY_PREFIX, {@link #EXPRESSION_PREFIX}, {@link #BI_DIRECTIONAL_BINDING_PREFIX}
* @since JavaFX 2.1
*/
public static final
String ESCAPE_PREFIX = "\\";
/**
* Prefix for relative location resultion
*/
public static final
String RELATIVE_PATH_PREFIX = "@";
/**
* Prefix for resource resolution
*/
public static final
String RESOURCE_KEY_PREFIX = "%";
/**
* Prefix for (variable) expression resolution
*/
public static final
String EXPRESSION_PREFIX = "$";
/**
* Prefix for binding expression resolution
*/
public static final
String BINDING_EXPRESSION_PREFIX = "${";
/**
* Suffix for binding expression resolution
*/
public static final
String BINDING_EXPRESSION_SUFFIX = "}";
/**
* Prefix for bidirectional-binding expression resolution
* @since JavaFX 2.1
*/
public static final
String BI_DIRECTIONAL_BINDING_PREFIX = "#{";
/**
* Suffix for bidirectional-binding expression resolution
* @since JavaFX 2.1
*/
public static final
String BI_DIRECTIONAL_BINDING_SUFFIX = "}";
/**
* Delimiter for arrays as values
* @since JavaFX 2.1
*/
public static final
String ARRAY_COMPONENT_DELIMITER = ",";
/**
* A key for location URL in namespace map
* @see #getNamespace()
* @since JavaFX 2.2
*/
public static final
String LOCATION_KEY = "location";
/**
* A key for ResourceBundle in namespace map
* @see #getNamespace()
* @since JavaFX 2.2
*/
public static final
String RESOURCES_KEY = "resources";
/**
* Prefix for controller method resolution
*/
public static final
String CONTROLLER_METHOD_PREFIX = "#";
/**
* A key for controller in namespace map
* @see #getNamespace()
* @since JavaFX 2.1
*/
public static final
String CONTROLLER_KEYWORD = "controller";
/**
* A suffix for controllers of included fxml files.
* The full key is stored in namespace map.
* @see #getNamespace()
* @since JavaFX 2.2
*/
public static final
String CONTROLLER_SUFFIX = "Controller";
/**
* The name of initialize method
* @since JavaFX 2.2
*/
public static final
String INITIALIZE_METHOD_NAME = "initialize";
/**
* Contains the current javafx version
* @since JavaFX 8.0
*/
public static final
String JAVAFX_VERSION;
/**
* Contains the current fx namepsace version
* @since JavaFX 8.0
*/
public static final
String FX_NAMESPACE_VERSION = "1";
static {
JAVAFX_VERSION =
AccessController.
doPrivileged(new
PrivilegedAction<
String>() {
@
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, null);
}
/**
* 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<
Class<?>,
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<
Class<?>,
Object>
controllerFactory,
Charset charset) {
this(
location,
resources,
builderFactory,
controllerFactory,
charset,
new
LinkedList<
FXMLLoader>());
}
/**
* 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<
Class<?>,
Object>
controllerFactory,
Charset charset,
LinkedList<
FXMLLoader>
loaders) {
setLocation(
location);
setResources(
resources);
setBuilderFactory(
builderFactory);
setControllerFactory(
controllerFactory);
setCharset(
charset);
this.
loaders = new
LinkedList(
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<
String,
Object>
getNamespace() {
return
namespace;
}
/**
* Returns the root of the object hierarchy.
*/
@
SuppressWarnings("unchecked")
public <T> 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 <tt><fx:root></tt> tag. This method
* must be called prior to loading the document when using
* <tt><fx:root></tt>.
*
* @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;
if (
location == null ||
loader.
location == null) {
return
loader.
location ==
location;
}
return
location.
toExternalForm().
equals(
loader.
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> 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 <tt>fx:controller</tt> attribute.
* This method must be called prior to loading the document when using
* controller event handlers when an <tt>fx:controller</tt> 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);
}
controllerAccessor.
setController(
controller);
}
/**
* 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<
Class<?>,
Object>
getControllerFactory() {
return
controllerFactory;
}
/**
* Sets the controller factory used by this serializer.
*
* @param controllerFactory
* @since JavaFX 2.1
*/
public void
setControllerFactory(
Callback<
Class<?>,
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
*/
@
CallerSensitive
public
ClassLoader getClassLoader() {
if (
classLoader == null) {
final
SecurityManager sm =
System.
getSecurityManager();
final
Class caller = (
sm != null) ?
Reflection.
getCallerClass() :
null;
return
getDefaultClassLoader(
caller);
}
return
classLoader;
}
/**
* Sets the classloader used by this serializer and clears any existing
* imports
*
* @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
impl_isStaticLoad() {
// SB-dependency: RT-21226 has been filed to track this
return
staticLoad;
}
/**
* Sets the static load flag.
*
* @param staticLoad
*
* @treatAsPrivate
* @deprecated
*/
public void
impl_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 impl_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
impl_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)}.
*
* @return
* The loaded object hierarchy.
* @since JavaFX 2.1
*/
@
CallerSensitive
public <T> T
load() throws
IOException {
return
loadImpl((
System.
getSecurityManager() != null)
?
Reflection.
getCallerClass()
: null);
}
/**
* Loads an object hierarchy from a FXML document.
*
* @param inputStream
* An input stream containing the FXML data to load.
*
* @return
* The loaded object hierarchy.
*/
@
CallerSensitive
public <T> T
load(
InputStream inputStream) throws
IOException {
return
loadImpl(
inputStream, (
System.
getSecurityManager() != null)
?
Reflection.
getCallerClass()
: null);
}
private
Class<?>
callerClass;
private <T> T
loadImpl(final
Class<?>
callerClass) throws
IOException {
if (
location == null) {
throw new
IllegalStateException("Location is not set.");
}
InputStream inputStream = null;
T
value;
try {
inputStream =
location.
openStream();
value =
loadImpl(
inputStream,
callerClass);
} finally {
if (
inputStream != null) {
inputStream.
close();
}
}
return
value;
}
@
SuppressWarnings({ "dep-ann", "unchecked" })
private <T> T
loadImpl(
InputStream inputStream,
Class<?>
callerClass) throws
IOException {
if (
inputStream == null) {
throw new
NullPointerException("inputStream is null.");
}
this.
callerClass =
callerClass;
controllerAccessor.
setCallerClass(
callerClass);
try {
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
constructLoadException(
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
constructLoadException(
exception);
}
if (
controller != null) {
if (
controller instanceof
Initializable) {
((
Initializable)
controller).
initialize(
location,
resources);
} else {
// Inject controller fields
Map<
String,
List<
Field>>
controllerFields =
controllerAccessor.
getControllerFields();
injectFields(
LOCATION_KEY,
location);
injectFields(
RESOURCES_KEY,
resources);
// Initialize the controller
Method initializeMethod =
controllerAccessor
.
getControllerMethods()
.
get(
SupportedType.
PARAMETERLESS)
.
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 constructLoadException(exception);
} catch (
InvocationTargetException exception) {
throw
constructLoadException(
exception);
}
}
}
}
} catch (final
LoadException exception) {
throw
exception;
} catch (final
Exception exception) {
throw
constructLoadException(
exception);
} finally {
controllerAccessor.
setCallerClass(null);
// Clear controller accessor caches
controllerAccessor.
reset();
// Clear the parser
xmlStreamReader = null;
}
return (T)
root;
}
private void
clearImports() {
packages.
clear();
classes.
clear();
}
private
LoadException constructLoadException(
String message){
return new
LoadException(
message +
constructFXMLTrace());
}
private
LoadException constructLoadException(
Throwable cause) {
return new
LoadException(
constructFXMLTrace(),
cause);
}
private
LoadException constructLoadException(
String message,
Throwable cause){
return new
LoadException(
message +
constructFXMLTrace(),
cause);
}
private
String constructFXMLTrace() {
StringBuilder messageBuilder = new
StringBuilder("\n");
for (
FXMLLoader loader :
loaders) {
messageBuilder.
append(
loader.
location != null ?
loader.
location.
getPath() : "unknown path");
if (
loader.
current != null) {
messageBuilder.
append(":");
messageBuilder.
append(
loader.
impl_getLineNumber());
}
messageBuilder.
append("\n");
}
return
messageBuilder.
toString();
}
/**
* Returns the current line number.
*
* @treatAsPrivate
* @deprecated
* @since JavaFX 2.2
*/
public int
impl_getLineNumber() {
return
xmlStreamReader.
getLocation().
getLineNumber();
}
/**
* Returns the current parse trace.
*
* @treatAsPrivate
* @deprecated
* @since JavaFX 2.1
*/
// SB-dependency: RT-21475 has been filed to track this
public
ParseTraceElement[]
impl_getParseTrace() {
ParseTraceElement[]
parseTrace = new
ParseTraceElement[
loaders.
size()];
int
i = 0;
for (
FXMLLoader loader :
loaders) {
parseTrace[
i++] = new
ParseTraceElement(
loader.
location, (
loader.
current != null) ?
loader.
impl_getLineNumber() : -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
constructLoadException("Page language already set.");
}
String language =
xmlStreamReader.
getPIData();
if (
loadListener != null) {
loadListener.
readLanguageProcessingInstruction(
language);
}
if (!
staticLoad) {
ScriptEngineManager scriptEngineManager =
getScriptEngineManager();
scriptEngine =
scriptEngineManager.
getEngineByName(
language);
}
}
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
constructLoadException(
localName + " is not a valid property.");
}
}
} else {
if (
current == null &&
root != null) {
throw
constructLoadException("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
constructLoadException(
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
constructLoadException(
prefix + ":" +
localName + " is not a valid element.");
}
} else {
throw
constructLoadException("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
constructLoadException(
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
getClassLoader().
loadClass(
packageName + "." +
className.
replace('.', '$'));
}
private static enum
SupportedType {
PARAMETERLESS {
@
Override
protected boolean
methodIsOfType(
Method m) {
return
m.
getParameterTypes().length == 0;
}
},
EVENT {
@
Override
protected boolean
methodIsOfType(
Method m) {
return
m.
getParameterTypes().length == 1 &&
Event.class.
isAssignableFrom(
m.
getParameterTypes()[0]);
}
},
LIST_CHANGE_LISTENER {
@
Override
protected boolean
methodIsOfType(
Method m) {
return
m.
getParameterTypes().length == 1 &&
m.
getParameterTypes()[0].
equals(
ListChangeListener.
Change.class);
}
},
MAP_CHANGE_LISTENER {
@
Override
protected boolean
methodIsOfType(
Method m) {
return
m.
getParameterTypes().length == 1 &&
m.
getParameterTypes()[0].
equals(
MapChangeListener.
Change.class);
}
},
SET_CHANGE_LISTENER {
@
Override
protected boolean
methodIsOfType(
Method m) {
return
m.
getParameterTypes().length == 1 &&
m.
getParameterTypes()[0].
equals(
SetChangeListener.
Change.class);
}
},
PROPERTY_CHANGE_LISTENER {
@
Override
protected boolean
methodIsOfType(
Method m) {
return
m.
getParameterTypes().length == 3 &&
ObservableValue.class.
isAssignableFrom(
m.
getParameterTypes()[0])
&&
m.
getParameterTypes()[1].
equals(
m.
getParameterTypes()[2]);
}
};
protected abstract boolean
methodIsOfType(
Method m);
}
private static
SupportedType toSupportedType(
Method m) {
for (
SupportedType t :
SupportedType.
values()) {
if (
t.
methodIsOfType(
m)) {
return
t;
}
}
return null;
}
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,
getDefaultClassLoader());
}
private static boolean
needsClassLoaderPermissionCheck(
ClassLoader from,
ClassLoader to) {
if (
from ==
to) {
return false;
}
if (
from == null) {
return false;
}
if (
to == null) {
return true;
}
ClassLoader acl =
to;
do {
acl =
acl.
getParent();
if (
from ==
acl) {
return false;
}
} while (
acl != null);
return true;
}
private static
ClassLoader getDefaultClassLoader(
Class caller) {
if (
defaultClassLoader == null) {
final
SecurityManager sm =
System.
getSecurityManager();
if (
sm != null) {
final
ClassLoader callerClassLoader = (
caller != null) ?
caller.
getClassLoader() :
null;
if (
needsClassLoaderPermissionCheck(
callerClassLoader,
FXMLLoader.class.
getClassLoader())) {
sm.
checkPermission(
GET_CLASSLOADER_PERMISSION);
}
}
return
Thread.
currentThread().
getContextClassLoader();
}
return
defaultClassLoader;
}
/**
* Returns the default class loader.
* @since JavaFX 2.1
*/
@
CallerSensitive
public static
ClassLoader getDefaultClassLoader() {
final
SecurityManager sm =
System.
getSecurityManager();
final
Class caller = (
sm != null) ?
Reflection.
getCallerClass() :
null;
return
getDefaultClassLoader(
caller);
}
/**
* 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();
}
final
SecurityManager sm =
System.
getSecurityManager();
if (
sm != null) {
sm.
checkPermission(new
AllPermission());
}
FXMLLoader.
defaultClassLoader =
defaultClassLoader;
}
/**
* Loads an object hierarchy from a FXML document.
*
* @param location
*/
@
CallerSensitive
public static <T> T
load(
URL location) throws
IOException {
return
loadImpl(
location, (
System.
getSecurityManager() != null)
?
Reflection.
getCallerClass()
: null);
}
private static <T> T
loadImpl(
URL location,
Class<?>
callerClass)
throws
IOException {
return
loadImpl(
location, null,
callerClass);
}
/**
* Loads an object hierarchy from a FXML document.
*
* @param location
* @param resources
*/
@
CallerSensitive
public static <T> T
load(
URL location,
ResourceBundle resources)
throws
IOException {
return
loadImpl(
location,
resources,
(
System.
getSecurityManager() != null)
?
Reflection.
getCallerClass()
: null);
}
private static <T> T
loadImpl(
URL location,
ResourceBundle resources,
Class<?>
callerClass) throws
IOException {
return
loadImpl(
location,
resources, null,
callerClass);
}
/**
* Loads an object hierarchy from a FXML document.
*
* @param location
* @param resources
* @param builderFactory
*/
@
CallerSensitive
public static <T> T
load(
URL location,
ResourceBundle resources,
BuilderFactory builderFactory)
throws
IOException {
return
loadImpl(
location,
resources,
builderFactory,
(
System.
getSecurityManager() != null)
?
Reflection.
getCallerClass()
: null);
}
private static <T> T
loadImpl(
URL location,
ResourceBundle resources,
BuilderFactory builderFactory,
Class<?>
callerClass) throws
IOException {
return
loadImpl(
location,
resources,
builderFactory, null,
callerClass);
}
/**
* Loads an object hierarchy from a FXML document.
*
* @param location
* @param resources
* @param builderFactory
* @param controllerFactory
* @since JavaFX 2.1
*/
@
CallerSensitive
public static <T> T
load(
URL location,
ResourceBundle resources,
BuilderFactory builderFactory,
Callback<
Class<?>,
Object>
controllerFactory)
throws
IOException {
return
loadImpl(
location,
resources,
builderFactory,
controllerFactory,
(
System.
getSecurityManager() != null)
?
Reflection.
getCallerClass()
: null);
}
private static <T> T
loadImpl(
URL location,
ResourceBundle resources,
BuilderFactory builderFactory,
Callback<
Class<?>,
Object>
controllerFactory,
Class<?>
callerClass) throws
IOException {
return
loadImpl(
location,
resources,
builderFactory,
controllerFactory,
Charset.
forName(
DEFAULT_CHARSET_NAME),
callerClass);
}
/**
* Loads an object hierarchy from a FXML document.
*
* @param location
* @param resources
* @param builderFactory
* @param controllerFactory
* @param charset
* @since JavaFX 2.1
*/
@
CallerSensitive
public static <T> T
load(
URL location,
ResourceBundle resources,
BuilderFactory builderFactory,
Callback<
Class<?>,
Object>
controllerFactory,
Charset charset) throws
IOException {
return
loadImpl(
location,
resources,
builderFactory,
controllerFactory,
charset,
(
System.
getSecurityManager() != null)
?
Reflection.
getCallerClass()
: null);
}
private static <T> T
loadImpl(
URL location,
ResourceBundle resources,
BuilderFactory builderFactory,
Callback<
Class<?>,
Object>
controllerFactory,
Charset charset,
Class<?>
callerClass)
throws
IOException {
if (
location == null) {
throw new
NullPointerException("Location is required.");
}
FXMLLoader fxmlLoader =
new
FXMLLoader(
location,
resources,
builderFactory,
controllerFactory,
charset);
return
fxmlLoader.<T>
loadImpl(
callerClass);
}
/**
* 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;
}
private static void
checkAllPermissions() {
final
SecurityManager securityManager =
System.
getSecurityManager();
if (
securityManager != null) {
securityManager.
checkPermission(new
AllPermission());
}
}
private final
ControllerAccessor controllerAccessor =
new
ControllerAccessor();
private static final class
ControllerAccessor {
private static final int
PUBLIC = 1;
private static final int
PROTECTED = 2;
private static final int
PACKAGE = 4;
private static final int
PRIVATE = 8;
private static final int
INITIAL_CLASS_ACCESS =
PUBLIC |
PROTECTED |
PACKAGE |
PRIVATE;
private static final int
INITIAL_MEMBER_ACCESS =
PUBLIC |
PROTECTED |
PACKAGE |
PRIVATE;
private static final int
METHODS = 0;
private static final int
FIELDS = 1;
private
Object controller;
private
ClassLoader callerClassLoader;
private
Map<
String,
List<
Field>>
controllerFields;
private
Map<
SupportedType,
Map<
String,
Method>>
controllerMethods;
void
setController(final
Object controller) {
if (this.
controller !=
controller) {
this.
controller =
controller;
reset();
}
}
void
setCallerClass(final
Class<?>
callerClass) {
final
ClassLoader newCallerClassLoader =
(
callerClass != null) ?
callerClass.
getClassLoader()
: null;
if (
callerClassLoader !=
newCallerClassLoader) {
callerClassLoader =
newCallerClassLoader;
reset();
}
}
void
reset() {
controllerFields = null;
controllerMethods = null;
}
Map<
String,
List<
Field>>
getControllerFields() {
if (
controllerFields == null) {
controllerFields = new
HashMap<>();
if (
callerClassLoader == null) {
// allow null class loader only with full permission check
checkAllPermissions();
}
addAccessibleMembers(
controller.
getClass(),
INITIAL_CLASS_ACCESS,
INITIAL_MEMBER_ACCESS,
FIELDS);
}
return
controllerFields;
}
Map<
SupportedType,
Map<
String,
Method>>
getControllerMethods() {
if (
controllerMethods == null) {
controllerMethods = new
EnumMap<>(
SupportedType.class);
for (
SupportedType t:
SupportedType.
values()) {
controllerMethods.
put(
t, new
HashMap<
String,
Method>());
}
if (
callerClassLoader == null) {
// allow null class loader only with full permission check
checkAllPermissions();
}
addAccessibleMembers(
controller.
getClass(),
INITIAL_CLASS_ACCESS,
INITIAL_MEMBER_ACCESS,
METHODS);
}
return
controllerMethods;
}
private void
addAccessibleMembers(final
Class<?>
type,
final int
prevAllowedClassAccess,
final int
prevAllowedMemberAccess,
final int
membersType) {
if (
type ==
Object.class) {
return;
}
int
allowedClassAccess =
prevAllowedClassAccess;
int
allowedMemberAccess =
prevAllowedMemberAccess;
if ((
callerClassLoader != null)
&& (
type.
getClassLoader() !=
callerClassLoader)) {
// restrict further access
allowedClassAccess &=
PUBLIC;
allowedMemberAccess &=
PUBLIC;
}
final int
classAccess =
getAccess(
type.
getModifiers());
if ((
classAccess &
allowedClassAccess) == 0) {
// we are done
return;
}
ReflectUtil.
checkPackageAccess(
type);
addAccessibleMembers(
type.
getSuperclass(),
allowedClassAccess,
allowedMemberAccess,
membersType);
final int
finalAllowedMemberAccess =
allowedMemberAccess;
AccessController.
doPrivileged(
new
PrivilegedAction<
Void>() {
@
Override
public
Void run() {
if (
membersType ==
FIELDS) {
addAccessibleFields(
type,
finalAllowedMemberAccess);
} else {
addAccessibleMethods(
type,
finalAllowedMemberAccess);
}
return null;
}
});
}
private void
addAccessibleFields(final
Class<?>
type,
final int
allowedMemberAccess) {
final boolean
isPublicType =
Modifier.
isPublic(
type.
getModifiers());
final
Field[]
fields =
type.
getDeclaredFields();
for (int
i = 0;
i <
fields.length; ++
i) {
final
Field field =
fields[
i];
final int
memberModifiers =
field.
getModifiers();
if (((
memberModifiers & (
Modifier.
STATIC
|
Modifier.
FINAL)) != 0)
|| ((
getAccess(
memberModifiers) &
allowedMemberAccess)
== 0)) {
continue;
}
if (!
isPublicType || !
Modifier.
isPublic(
memberModifiers)) {
if (
field.
getAnnotation(
FXML.class) == null) {
// no fxml annotation on a non-public field
continue;
}
// Ensure that the field is accessible
field.
setAccessible(true);
}
List<
Field>
list =
controllerFields.
get(
field.
getName());
if (
list == null) {
list = new
ArrayList<>(1);
controllerFields.
put(
field.
getName(),
list);
}
list.
add(
field);
}
}
private void
addAccessibleMethods(final
Class<?>
type,
final int
allowedMemberAccess) {
final boolean
isPublicType =
Modifier.
isPublic(
type.
getModifiers());
final
Method[]
methods =
type.
getDeclaredMethods();
for (int
i = 0;
i <
methods.length; ++
i) {
final
Method method =
methods[
i];
final int
memberModifiers =
method.
getModifiers();
if (((
memberModifiers & (
Modifier.
STATIC
|
Modifier.
NATIVE)) != 0)
|| ((
getAccess(
memberModifiers) &
allowedMemberAccess)
== 0)) {
continue;
}
if (!
isPublicType || !
Modifier.
isPublic(
memberModifiers)) {
if (
method.
getAnnotation(
FXML.class) == null) {
// no fxml annotation on a non-public method
continue;
}
// Ensure that the method is accessible
method.
setAccessible(true);
}
// 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
final
String methodName =
method.
getName();
final
SupportedType convertedType;
if ((
convertedType =
toSupportedType(
method)) != null) {
controllerMethods.
get(
convertedType)
.
put(
methodName,
method);
}
}
}
private static int
getAccess(final int
fullModifiers) {
final int
untransformedAccess =
fullModifiers & (
Modifier.
PRIVATE |
Modifier.
PROTECTED
|
Modifier.
PUBLIC);
switch (
untransformedAccess) {
case
Modifier.
PUBLIC:
return
PUBLIC;
case
Modifier.
PROTECTED:
return
PROTECTED;
case
Modifier.
PRIVATE:
return
PRIVATE;
default:
return
PACKAGE;
}
}
}
}