changeset 4957:dc4e8f0255ba

RT-17714 Support collection events in FXML
author Martin Sladecek <martin.sladecek@oracle.com>
date Wed, 04 Sep 2013 14:36:02 +0200
parents bb696d27c662
children 04f6adb4eada 520a712d8a10
files modules/fxml/src/main/java/com/sun/javafx/fxml/ObservableListChangeEvent.java modules/fxml/src/main/java/com/sun/javafx/fxml/ObservableMapChangeEvent.java modules/fxml/src/main/java/javafx/fxml/FXMLLoader.java modules/fxml/src/test/java/javafx/fxml/RT_17714Controller.java modules/fxml/src/test/java/javafx/fxml/RT_17714Controller2.java modules/fxml/src/test/java/javafx/fxml/RT_17714Test.java modules/fxml/src/test/java/javafx/fxml/Widget.java modules/fxml/src/test/resources/javafx/fxml/rt_17714.fxml
diffstat 8 files changed, 465 insertions(+), 452 deletions(-) [+]
line wrap: on
line diff
--- a/modules/fxml/src/main/java/com/sun/javafx/fxml/ObservableListChangeEvent.java	Wed Sep 04 13:53:41 2013 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-/*
- * 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 com.sun.javafx.fxml;
-
-import java.util.List;
-
-import javafx.collections.ObservableList;
-import javafx.event.Event;
-import javafx.event.EventType;
-
-/**
- * Observable list change event.
- */
-public class ObservableListChangeEvent<E> extends Event {
-    private static final long serialVersionUID = 0;
-
-    private EventType<ObservableListChangeEvent<?>> type;
-    private int from;
-    private int to;
-    private List<E> removed;
-
-    public static final EventType<ObservableListChangeEvent<?>> ADD =
-        new EventType<ObservableListChangeEvent<?>>(EventType.ROOT, ObservableListChangeEvent.class.getName() + "_ADD");
-    public static final EventType<ObservableListChangeEvent<?>> UPDATE =
-        new EventType<ObservableListChangeEvent<?>>(EventType.ROOT, ObservableListChangeEvent.class.getName() + "_UPDATE");
-    public static final EventType<ObservableListChangeEvent<?>> REMOVE =
-        new EventType<ObservableListChangeEvent<?>>(EventType.ROOT, ObservableListChangeEvent.class.getName() + "_REMOVE");
-
-    public ObservableListChangeEvent(ObservableList<E> source, EventType<ObservableListChangeEvent<?>> type,
-        int from, int to, List<E> removed) {
-        super(source, null, type);
-
-        this.from = from;
-        this.to = to;
-        this.removed = removed;
-    }
-
-    /**
-     * Returns the first index affected by the change.
-     */
-    public int getFrom() {
-        return from;
-    }
-
-    /**
-     * Returns the index immediately following the last index affected by the
-     * change.
-     */
-    public int getTo() {
-        return to;
-    }
-
-    /**
-     * Returns any elements that were removed as part of the change, or
-     * <tt>null</tt> if either this change did not cause any values to be
-     * removed or the removed values could not be determined.
-     */
-    public List<E> getRemoved() {
-        return removed;
-    }
-
-    @Override
-    public String toString() {
-        return getClass().getName() + " " + type + ": [" + from + ".." + to + ") "
-            + ((removed == null) ? "" : removed);
-    }
-}
--- a/modules/fxml/src/main/java/com/sun/javafx/fxml/ObservableMapChangeEvent.java	Wed Sep 04 13:53:41 2013 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/*
- * 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 com.sun.javafx.fxml;
-
-import javafx.collections.ObservableMap;
-import javafx.event.Event;
-import javafx.event.EventType;
-
-/**
- * Observable map change event.
- */
-public class ObservableMapChangeEvent<K, V> extends Event {
-    private static final long serialVersionUID = 0;
-
-    private EventType<ObservableMapChangeEvent<?, ?>> type;
-    private K key;
-    private V removed;
-
-    public static final EventType<ObservableMapChangeEvent<?, ?>> ADD =
-        new EventType<ObservableMapChangeEvent<?, ?>>(EventType.ROOT, ObservableMapChangeEvent.class.getName() + "_ADD");
-    public static final EventType<ObservableMapChangeEvent<?, ?>> UPDATE =
-        new EventType<ObservableMapChangeEvent<?, ?>>(EventType.ROOT, ObservableMapChangeEvent.class.getName() + "_UPDATE");
-    public static final EventType<ObservableMapChangeEvent<?, ?>> REMOVE =
-        new EventType<ObservableMapChangeEvent<?, ?>>(EventType.ROOT, ObservableMapChangeEvent.class.getName() + "_REMOVE");
-
-    public ObservableMapChangeEvent(ObservableMap<K, V> source, EventType<ObservableMapChangeEvent<?, ?>> type,
-        K key, V removed) {
-        super(source, null, type);
-
-        this.type = type;
-        this.key = key;
-        this.removed = removed;
-    }
-
-    /**
-     * The key associated with the change.
-     */
-    public K getKey() {
-        return key;
-    }
-
-    /**
-     * They value that was removed as part of the change, or <tt>null</tt> if
-     * this change did not cause a value to be removed.
-     */
-    public V getRemoved() {
-        return removed;
-    }
-
-    @Override
-    public String toString() {
-        return getClass().getName() + " " + type + ": " + key + " "
-            + ((removed == null) ? "" : removed);
-    }
-}
--- a/modules/fxml/src/main/java/javafx/fxml/FXMLLoader.java	Wed Sep 04 13:53:41 2013 +0400
+++ b/modules/fxml/src/main/java/javafx/fxml/FXMLLoader.java	Wed Sep 04 14:36:02 2013 +0200
@@ -50,15 +50,12 @@
 import java.util.Set;
 import java.util.regex.Pattern;
 
+import com.sun.javafx.fxml.*;
 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.collections.*;
 import javafx.event.Event;
 import javafx.event.EventHandler;
 import javafx.event.EventType;
@@ -79,12 +76,6 @@
 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;
@@ -506,59 +497,82 @@
             }
         }
 
+        private <T> T getExpressionObjectOfType(String handlerName, Class<T> type) throws LoadException{
+            if (handlerName.startsWith(EXPRESSION_PREFIX)) {
+                handlerName = handlerName.substring(EXPRESSION_PREFIX.length());
+
+                if (handlerName.length() == 0) {
+                    throw new LoadException("Missing expression reference.");
+                }
+
+                Object expression = Expression.get(namespace, KeyPath.parse(handlerName));
+                if (type.isInstance(expression)) {
+                    return (T) expression;
+                }
+            }
+            return null;
+        }
+
+        private <T> MethodHandler<T> getControllerMethodHandle(String handlerName, Class<T> type) 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 new LoadException("Missing controller method.");
+                    }
+
+                    if (controller == null) {
+                        throw new LoadException("No controller specified.");
+                    }
+
+                    Method method = getControllerMethods().get(type).get(handlerName);
+                    if (method == null) {
+                        method = getControllerMethods().get(null).get(handlerName);
+                    }
+
+                    if (method == null) {
+                        return null;
+                    }
+
+                    return new MethodHandler<>(controller, method);
+                }
+
+            }
+            return null;
+        }
+
         public void processEventHandlerAttributes() throws LoadException {
             if (eventHandlerAttributes.size() > 0 && !staticLoad) {
                 for (Attribute attribute : eventHandlerAttributes) {
-                    EventHandler<? extends Event> 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.");
+                    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 {
+                        EventHandler<? extends Event> eventHandler = null;
+                        MethodHandler handler = getControllerMethodHandle(handlerName, Event.class);
+                        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 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.");
                             }
 
-                            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);
+                            eventHandler = new ScriptEventHandler(handlerName, scriptEngine);
                         }
 
-                    } 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<? extends Event>) 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){
+                        // Add the handler
                         addEventHandler(attribute, eventHandler);
                     }
                 }
@@ -572,19 +586,7 @@
                 int i = EVENT_HANDLER_PREFIX.length();
                 int j = attribute.name.length() - CHANGE_EVENT_HANDLER_SUFFIX.length();
 
-                if (i == j) {
-                    if (value instanceof ObservableList<?>) {
-                        ObservableList<Object> list = (ObservableList<Object>)value;
-                        list.addListener(new ObservableListChangeAdapter(list,
-                            (EventHandler<ObservableListChangeEvent<?>>)eventHandler));
-                    } else if (value instanceof ObservableMap<?, ?>) {
-                        ObservableMap<Object, Object> map = (ObservableMap<Object, Object>)value;
-                        map.addListener(new ObservableMapChangeAdapter(map,
-                            (EventHandler<ObservableMapChangeEvent<?, ?>>)eventHandler));
-                    } else {
-                        throw new LoadException("Invalid event source.");
-                    }
-                } else {
+                if (i != j) {
                     String key = Character.toLowerCase(attribute.name.charAt(i))
                         + attribute.name.substring(i + 1, j);
 
@@ -601,6 +603,57 @@
                 getValueAdapter().put(attribute.name, eventHandler);
             }
         }
+
+        private void processObservableListHandler(String handlerName) throws LoadException {
+            ObservableList list = (ObservableList)value;
+            if (handlerName.startsWith(CONTROLLER_METHOD_PREFIX)) {
+                MethodHandler handler = getControllerMethodHandle(handlerName, ListChangeListener.Change.class);
+                if (handler != null) {
+                    list.addListener(new ObservableListChangeAdapter(list, handler));
+                } else {
+                    throw new LoadException("Controller method \"" + handlerName + "\" not found.");
+                }
+            } else if (handlerName.startsWith(EXPRESSION_PREFIX)) {
+                ListChangeListener listener = getExpressionObjectOfType(handlerName, ListChangeListener.class);
+                if (listener != null) {
+                    list.addListener(listener);
+                }
+            }
+        }
+
+        private void processObservableMapHandler(String handlerName) throws LoadException {
+            ObservableMap map = (ObservableMap)value;
+            if (handlerName.startsWith(CONTROLLER_METHOD_PREFIX)) {
+                MethodHandler handler = getControllerMethodHandle(handlerName, MapChangeListener.Change.class);
+                if (handler != null) {
+                    map.addListener(new ObservableMapChangeAdapter(map, handler));
+                } else {
+                    throw new LoadException("Controller method \"" + handlerName + "\" not found.");
+                }
+            } else if (handlerName.startsWith(EXPRESSION_PREFIX)) {
+                MapChangeListener listener = getExpressionObjectOfType(handlerName, MapChangeListener.class);
+                if (listener != null) {
+                    map.addListener(listener);
+                }
+            }
+        }
+
+        private void processObservableSetHandler(String handlerName) throws LoadException {
+            ObservableSet set = (ObservableSet)value;
+            if (handlerName.startsWith(CONTROLLER_METHOD_PREFIX)) {
+                MethodHandler handler = getControllerMethodHandle(handlerName, SetChangeListener.Change.class);
+                if (handler != null) {
+                    set.addListener(new ObservableSetChangeAdapter(set, handler));
+                } else {
+                    throw new LoadException("Controller method \"" + handlerName + "\" not found.");
+                }
+            } else if (handlerName.startsWith(EXPRESSION_PREFIX)) {
+                SetChangeListener listener = getExpressionObjectOfType(handlerName, SetChangeListener.class);
+                if (listener != null) {
+                    set.addListener(listener);
+                }
+            }
+        }
     }
 
     // Element representing a value
@@ -1482,19 +1535,144 @@
     }
 
     // Event handler that delegates to a method defined by the controller object
-    private static class ControllerMethodEventHandler implements EventHandler<Event> {
-        public final Object controller;
-        public final Method method;
-        public final boolean typed;
-
-        public ControllerMethodEventHandler(Object controller, Method method) {
+    private static class ControllerMethodEventHandler<T extends Event> implements EventHandler<T> {
+        private final MethodHandler<T> handler;
+
+        public ControllerMethodEventHandler(MethodHandler<T> 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 ObservableList source;
+        private final MethodHandler<Change> handler;
+
+        public ObservableListChangeAdapter(ObservableList source,
+                                           MethodHandler<Change> handler) {
+            this.source = source;
+            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 ObservableMap source;
+        public final MethodHandler<Change> handler;
+
+        public ObservableMapChangeAdapter(ObservableMap source,
+                                          MethodHandler<Change> handler) {
+            this.source = source;
+            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 ObservableSet source;
+        public final MethodHandler<Change> handler;
+
+        public ObservableSetChangeAdapter(ObservableSet source,
+                                          MethodHandler<Change> handler) {
+            this.source = source;
+            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 Object source;
+        public final EventHandler<PropertyChangeEvent<?>> handler;
+
+        public PropertyChangeAdapter(Object source, EventHandler<PropertyChangeEvent<?>> handler) {
+            if (source == null) {
+                throw new NullPointerException();
+            }
+
+            if (handler == null) {
+                throw new NullPointerException();
+            }
+
+            this.source = source;
+            this.handler = handler;
+        }
+
+        @Override
+        public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) {
+            handler.handle(new PropertyChangeEvent<Object>(source, oldValue));
+        }
+    }
+
+    private static class MethodHandler<E> {
+        private final Object controller;
+        private final Method method;
+        private final boolean typed;
+
+        private MethodHandler(Object controller, Method method) {
+            this.method = method;
             this.controller = controller;
-            this.method = method;
             this.typed = (method.getParameterTypes().length == 1);
         }
 
-        @Override
-        public void handle(Event event) {
+        public void invoke(E event) {
             try {
                 if (typed) {
                     MethodUtil.invoke(method, controller, new Object[] { event });
@@ -1509,128 +1687,6 @@
         }
     }
 
-    // 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<Object> {
-        public final ObservableList<Object> source;
-        public final EventHandler<ObservableListChangeEvent<?>> handler;
-
-        public ObservableListChangeAdapter(ObservableList<Object> source,
-            EventHandler<ObservableListChangeEvent<?>> handler) {
-            this.source = source;
-            this.handler = handler;
-        }
-
-        @Override
-        @SuppressWarnings("unchecked")
-        public void onChanged(Change<? extends Object> change) {
-            while (change.next()) {
-                EventType<ObservableListChangeEvent<?>> eventType;
-                List<Object> removed = (List<Object>)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<Object>(source,
-                    eventType, change.getFrom(), change.getTo(),
-                    removed));
-            }
-        }
-    }
-
-    // Observable map change listener
-    private static class ObservableMapChangeAdapter implements MapChangeListener<Object, Object> {
-        public final ObservableMap<Object, Object> source;
-        public final EventHandler<ObservableMapChangeEvent<?, ?>> handler;
-
-        public ObservableMapChangeAdapter(ObservableMap<Object, Object> source,
-            EventHandler<ObservableMapChangeEvent<?, ?>> handler) {
-            this.source = source;
-            this.handler = handler;
-        }
-
-        @Override
-        public void onChanged(Change<? extends Object, ? extends Object> change) {
-            EventType<ObservableMapChangeEvent<?, ?>> 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<Object, Object>(source,
-                eventType, change.getKey(), change.getValueRemoved()));
-        }
-    }
-
-    // Property model change listener
-    private static class PropertyChangeAdapter implements ChangeListener<Object> {
-        public final Object source;
-        public final EventHandler<PropertyChangeEvent<?>> handler;
-
-        public PropertyChangeAdapter(Object source, EventHandler<PropertyChangeEvent<?>> handler) {
-            if (source == null) {
-                throw new NullPointerException();
-            }
-
-            if (handler == null) {
-                throw new NullPointerException();
-            }
-
-            this.source = source;
-            this.handler = handler;
-        }
-
-        @Override
-        public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) {
-            handler.handle(new PropertyChangeEvent<Object>(source, oldValue));
-        }
-    }
-
     /**
      * @since JavaFX 8.0
      */
@@ -1674,7 +1730,7 @@
     private HashMap<String, Class<?>> classes = new HashMap<String, Class<?>>();
 
     private HashMap<String, Field> controllerFields = null;
-    private HashMap<String, Method> controllerMethods = null;
+    private HashMap<Class, Map<String, Method>> controllerMethods = null;
 
     private ScriptEngineManager scriptEngineManager = null;
 
@@ -1726,6 +1782,7 @@
     public static final String EVENT_HANDLER_PREFIX = "on";
     public static final String EVENT_KEY = "event";
     public static final String CHANGE_EVENT_HANDLER_SUFFIX = "Change";
+    private static final String COLLECTION_HANDLER_NAME = EVENT_HANDLER_PREFIX + CHANGE_EVENT_HANDLER_SUFFIX;
 
     public static final String NULL_KEYWORD = "null";
 
@@ -2343,7 +2400,7 @@
                 }
 
                 // Initialize the controller
-                Method initializeMethod = getControllerMethods().get(INITIALIZE_METHOD_NAME);
+                Method initializeMethod = getControllerMethods().get(null).get(INITIALIZE_METHOD_NAME);
 
                 if (initializeMethod != null) {
                     try {
@@ -2736,9 +2793,26 @@
         return controllerFields;
     }
 
-    private HashMap<String, Method> getControllerMethods() throws LoadException {
+    private static final Class[] SUPPORTED_TYPES = { Event.class, ListChangeListener.Change.class,
+        MapChangeListener.Change.class, SetChangeListener.Change.class
+    };
+
+    private Class<?> toSupportedEventType(Class<?> type) {
+        for (Class c : SUPPORTED_TYPES) {
+            if (c.isAssignableFrom(type)) {
+                return c;
+            }
+        }
+        return null;
+    }
+
+    private HashMap<Class, Map<String, Method>> getControllerMethods() throws LoadException {
         if (controllerMethods == null) {
-            controllerMethods = new HashMap<String, Method>();
+            controllerMethods = new HashMap<>();
+            controllerMethods.put(null, new HashMap<>());
+            for (Class c: SUPPORTED_TYPES) {
+                controllerMethods.put(c, new HashMap<>());
+            }
 
             Class<?> controllerType = controller.getClass();
             Class<?> type = controllerType;
@@ -2771,14 +2845,17 @@
                         //    name has not already been defined
                         String methodName = method.getName();
                         Class<?>[] parameterTypes = method.getParameterTypes();
+                        Class convertedType;
 
                         if (methodName.equals(INITIALIZE_METHOD_NAME)) {
                             if (parameterTypes.length == 0) {
-                                controllerMethods.put(method.getName(), method);
+                                controllerMethods.get(null).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);
+                        } else if (parameterTypes.length == 1 &&
+                                (convertedType = toSupportedEventType(parameterTypes[0])) != null) {
+                            controllerMethods.get(convertedType).put(methodName, method);
+                        } else if (parameterTypes.length == 0) {
+                            controllerMethods.get(null).put(methodName, method);
                         }
                     }
                 }
--- a/modules/fxml/src/test/java/javafx/fxml/RT_17714Controller.java	Wed Sep 04 13:53:41 2013 +0400
+++ b/modules/fxml/src/test/java/javafx/fxml/RT_17714Controller.java	Wed Sep 04 14:36:02 2013 +0200
@@ -25,69 +25,57 @@
 
 package javafx.fxml;
 
-import com.sun.javafx.fxml.ObservableListChangeEvent;
-import com.sun.javafx.fxml.ObservableMapChangeEvent;
 import java.net.URL;
 import java.util.*;
-import javafx.collections.ObservableList;
-import javafx.collections.ObservableMap;
+
+import javafx.collections.*;
 
 public class RT_17714Controller implements Initializable {
     @FXML private Widget root;
 
-    private ArrayList<Widget> children = new ArrayList<Widget>();
-    private HashMap<String, Object> properties = new HashMap<String, Object>();
+    boolean listWithParamCalled = false;
+    boolean listNoParamCalled = false;
+    boolean setWithParamCalled = false;
+    boolean setNoParamCalled = false;
+    boolean mapWithParamCalled = false;
+    boolean mapNoParamCalled = false;
+
 
     @Override
     public void initialize(URL location, ResourceBundle resources) {
-        children.addAll(root.getChildren());
-        properties.putAll(root.getProperties());
-    }
-
-    public List<Widget> getChildren() {
-        return children;
-    }
-
-    public Map<String, Object> getProperties() {
-        return properties;
     }
 
     @FXML
     @SuppressWarnings("unchecked")
-    protected void handleChildListChange(ObservableListChangeEvent<Widget> event) {
-        ObservableList<Widget> list = (ObservableList<Widget>)event.getSource();
-        int from = event.getFrom();
-        int to = event.getTo();
-        List<Widget> removed = event.getRemoved();
-
-        if (event.getEventType() == ObservableListChangeEvent.ADD) {
-            children.addAll(list.subList(from, to));
-        } else if (event.getEventType() == ObservableListChangeEvent.UPDATE) {
-            if (removed != null) {
-                for (int i = from, n = from + removed.size(); i < n; i++) {
-                    children.set(i, list.get(i));
-                }
-            }
-        } else if (event.getEventType() == ObservableListChangeEvent.REMOVE) {
-            children.subList(from, from + removed.size()).clear();
-        } else {
-            throw new UnsupportedOperationException();
-        }
+    protected void handleChildListChange(ListChangeListener.Change<Widget> event) {
+        listWithParamCalled = true;
+    }
+    @FXML
+    @SuppressWarnings("unchecked")
+    protected void handleChildListChange() {
+        listNoParamCalled = true;
     }
 
     @FXML
     @SuppressWarnings("unchecked")
-    protected void handlePropertiesChange(ObservableMapChangeEvent<String, Object> event) {
-        ObservableMap<String, Object> map = (ObservableMap<String, Object>)event.getSource();
-        String key = event.getKey();
+    protected void handlePropertiesChange(MapChangeListener.Change<String, Object> event) {
+        mapWithParamCalled = true;
+    }
 
-        if (event.getEventType() == ObservableMapChangeEvent.ADD
-            || event.getEventType() == ObservableMapChangeEvent.UPDATE) {
-            properties.put(key, map.get(key));
-        } else if (event.getEventType() == ObservableMapChangeEvent.REMOVE) {
-            properties.remove(key);
-        } else {
-            throw new UnsupportedOperationException();
-        }
+    @FXML
+    @SuppressWarnings("unchecked")
+    protected void handlePropertiesChange() {
+        mapNoParamCalled = true;
+    }
+
+    @FXML
+    @SuppressWarnings("unchecked")
+    protected void handleSetChange(SetChangeListener.Change<String> event) {
+        setWithParamCalled = true;
+    }
+    @FXML
+    @SuppressWarnings("unchecked")
+    protected void handleSetChange() {
+        setNoParamCalled = true;
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/fxml/src/test/java/javafx/fxml/RT_17714Controller2.java	Wed Sep 04 14:36:02 2013 +0200
@@ -0,0 +1,63 @@
+/*
+ * 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 javafx.collections.ListChangeListener;
+import javafx.collections.MapChangeListener;
+import javafx.collections.SetChangeListener;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+public class RT_17714Controller2 implements Initializable {
+    @FXML private Widget root;
+
+    boolean listNoParamCalled = false;
+    boolean setNoParamCalled = false;
+    boolean mapNoParamCalled = false;
+
+
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+    }
+
+    @FXML
+    @SuppressWarnings("unchecked")
+    protected void handleChildListChange() {
+        listNoParamCalled = true;
+    }
+
+    @FXML
+    @SuppressWarnings("unchecked")
+    protected void handlePropertiesChange() {
+        mapNoParamCalled = true;
+    }
+
+    @FXML
+    @SuppressWarnings("unchecked")
+    protected void handleSetChange() {
+        setNoParamCalled = true;
+    }
+}
\ No newline at end of file
--- a/modules/fxml/src/test/java/javafx/fxml/RT_17714Test.java	Wed Sep 04 13:53:41 2013 +0400
+++ b/modules/fxml/src/test/java/javafx/fxml/RT_17714Test.java	Wed Sep 04 14:36:02 2013 +0200
@@ -25,6 +25,7 @@
 
 package javafx.fxml;
 
+import javafx.util.Callback;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
@@ -36,24 +37,12 @@
         Widget widget = (Widget)fxmlLoader.load();
         RT_17714Controller controller = (RT_17714Controller)fxmlLoader.getController();
 
-        assertEquals(widget.getChildren(), controller.getChildren());
-
-        // Test add
+        assertFalse(controller.listWithParamCalled);
+        assertFalse(controller.listNoParamCalled);
+        // Test
         widget.getChildren().add(new Widget("Widget 4"));
-        widget.getChildren().add(new Widget("Widget 5"));
-
-        assertEquals(widget.getChildren(), controller.getChildren());
-
-        // Test update
-        widget.getChildren().set(0, new Widget("Widget 1a"));
-        widget.getChildren().set(2, new Widget("Widget 3a"));
-
-        assertEquals(widget.getChildren(), controller.getChildren());
-
-        // Test remove
-        widget.getChildren().remove(1);
-
-        assertEquals(widget.getChildren(), controller.getChildren());
+        assertTrue(controller.listWithParamCalled);
+        assertFalse(controller.listNoParamCalled);
     }
 
     @Test
@@ -62,23 +51,80 @@
         Widget widget = (Widget)fxmlLoader.load();
         RT_17714Controller controller = (RT_17714Controller)fxmlLoader.getController();
 
-        assertEquals(widget.getProperties(), controller.getProperties());
+        assertFalse(controller.mapWithParamCalled);
+        assertFalse(controller.mapNoParamCalled);
+        // Test
+        widget.getProperties().put("d", 1000);
 
-        // Test add
+        assertTrue(controller.mapWithParamCalled);
+        assertFalse(controller.mapNoParamCalled);
+    }
+
+    @Test
+    public void testSetEvents() throws Exception {
+        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("rt_17714.fxml"));
+        Widget widget = (Widget)fxmlLoader.load();
+        RT_17714Controller controller = (RT_17714Controller)fxmlLoader.getController();
+
+        assertFalse(controller.setWithParamCalled);
+        assertFalse(controller.setNoParamCalled);
+        // Test
+        widget.getSet().add("x");
+
+        assertTrue(controller.setWithParamCalled);
+        assertFalse(controller.setNoParamCalled);
+    }
+
+    @Test
+    public void testListEvents_NoParam() throws Exception {
+        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("rt_17714.fxml"), null, null, new Callback<Class<?>, Object>() {
+            @Override
+            public Object call(Class<?> param) {
+                return new RT_17714Controller2();
+            }
+        });
+        Widget widget = (Widget)fxmlLoader.load();
+        RT_17714Controller2 controller = (RT_17714Controller2)fxmlLoader.getController();
+
+        assertFalse(controller.listNoParamCalled);
+        // Test
+        widget.getChildren().add(new Widget("Widget 4"));
+        assertTrue(controller.listNoParamCalled);
+    }
+
+    @Test
+    public void testMapEvents_NoParam() throws Exception {
+        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("rt_17714.fxml"), null, null, new Callback<Class<?>, Object>() {
+            @Override
+            public Object call(Class<?> param) {
+                return new RT_17714Controller2();
+            }
+        });
+        Widget widget = (Widget)fxmlLoader.load();
+        RT_17714Controller2 controller = (RT_17714Controller2)fxmlLoader.getController();
+
+        assertFalse(controller.mapNoParamCalled);
+        // Test
         widget.getProperties().put("d", 1000);
-        widget.getProperties().put("e", 10000);
 
-        assertEquals(widget.getProperties(), controller.getProperties());
+        assertTrue(controller.mapNoParamCalled);
+    }
 
-        // Test update
-        widget.getProperties().put("a", 2);
-        widget.getProperties().put("c", 200);
+    @Test
+    public void testSetEvents_NoParam() throws Exception {
+        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("rt_17714.fxml"), null, null, new Callback<Class<?>, Object>() {
+            @Override
+            public Object call(Class<?> param) {
+                return new RT_17714Controller2();
+            }
+        });
+        Widget widget = (Widget)fxmlLoader.load();
+        RT_17714Controller2 controller = (RT_17714Controller2)fxmlLoader.getController();
 
-        assertEquals(widget.getProperties(), controller.getProperties());
+        assertFalse(controller.setNoParamCalled);
+        // Test
+        widget.getSet().add("x");
 
-        // Test remove
-        widget.getProperties().remove("b");
-
-        assertEquals(widget.getProperties(), controller.getProperties());
+        assertTrue(controller.setNoParamCalled);
     }
 }
--- a/modules/fxml/src/test/java/javafx/fxml/Widget.java	Wed Sep 04 13:53:41 2013 +0400
+++ b/modules/fxml/src/test/java/javafx/fxml/Widget.java	Wed Sep 04 14:36:02 2013 +0200
@@ -39,6 +39,7 @@
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 import javafx.collections.ObservableMap;
+import javafx.collections.ObservableSet;
 
 @IDProperty("id")
 @DefaultProperty("children")
@@ -47,6 +48,7 @@
     private StringProperty name = new SimpleStringProperty();
     private IntegerProperty number = new SimpleIntegerProperty();
     private ObservableList<Widget> children = FXCollections.observableArrayList();
+    private ObservableSet<String> set = FXCollections.observableSet();
     private ObservableMap<String, Object> properties = FXCollections.observableHashMap();
     private BooleanProperty enabledProperty = new SimpleBooleanProperty(true);
     private ArrayList<String> styles = new ArrayList<String>();
@@ -154,6 +156,10 @@
         this.names = Arrays.copyOf(names, names.length);
     }
 
+    public ObservableSet<String> getSet() {
+        return set;
+    }
+
 
     public static Alignment getAlignment(Widget widget) {
         return (Alignment)widget.getProperties().get(ALIGNMENT_KEY);
--- a/modules/fxml/src/test/resources/javafx/fxml/rt_17714.fxml	Wed Sep 04 13:53:41 2013 +0400
+++ b/modules/fxml/src/test/resources/javafx/fxml/rt_17714.fxml	Wed Sep 04 14:36:02 2013 +0200
@@ -35,4 +35,5 @@
     </children>
     
     <properties a="1" b="10" c="100" onChange="#handlePropertiesChange"/>
+    <set onChange="#handleSetChange"/>
 </Widget>