diff -r 3da9153bcf2d -r 0324c269b390 javafx-beans/src/com/sun/javafx/collections/ArrayListenerHelper.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-beans/src/com/sun/javafx/collections/ArrayListenerHelper.java	Mon Apr 22 15:45:02 2013 -0700
@@ -0,0 +1,332 @@
+/*
+ * Copyright (c) 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.collections;
+
+import com.sun.javafx.binding.ExpressionHelperBase;
+import java.util.Arrays;
+import javafx.beans.InvalidationListener;
+import javafx.collections.ArrayChangeListener;
+import javafx.collections.ObservableArray;
+
+/**
+ *
+ * @author akouznet
+ */
+public abstract class ArrayListenerHelper<T extends ObservableArray> extends ExpressionHelperBase {
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Static methods
+
+    public static <T extends ObservableArray> ArrayListenerHelper addListener(ArrayListenerHelper helper, T observable, InvalidationListener listener) {
+        if (listener == null) {
+            throw new NullPointerException();
+        }
+        return (helper == null)? new ArrayListenerHelper.SingleInvalidation(observable, listener) : helper.addListener(listener);
+    }
+
+    public static  ArrayListenerHelper removeListener(ArrayListenerHelper helper, InvalidationListener listener) {
+        if (listener == null) {
+            throw new NullPointerException();
+        }
+        return (helper == null)? null : helper.removeListener(listener);
+    }
+
+    public static <T extends ObservableArray> ArrayListenerHelper addListener(ArrayListenerHelper helper, T observable, ArrayChangeListener listener) {
+        if (listener == null) {
+            throw new NullPointerException();
+        }
+        return (helper == null)? new ArrayListenerHelper.SingleChange(observable, listener) : helper.addListener(listener);
+    }
+
+    public static  ArrayListenerHelper removeListener(ArrayListenerHelper helper, ArrayChangeListener listener) {
+        if (listener == null) {
+            throw new NullPointerException();
+        }
+        return (helper == null)? null : helper.removeListener(listener);
+    }
+
+    public static  void fireValueChangedEvent(ArrayListenerHelper helper, boolean sizeChanged, int from, int to) {
+        if (helper != null) {
+            helper.fireValueChangedEvent(sizeChanged, from, to);
+        }
+    }
+
+    public static  boolean hasListeners(ArrayListenerHelper helper) {
+        return helper != null;
+    }
+    
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Common implementations
+    
+    protected final T observable;
+
+    public ArrayListenerHelper(T observable) {
+        this.observable = observable;
+    }
+    
+    protected abstract ArrayListenerHelper addListener(InvalidationListener listener);
+    protected abstract ArrayListenerHelper removeListener(InvalidationListener listener);
+
+    protected abstract ArrayListenerHelper addListener(ArrayChangeListener<T> listener);
+    protected abstract ArrayListenerHelper removeListener(ArrayChangeListener<T> listener);
+
+    protected abstract void fireValueChangedEvent(boolean sizeChanged, int from, int to);
+    
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Implementations
+
+    private static class SingleInvalidation<T extends ObservableArray> extends ArrayListenerHelper<T> {
+
+        private final InvalidationListener listener;
+
+        private SingleInvalidation(T observable, InvalidationListener listener) {
+            super(observable);
+            this.listener = listener;
+        }
+
+        @Override
+        protected ArrayListenerHelper addListener(InvalidationListener listener) {
+            return new Generic(observable, this.listener, listener);
+        }
+
+        @Override
+        protected ArrayListenerHelper removeListener(InvalidationListener listener) {
+            return (listener.equals(this.listener))? null : this;
+        }
+
+        @Override
+        protected ArrayListenerHelper addListener(ArrayChangeListener listener) {
+            return new Generic(observable, this.listener, listener);
+        }
+
+        @Override
+        protected ArrayListenerHelper removeListener(ArrayChangeListener listener) {
+            return this;
+        }
+
+        @Override
+        protected void fireValueChangedEvent(boolean sizeChanged, int from, int to) {
+            listener.invalidated(observable);
+        }
+    }
+
+    private static class SingleChange<T extends ObservableArray> extends ArrayListenerHelper<T> {
+
+        private final ArrayChangeListener listener;
+
+        private SingleChange(T observable, ArrayChangeListener listener) {
+            super(observable);
+            this.listener = listener;
+        }
+
+        @Override
+        protected ArrayListenerHelper addListener(InvalidationListener listener) {
+            return new Generic(observable, listener, this.listener);
+        }
+
+        @Override
+        protected ArrayListenerHelper removeListener(InvalidationListener listener) {
+            return this;
+        }
+
+        @Override
+        protected ArrayListenerHelper addListener(ArrayChangeListener listener) {
+            return new Generic(observable, this.listener, listener);
+        }
+
+        @Override
+        protected ArrayListenerHelper removeListener(ArrayChangeListener listener) {
+            return (listener.equals(this.listener))? null : this;
+        }
+
+        @Override
+        protected void fireValueChangedEvent(boolean sizeChanged, int from, int to) {
+            listener.onChanged(observable, sizeChanged, from, to);
+        }
+    }
+
+    private static class Generic<T extends ObservableArray> extends ArrayListenerHelper<T> {
+
+        private InvalidationListener[] invalidationListeners;
+        private ArrayChangeListener[] changeListeners;
+        private int invalidationSize;
+        private int changeSize;
+        private boolean locked;
+
+        private Generic(T observable, InvalidationListener listener0, InvalidationListener listener1) {
+            super(observable);
+            this.invalidationListeners = new InvalidationListener[] {listener0, listener1};
+            this.invalidationSize = 2;
+        }
+
+        private Generic(T observable, ArrayChangeListener listener0, ArrayChangeListener listener1) {
+            super(observable);
+            this.changeListeners = new ArrayChangeListener[] {listener0, listener1};
+            this.changeSize = 2;
+        }
+
+        private Generic(T observable, InvalidationListener invalidationListener, ArrayChangeListener changeListener) {
+            super(observable);
+            this.invalidationListeners = new InvalidationListener[] {invalidationListener};
+            this.invalidationSize = 1;
+            this.changeListeners = new ArrayChangeListener[] {changeListener};
+            this.changeSize = 1;
+        }
+
+        @Override
+        protected Generic addListener(InvalidationListener listener) {
+            if (invalidationListeners == null) {
+                invalidationListeners = new InvalidationListener[] {listener};
+                invalidationSize = 1;
+            } else {
+                final int oldCapacity = invalidationListeners.length;
+                if (locked) {
+                    final int newCapacity = (invalidationSize < oldCapacity)? oldCapacity : (oldCapacity * 3)/2 + 1;
+                    invalidationListeners = Arrays.copyOf(invalidationListeners, newCapacity);
+                } else if (invalidationSize == oldCapacity) {
+                    invalidationSize = trim(invalidationSize, invalidationListeners);
+                    if (invalidationSize == oldCapacity) {
+                        final int newCapacity = (oldCapacity * 3)/2 + 1;
+                        invalidationListeners = Arrays.copyOf(invalidationListeners, newCapacity);
+                    }
+                }
+                invalidationListeners[invalidationSize++] = listener;
+            }
+            return this;
+        }
+
+        @Override
+        protected ArrayListenerHelper removeListener(InvalidationListener listener) {
+            if (invalidationListeners != null) {
+                for (int index = 0; index < invalidationSize; index++) {
+                    if (listener.equals(invalidationListeners[index])) {
+                        if (invalidationSize == 1) {
+                            if (changeSize == 1) {
+                                return new SingleChange(observable, changeListeners[0]);
+                            }
+                            invalidationListeners = null;
+                            invalidationSize = 0;
+                        } else if ((invalidationSize == 2) && (changeSize == 0)) {
+                            return new SingleInvalidation(observable, invalidationListeners[1-index]);
+                        } else {
+                            final int numMoved = invalidationSize - index - 1;
+                            final InvalidationListener[] oldListeners = invalidationListeners;
+                            if (locked) {
+                                invalidationListeners = new InvalidationListener[invalidationListeners.length];
+                                System.arraycopy(oldListeners, 0, invalidationListeners, 0, index+1);
+                            }
+                            if (numMoved > 0) {
+                                System.arraycopy(oldListeners, index+1, invalidationListeners, index, numMoved);
+                            }
+                            invalidationSize--;
+                            if (!locked) {
+                                invalidationListeners[invalidationSize] = null; // Let gc do its work
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+            return this;
+        }
+
+        @Override
+        protected ArrayListenerHelper addListener(ArrayChangeListener<T> listener) {
+            if (changeListeners == null) {
+                changeListeners = new ArrayChangeListener[] {listener};
+                changeSize = 1;
+            } else {
+                final int oldCapacity = changeListeners.length;
+                if (locked) {
+                    final int newCapacity = (changeSize < oldCapacity)? oldCapacity : (oldCapacity * 3)/2 + 1;
+                    changeListeners = Arrays.copyOf(changeListeners, newCapacity);
+                } else if (changeSize == oldCapacity) {
+                    changeSize = trim(changeSize, changeListeners);
+                    if (changeSize == oldCapacity) {
+                        final int newCapacity = (oldCapacity * 3)/2 + 1;
+                        changeListeners = Arrays.copyOf(changeListeners, newCapacity);
+                    }
+                }
+                changeListeners[changeSize++] = listener;
+            }
+            return this;
+        }
+
+        @Override
+        protected ArrayListenerHelper removeListener(ArrayChangeListener<T> listener) {
+            if (changeListeners != null) {
+                for (int index = 0; index < changeSize; index++) {
+                    if (listener.equals(changeListeners[index])) {
+                        if (changeSize == 1) {
+                            if (invalidationSize == 1) {
+                                return new SingleInvalidation(observable, invalidationListeners[0]);
+                            }
+                            changeListeners = null;
+                            changeSize = 0;
+                        } else if ((changeSize == 2) && (invalidationSize == 0)) {
+                            return new SingleChange(observable, changeListeners[1-index]);
+                        } else {
+                            final int numMoved = changeSize - index - 1;
+                            final ArrayChangeListener[] oldListeners = changeListeners;
+                            if (locked) {
+                                changeListeners = new ArrayChangeListener[changeListeners.length];
+                                System.arraycopy(oldListeners, 0, changeListeners, 0, index+1);
+                            }
+                            if (numMoved > 0) {
+                                System.arraycopy(oldListeners, index+1, changeListeners, index, numMoved);
+                            }
+                            changeSize--;
+                            if (!locked) {
+                                changeListeners[changeSize] = null; // Let gc do its work
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+            return this;
+        }
+
+        @Override
+        protected void fireValueChangedEvent(boolean sizeChanged, int from, int to) {
+            final InvalidationListener[] curInvalidationList = invalidationListeners;
+            final int curInvalidationSize = invalidationSize;
+            final ArrayChangeListener[] curChangeList = changeListeners;
+            final int curChangeSize = changeSize;
+
+            try {
+                locked = true;
+                for (int i = 0; i < curInvalidationSize; i++) {
+                    curInvalidationList[i].invalidated(observable);
+                }
+                for (int i = 0; i < curChangeSize; i++) {
+                    curChangeList[i].onChanged(observable, sizeChanged, from, to);
+                }
+            } finally {
+                locked = false;
+            }
+        }
+    }
+}
diff -r 3da9153bcf2d -r 0324c269b390 javafx-beans/src/com/sun/javafx/collections/ArraySyncer.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-beans/src/com/sun/javafx/collections/ArraySyncer.java	Mon Apr 22 15:45:02 2013 -0700
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 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.collections;
+
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ReadOnlyBooleanProperty;
+import javafx.beans.property.ReadOnlyBooleanWrapper;
+import javafx.collections.ArrayChangeListener;
+import javafx.collections.ObservableArray;
+
+
+/**
+ *
+ * @author akouznet
+ */
+public class ArraySyncer<T extends ObservableArray> implements ArrayChangeListener<T> {
+    
+    protected final T array;
+    private ReadOnlyBooleanWrapper dirty = new ReadOnlyBooleanWrapper(this, "dirty", true);
+    private BooleanProperty parentDirty;
+    private boolean dirtyInFull;
+    private int dirtyRangeFrom;
+    private int dirtyRangeLength;
+
+    public ArraySyncer(T array, BooleanProperty parentDirty) {
+        this.array = array;
+        this.parentDirty = parentDirty;
+        array.addListener(this);
+    }
+    
+    public ArraySyncer(T array) {
+        this(array, null);
+    }
+
+    public T get() {
+        return array;
+    }
+    
+    public ReadOnlyBooleanProperty dirtyProperty() {
+        return dirty;
+    }
+
+    public final boolean isDirty() {
+        return dirty.get();
+    }
+
+    /**
+     * Array was replaced
+     * @return true if array was replaced; false otherwise
+     */
+    protected final boolean isDirtyInFull() {
+        return dirtyInFull;
+    }
+    
+    protected final int getDirtyRangeLength() {
+        return dirtyRangeLength;
+    }
+
+    protected final int getDirtyRangeFrom() {
+        return dirtyRangeFrom;
+    }
+    
+    private void markDirty() {
+        dirty.set(true);
+        if (parentDirty != null) {
+            parentDirty.set(true);
+        }
+    }
+
+    /**
+     * @param dirty if true, the whole collection is marked as dirty;
+     * if false, the whole collection is marked as not-dirty
+     */
+    public final void setDirty(boolean dirty) {
+        this.dirtyInFull = dirty;
+        if (dirty) {
+            markDirty();
+            dirtyRangeFrom = 0;
+            dirtyRangeLength = array.size();
+        } else {
+            this.dirty.set(false);
+            dirtyRangeFrom = dirtyRangeLength = 0;
+        }
+    }
+    
+    /**
+     * Adds a dirty range
+     * @param from index of the first modified element
+     * @param length length of the modified range
+     */
+    protected final void addDirtyRange(int from, int length) {
+        if (length > 0 && !dirtyInFull) {
+            markDirty();
+            if (dirtyRangeLength == 0) {
+                dirtyRangeFrom = from;
+                dirtyRangeLength = length;
+            } else {
+                int fromIndex = Math.min(dirtyRangeFrom, from);
+                int toIndex = Math.max(dirtyRangeFrom + dirtyRangeLength, from + length);            
+                dirtyRangeFrom = fromIndex;
+                dirtyRangeLength = toIndex - fromIndex;
+            }
+        }
+    }    
+        
+    @Override
+    public void onChanged(T observableArray, boolean sizeChanged, int from, int to) {
+        if (sizeChanged) {
+            setDirty(true);
+        } else {
+            addDirtyRange(from, to - from);
+        }
+    }
+}
diff -r 3da9153bcf2d -r 0324c269b390 javafx-beans/src/com/sun/javafx/collections/FloatArraySyncer.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-beans/src/com/sun/javafx/collections/FloatArraySyncer.java	Mon Apr 22 15:45:02 2013 -0700
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 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.collections;
+
+
+import javafx.beans.property.BooleanProperty;
+import javafx.collections.ObservableFloatArray;
+
+
+/**
+ *
+ * @author akouznet
+ */
+public class FloatArraySyncer extends ArraySyncer<ObservableFloatArray> {
+
+    public FloatArraySyncer(ObservableFloatArray array, BooleanProperty parentDirty) {
+        super(array, parentDirty);
+    }
+    
+    /**
+     * This method is used to sync arrays on pulses. This method expects
+     * the same array was synced before. The usage is similar to toArray method
+     * so always use it as following: {@code dest = source.syncTo(dest);}
+     * @param array previously synced array
+     * @return a synced array, which is the same or new array (depending on 
+     * the change).
+     */
+    public float[] syncTo(float[] array) {
+        if (this.array == null) {
+            return null;
+        }
+        if (isDirtyInFull() || array == null || array.length != this.array.size()) {
+            return this.array.toArray(array);
+        }
+        this.array.copyTo(getDirtyRangeFrom(), array, getDirtyRangeFrom(), getDirtyRangeLength());
+        return array;
+    }     
+}
diff -r 3da9153bcf2d -r 0324c269b390 javafx-beans/src/com/sun/javafx/collections/IntegerArraySyncer.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-beans/src/com/sun/javafx/collections/IntegerArraySyncer.java	Mon Apr 22 15:45:02 2013 -0700
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 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.collections;
+
+
+import javafx.beans.property.BooleanProperty;
+import javafx.collections.ObservableIntegerArray;
+
+
+/**
+ *
+ * @author akouznet
+ */
+public class IntegerArraySyncer extends ArraySyncer<ObservableIntegerArray> {
+
+    public IntegerArraySyncer(ObservableIntegerArray array, BooleanProperty parentDirty) {
+        super(array, parentDirty);
+    }
+    
+    /**
+     * This method is used to sync arrays on pulses. This method expects
+     * the same array was synced before. The usage is similar to toArray method
+     * so always use it as following: {@code dest = source.syncTo(dest);}
+     * @param array previously synced array
+     * @return a synced array, which is the same or new array (depending on 
+     * the change).
+     */
+    public int[] syncTo(int[] array) {
+        if (this.array == null) {
+            return null;
+        }
+        if (isDirtyInFull() || array == null || array.length != this.array.size()) {
+            return this.array.toArray(array);
+        }
+        this.array.copyTo(getDirtyRangeFrom(), array, getDirtyRangeFrom(), getDirtyRangeLength());
+        return array;
+    }     
+}
diff -r 3da9153bcf2d -r 0324c269b390 javafx-beans/src/javafx/collections/ArrayChangeListener.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-beans/src/javafx/collections/ArrayChangeListener.java	Mon Apr 22 15:45:02 2013 -0700
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 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.collections;
+
+/**
+ * Interface that receives notifications of changes to an ObservableArray.
+ */
+public interface ArrayChangeListener<T extends ObservableArray> {
+    
+    /**
+     * Called after a change has been made to an ObservableArray.
+     *
+     * @param sizeChanged indicates size of array changed
+     * @param from A beginning (inclusive) of an interval related to the change
+     * @param to An end (exclusive) of an interval related to the change.
+     */
+    public void onChanged(T observableArray, boolean sizeChanged, int from, int to);    
+}
diff -r 3da9153bcf2d -r 0324c269b390 javafx-beans/src/javafx/collections/ObservableArray.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-beans/src/javafx/collections/ObservableArray.java	Mon Apr 22 15:45:02 2013 -0700
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 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.collections;
+
+
+import com.sun.javafx.collections.ArrayListenerHelper;
+import javafx.beans.InvalidationListener;
+import javafx.beans.Observable;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ReadOnlyIntegerProperty;
+import javafx.beans.property.ReadOnlyIntegerWrapper;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+
+
+/**
+ *
+ * @author akouznet
+ */
+public abstract class ObservableArray<T extends ObservableArray> implements Observable {
+    
+    private final int componentsPerElement;
+    protected ArrayListenerHelper<T> listenerHelper;
+
+    public ObservableArray() {
+        this.componentsPerElement = 1;
+    }
+    
+    public ObservableArray(int COMPONENTS_PER_ELEMENT) {
+        this.componentsPerElement = COMPONENTS_PER_ELEMENT;
+    }
+
+    /**
+     * Length of data in this array. Is less or equal to an array length.
+     * Setting different size will force {@code ObservableArray} to create array
+     * of given size and copy existing data into it. To avoid data copying 
+     * set size to 0 before setting it to new value.
+     */
+    private final IntegerProperty size = new SimpleIntegerProperty(this, "size");
+    private boolean ignoreSizeChange = false;
+    
+    {
+        size.addListener(new ChangeListener<Number>() {
+
+            @Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
+                if (!ignoreSizeChange) {
+                    resizeArray(oldValue.intValue(), newValue.intValue());
+                }
+            }
+        });
+    }
+    
+    /**
+     * This method should resize array and copy existing elements into it
+     * @param oldSize old size of data in array
+     * @param newSize new size of data in array
+     */
+    protected abstract void resizeArray(int oldSize, int newSize);
+    
+    /**
+     * This method allows the change array size not causing resizeArray method to be invoked.
+     * @param newSize new array size
+     * @param ignoreSizeChange if true then new size value is just stored in the property,
+     * otherwise resizeArray method is invoked
+     */
+    protected void setSize(int newSize, boolean ignoreSizeChange) {
+        this.ignoreSizeChange = ignoreSizeChange;
+        size.set(newSize);
+        this.ignoreSizeChange = false;
+    }
+    
+    /**
+     * Sets new length of data in this array. This method creates new array
+     * if necessary and copies existing data into it. To avoid data copying 
+     * set size to 0 before setting it to new value.
+     */
+    public void setSize(int value) {
+        size.set(value);
+    }
+
+    /**
+     * Retrieves length of this array
+     * @return array length
+     */
+    public final int size() {
+        return size.get();
+    }
+
+    public IntegerProperty sizeProperty() {
+        return size;
+    }
+
+    /**
+     * Checks that the given length is divisible by components_per_element
+     * @return true if it is divisible; false otherwise
+     */
+    protected boolean verifyLength(int length) {
+        return length % componentsPerElement == 0;
+    }    
+    
+    private ReadOnlyIntegerWrapper elementCount;
+
+    public ReadOnlyIntegerProperty elementCountProperty() {
+        if (elementCount == null) {
+            elementCount = new ReadOnlyIntegerWrapper(this, "elementCount");
+            elementCount.bind(sizeProperty().divide(componentsPerElement));
+        }
+        return elementCount.getReadOnlyProperty();
+    }
+    
+    /**
+     * The total number of elements of this {@code ObservableArray}
+     * This is {@code size() / COMPONENTS_PER_ELEMENT}.
+     */
+    public int getElementCount() {
+        return size() / componentsPerElement;
+    }
+    
+    /**
+     * @return Number of components per element. For example, 3D point has 
+     * 3 components per element: x, y, z.
+     */
+    public int getComponentsPerElement() {
+        return componentsPerElement;
+    }
+
+    @Override public void addListener(InvalidationListener listener) {
+        listenerHelper = ArrayListenerHelper.addListener(listenerHelper, this, listener);
+    }
+
+    @Override public void removeListener(InvalidationListener listener) {
+        listenerHelper = ArrayListenerHelper.removeListener(listenerHelper, listener);
+    }
+    
+    public void addListener(ArrayChangeListener listener) {
+        listenerHelper = ArrayListenerHelper.addListener(listenerHelper, this, listener);
+    }
+
+    public void removeListener(ArrayChangeListener listener) {
+        listenerHelper = ArrayListenerHelper.removeListener(listenerHelper, listener);
+    }
+}
\ No newline at end of file
diff -r 3da9153bcf2d -r 0324c269b390 javafx-beans/src/javafx/collections/ObservableFloatArray.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-beans/src/javafx/collections/ObservableFloatArray.java	Mon Apr 22 15:45:02 2013 -0700
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 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.collections;
+
+import com.sun.javafx.collections.ArrayListenerHelper;
+
+
+/**
+ *
+ * @author akouznet
+ */
+public class ObservableFloatArray extends ObservableArray<ObservableFloatArray> {
+    
+    private float[] array;
+
+    public ObservableFloatArray() {
+    }
+    
+    public ObservableFloatArray(int COMPONENTS_PER_ELEMENT) {
+        super(COMPONENTS_PER_ELEMENT);
+    }
+
+    /**
+     * Sets the array of this {@code TriangleMesh}
+     * 
+     * @param array source array of components_per_element * n values containing n new array.
+     */
+    public final void set(float[] array) {
+        if (!verifyLength(array.length)) {
+            throw new IllegalArgumentException("array.length has to be divisible by " + getComponentsPerElement() + ".");
+        }
+        boolean sizeChanged = this.array == null || this.array.length != array.length;
+
+        if ((this.array == null) || (this.array.length < array.length)) {
+            this.array = new float[array.length];
+        }
+        System.arraycopy(array, 0, this.array, 0, array.length);
+        // Store the valid elements count.
+        // Note this.array.length can be bigger than array.length.
+        setSize(array.length, true);
+        
+        ArrayListenerHelper.fireValueChangedEvent(listenerHelper, sizeChanged, 0, size());
+    }
+    
+    /**
+     * Sets the array associated with this {@code FloatArray}
+     * starting at the specified {@code index} using data in {@code array} 
+     * starting at index {@code start} for {@code length} number of array.
+     * 
+     * @param index the starting destination index in this TriangleMesh's array array
+     * @param array source array of floats containing the new array
+     * @param start starting source index in the array array.
+     * @param length number of point elements to be copied.
+     */
+    public final void set(int index, float[] array,
+                      int start, int length) {
+
+        System.arraycopy(array, start, this.array, index, length);
+
+        ArrayListenerHelper.fireValueChangedEvent(listenerHelper, false, index, index + length);
+    }
+    
+    public final float[] get() {
+        return toArray(null);
+    }
+    
+    /**
+     * Gets the array of this {@code TriangleMesh}
+     *
+     * @param array a float array that will receive the array
+     * if it not null and has sufficient capacity.
+     * @return a float array of array
+     */
+    public final float[] toArray(float[] array) {
+        if (this.array == null) {
+            return null;
+        }
+        if ((array == null) || (size() > array.length)) {
+            array = new float[size()];
+        }
+        System.arraycopy(this.array, 0, array, 0, size());
+        return array;
+    }
+    
+    public final float getValue(int index) {
+        return array[index];
+    }
+    
+    public final void setValue(int index, float value) {
+        array[index] = value;
+        ArrayListenerHelper.fireValueChangedEvent(listenerHelper, false, index, index + 1);
+    }
+
+    /**
+     * Gets the array associated with this {@code TriangleMesh} starting at the
+     * specified {@code index} for {@code length} number of array.
+     * 
+     * @param index starting source array index in this {@code TriangleMesh}
+     * @param array destination array that will receive this {@code TriangleMesh}'s array data
+     * @param length number of point elements to be copied
+     * @return a float array
+     */
+    public final float[] toArray(int index, float[] array, int length) {
+        if (this.array == null) {
+            return null;
+        }
+        System.arraycopy(this.array, index, array, 0, length);
+        return array;
+    }
+    
+    public final void copyTo(int srcPos, float[] dest, int destPos, int length) {
+        if (this.array == null) {
+            return;
+        }
+        System.arraycopy(this.array, srcPos, dest, destPos, length);
+    }
+
+    @Override
+    protected void resizeArray(int oldSize, int newSize) {
+        int minSize = Math.min(oldSize, newSize);
+        if (array == null) {
+            array = new float[newSize];
+        } else if (array.length != newSize) {
+            float[] newArray = new float[newSize];
+            System.arraycopy(array, 0, newArray, 0, minSize);
+            array = newArray;
+        }
+        ArrayListenerHelper.fireValueChangedEvent(listenerHelper, oldSize != newSize, minSize, minSize);
+    }
+}
\ No newline at end of file
diff -r 3da9153bcf2d -r 0324c269b390 javafx-beans/src/javafx/collections/ObservableIntegerArray.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/javafx-beans/src/javafx/collections/ObservableIntegerArray.java	Mon Apr 22 15:45:02 2013 -0700
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 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.collections;
+
+import com.sun.javafx.collections.ArrayListenerHelper;
+
+
+/**
+ *
+ * @author akouznet
+ */
+public class ObservableIntegerArray extends ObservableArray<ObservableIntegerArray> {
+    
+    private int[] array;
+
+    public ObservableIntegerArray() {
+    }
+    
+    public ObservableIntegerArray(int COMPONENTS_PER_ELEMENT) {
+        super(COMPONENTS_PER_ELEMENT);
+    }
+    
+    /**
+     * Sets the array, indices into the points and texCoords arrays,
+     * associated with this {@code TriangleMesh}.
+     * 
+     * @param array source array of components_per_element * n indices 
+     * (3 point indices and 3 texCood indices) containing n new array
+     */
+    public final void set(int[] array) {
+        if (!verifyLength(array.length)) {
+            throw new IllegalArgumentException("array.length has to be divisible by " + getComponentsPerElement() + ".");
+        }
+        boolean sizeChanged = this.array == null || this.array.length != array.length;
+        if ((this.array == null) || (this.array.length < array.length)) {
+            this.array = new int[array.length];
+        }
+        System.arraycopy(array, 0, this.array, 0, array.length);
+        // Store the valid face count.
+        // Note this.array.length can be bigger than array.length.
+        setSize(array.length, true);
+
+        ArrayListenerHelper.fireValueChangedEvent(listenerHelper, sizeChanged, 0, size());
+    }
+
+    /**
+     * Sets the array, indices into the points and texCoords arrays, 
+     * associated with this {@code TriangleMesh}
+     * starting at the specified{@code index} using data in {@code array} 
+     * starting at index {@code start} for {@code length} number of array.
+     * 
+     * @param index the starting destination index in this TriangleMesh's array array
+     * @param array an int array containing the new interleaved vertices
+     * @param start starting source index in the array array.
+     * @param length number of interleaved vertex elements to be copied
+     */
+    public final void set(int index, int[] array, int start, int length) {
+        
+        System.arraycopy(array, start, this.array, index, length);
+
+        ArrayListenerHelper.fireValueChangedEvent(listenerHelper, false, index, index + length);
+    }
+
+    public final int[] get() {
+        return toArray(null);
+    }
+    
+    /**
+     * Gets the array, indices into the points and texCoords arrays, of this 
+     * {@code TriangleMesh}
+     *
+     * @param array an int array that will receive the array if it not null and 
+     * has sufficient capacity.
+     * @return an int array of array
+     * 
+     */
+    public final int[] toArray(int[] array) {
+        if (this.array == null) {
+            return null;
+        }
+        if ((array == null) || (size() > array.length)) {
+            array = new int[size()];
+        }
+        System.arraycopy(this.array, 0, array, 0, size());
+        return array;
+    }
+    
+    public final int getValue(int index) {
+        return array[index];
+    }
+
+    public final void setValue(int index, int value) {
+        array[index] = value;
+        ArrayListenerHelper.fireValueChangedEvent(listenerHelper, false, index, index + 1);
+    }
+
+    
+    /**
+     * Gets the array, indices into the points and texCoords arrays,
+     * associated with this {@code TriangleMesh} starting at the specified
+     * {@code index} for {@code length} number of array.
+     * 
+     * @param index starting source array index in this {@code TriangleMesh}
+     * @param array destination array that will receive this {@code TriangleMesh}'s array data
+     * @param length number of face elements to be copied
+     * @return an int array
+     */
+    public final int[] toArray(int index, int[] array, int length) {
+        if (this.array == null) {
+            return null;
+        }
+        System.arraycopy(this.array, index, array, 0, length);
+        return array;
+    }    
+    
+    public final void copyTo(int srcPos, int[] dest, int destPos, int length) {
+        if (this.array == null) {
+            return;
+        }
+        System.arraycopy(this.array, srcPos, dest, destPos, length);
+    }   
+
+    @Override
+    protected void resizeArray(int oldSize, int newSize) {
+        int minSize = Math.min(oldSize, newSize);
+        if (array == null) {
+            array = new int[newSize];
+        } else if (array.length != newSize) {
+            int[] newArray = new int[newSize];
+            System.arraycopy(array, 0, newArray, 0, minSize);
+            array = newArray;
+        }
+        ArrayListenerHelper.fireValueChangedEvent(listenerHelper, oldSize != newSize, minSize, minSize);
+    }    
+}
diff -r 3da9153bcf2d -r 0324c269b390 javafx-fxml/src/com/sun/javafx/fxml/builder/TriangleMeshBuilder.java
--- a/javafx-fxml/src/com/sun/javafx/fxml/builder/TriangleMeshBuilder.java	Mon Apr 22 17:59:31 2013 +0100
+++ b/javafx-fxml/src/com/sun/javafx/fxml/builder/TriangleMeshBuilder.java	Mon Apr 22 15:45:02 2013 -0700
@@ -45,7 +45,7 @@
     public TriangleMesh build() {
         TriangleMesh mesh = new TriangleMesh(points, texCoords, faces);
         if (faceSmoothingGroups != null) {
-            mesh.setFaceSmoothingGroups(faceSmoothingGroups);
+            mesh.getFaceSmoothingGroups().set(faceSmoothingGroups);
         }
         return mesh;
     }
diff -r 3da9153bcf2d -r 0324c269b390 javafx-sg-common/build-closed.xml
--- a/javafx-sg-common/build-closed.xml	Mon Apr 22 17:59:31 2013 +0100
+++ b/javafx-sg-common/build-closed.xml	Mon Apr 22 15:45:02 2013 -0700
@@ -5,6 +5,7 @@
     <property name="runtime.dist.root.dir" value="../../rt-closed" />    
     <property name="rt.dist.root.dir" value="../../rt" />    
     <path id="javac.closed.classpath.path" path="
+        ${rt.dist.root.dir}/javafx-beans/dist/javafx-beans.jar:
         ${rt.dist.root.dir}/javafx-geom/dist/javafx-geom.jar:
         ${rt.dist.root.dir}/javafx-logging/dist/javafx-logging.jar:
         ${rt.dist.root.dir}/decora-runtime/dist/decora-runtime.jar" />
diff -r 3da9153bcf2d -r 0324c269b390 javafx-sg-common/project.properties
--- a/javafx-sg-common/project.properties	Mon Apr 22 17:59:31 2013 +0100
+++ b/javafx-sg-common/project.properties	Mon Apr 22 15:45:02 2013 -0700
@@ -1,6 +1,7 @@
 JFXRT_HOME=\
     ${runtime.dist.root.dir}/../artifacts/sdk/rt
 javac.classpath=\
+    ${rt.dist.root.dir}/javafx-beans/dist/javafx-beans.jar:\
     ${rt.dist.root.dir}/javafx-geom/dist/javafx-geom.jar:\
     ${rt.dist.root.dir}/javafx-logging/dist/javafx-logging.jar:\
     ${rt.dist.root.dir}/decora-runtime/dist/decora-runtime.jar:\
@@ -8,5 +9,4 @@
     ${JFXRT_HOME}/lib/ext/jfxrt.jar
 javac.test.additional.classpath=\
     ${rt.dist.root.dir}/javafx-ui-common/dist/javafx-ui-common.jar:\
-    ${rt.dist.root.dir}/javafx-sg-prism/dist/javafx-sg-prism.jar:\
-    ${rt.dist.root.dir}/javafx-beans/dist/javafx-beans.jar
+    ${rt.dist.root.dir}/javafx-sg-prism/dist/javafx-sg-prism.jar
\ No newline at end of file
diff -r 3da9153bcf2d -r 0324c269b390 javafx-sg-common/src/com/sun/javafx/sg/PGTriangleMesh.java
--- a/javafx-sg-common/src/com/sun/javafx/sg/PGTriangleMesh.java	Mon Apr 22 17:59:31 2013 +0100
+++ b/javafx-sg-common/src/com/sun/javafx/sg/PGTriangleMesh.java	Mon Apr 22 15:45:02 2013 -0700
@@ -25,16 +25,17 @@
 
 package com.sun.javafx.sg;
 
+
+import com.sun.javafx.collections.FloatArraySyncer;
+import com.sun.javafx.collections.IntegerArraySyncer;
+
+
 /**
  * TODO: 3D - Need documentation
  */
 public interface PGTriangleMesh {
-    public void setPoints(float[] points);
-    public void setPoints(float[] points, int index, int length);
-    public void setTexCoords(float[] texCoords);
-    public void setTexCoords(float[] texCoords, int index, int length);
-    public void setFaces(int[] faces);
-    public void setFaces(int[] faces, int index, int length);
-    public void setFaceSmoothingGroups(int[] faceSmoothingGroups);
-    public void setFaceSmoothingGroups(int[] faceSmoothingGroups, int index, int length);
+    public void syncPoints(FloatArraySyncer array);
+    public void syncTexCoords(FloatArraySyncer array);
+    public void syncFaces(IntegerArraySyncer array);
+    public void syncFaceSmoothingGroups(IntegerArraySyncer array);
 }
diff -r 3da9153bcf2d -r 0324c269b390 javafx-sg-prism/build-closed.xml
--- a/javafx-sg-prism/build-closed.xml	Mon Apr 22 17:59:31 2013 +0100
+++ b/javafx-sg-prism/build-closed.xml	Mon Apr 22 15:45:02 2013 -0700
@@ -6,6 +6,7 @@
     <property name="rt.dist.root.dir" value="../../rt" />    
     <property name="runtime.deps.dir" value="${runtime.dist.root.dir}/build/deps"/>
     <path id="javac.closed.classpath.path" path="
+        ${rt.dist.root.dir}/javafx-beans/dist/javafx-beans.jar:
         ${rt.dist.root.dir}/javafx-geom/dist/javafx-geom.jar:
         ${rt.dist.root.dir}/javafx-logging/dist/javafx-logging.jar:
         ${runtime.dist.root.dir}/javafx-font/dist/javafx-font.jar:
diff -r 3da9153bcf2d -r 0324c269b390 javafx-sg-prism/project.properties
--- a/javafx-sg-prism/project.properties	Mon Apr 22 17:59:31 2013 +0100
+++ b/javafx-sg-prism/project.properties	Mon Apr 22 15:45:02 2013 -0700
@@ -1,6 +1,7 @@
 JFXRT_HOME=\
     ${runtime.dist.root.dir}/../artifacts/sdk/rt
 javac.classpath=\
+    ${rt.dist.root.dir}/javafx-beans/dist/javafx-beans.jar:\
     ${rt.dist.root.dir}/javafx-geom/dist/javafx-geom.jar:\
     ${rt.dist.root.dir}/javafx-logging/dist/javafx-logging.jar:\
     ${rt.dist.root.dir}/javafx-common/dist/javafx-common.jar:\
diff -r 3da9153bcf2d -r 0324c269b390 javafx-sg-prism/src/com/sun/javafx/sg/prism/NGTriangleMesh.java
--- a/javafx-sg-prism/src/com/sun/javafx/sg/prism/NGTriangleMesh.java	Mon Apr 22 17:59:31 2013 +0100
+++ b/javafx-sg-prism/src/com/sun/javafx/sg/prism/NGTriangleMesh.java	Mon Apr 22 15:45:02 2013 -0700
@@ -25,10 +25,13 @@
 
 package com.sun.javafx.sg.prism;
 
+
+import com.sun.javafx.collections.FloatArraySyncer;
+import com.sun.javafx.collections.IntegerArraySyncer;
 import com.sun.javafx.sg.PGTriangleMesh;
 import com.sun.prism.Mesh;
 import com.sun.prism.ResourceFactory;
-import javafx.scene.shape.TriangleMesh;
+
 
 /**
  * TODO: 3D - Need documentation
@@ -99,109 +102,28 @@
         this.faceSmoothingGroups = faceSmoothingGroups;
     }
 
-    public void setPoints(float[] points) {
+    public void syncPoints(FloatArraySyncer array) {
         meshDirty = true;
-        if (points == null) {
-            this.points = null;
-            return;
-        }
-        if ((this.points == null) || (this.points.length != points.length)) {
-            this.points = new float[points.length];
-        }
-        System.arraycopy(points, 0, this.points, 0, this.points.length);      
+        points = array.syncTo(points);
+    }
+    
+    public void syncTexCoords(FloatArraySyncer array) {
+        meshDirty = true;
+        texCoords = array.syncTo(texCoords);
     }
 
-    public void setPoints(float[] points, int index, int length) {
+    public void syncFaces(IntegerArraySyncer array) {
         meshDirty = true;
-        if (points == null) {
-            this.points = null;
-            return;
-        }
-
-        // Range check were done in the FX layer.
-        int indexOffset = index * TriangleMesh.NUM_COMPONENTS_PER_POINT;
-        int lengthInFloatUnit = length * TriangleMesh.NUM_COMPONENTS_PER_POINT;
-        System.arraycopy(points, indexOffset, this.points, indexOffset, lengthInFloatUnit);
+        faces = array.syncTo(faces);    
     }
 
-    public void setTexCoords(float[] texCoords) {
+    public void syncFaceSmoothingGroups(IntegerArraySyncer array) {
         meshDirty = true;
-        if (texCoords == null) {
-            this.texCoords = null;
-            return;
-        }
-        if ((this.texCoords == null) || (this.texCoords.length != texCoords.length)) {
-            this.texCoords = new float[texCoords.length];
-        }
-        System.arraycopy(texCoords, 0, this.texCoords, 0, this.texCoords.length);
-    }
-
-    public void setTexCoords(float[] texCoords, int index, int length) {
-        meshDirty = true;
-        if (texCoords == null) {
-            this.texCoords = null;
-            return;
-        }
-
-        // Range check were done in the FX layer.
-        int indexOffset = index * TriangleMesh.NUM_COMPONENTS_PER_TEXCOORD;
-        int lengthInFloatUnit = length * TriangleMesh.NUM_COMPONENTS_PER_TEXCOORD;
-        System.arraycopy(texCoords, indexOffset, this.texCoords, indexOffset, lengthInFloatUnit);
-    }
-
-    public void setFaces(int[] faces) {
-        meshDirty = true;
-        if (faces == null) {
-            this.faces = null;
-            return;
-        }
-        if ((this.faces == null) || (this.faces.length != faces.length)) {
-            this.faces = new int[faces.length];
-        }
-        System.arraycopy(faces, 0, this.faces, 0, this.faces.length);
-    }
-
-    public void setFaces(int[] faces, int index, int length) {
-        meshDirty = true;
-        if (faces == null) {
-            this.faces = null;
-            return;
-        }
-
-        // Range check were done in the FX layer.
-        int indexOffset = index * TriangleMesh.NUM_COMPONENTS_PER_FACE;
-        int lengthInIntUnit = length * TriangleMesh.NUM_COMPONENTS_PER_FACE;
-        System.arraycopy(faces, indexOffset, this.faces, indexOffset, lengthInIntUnit);
-    }
-
-    public void setFaceSmoothingGroups(int[] faceSmoothingGroups) {
-        meshDirty = true;
-        if (faceSmoothingGroups == null) {
-            this.faceSmoothingGroups = null;
-            return;
-        }
-        if ((this.faceSmoothingGroups == null) || 
-                (this.faceSmoothingGroups.length != faceSmoothingGroups.length)) {
-            this.faceSmoothingGroups = new int[faceSmoothingGroups.length];
-        }
-
-        System.arraycopy(faceSmoothingGroups, 0, this.faceSmoothingGroups, 0,
-                this.faceSmoothingGroups.length);
-    }
-
-    public void setFaceSmoothingGroups(int[] faceSmoothingGroups, int index, int length) {
-        meshDirty = true;
-        if (faceSmoothingGroups == null) {
-            this.faceSmoothingGroups = null;
-            return;
-        }
-
-        // Range check were done in the FX layer.
-        System.arraycopy(faceSmoothingGroups, index, this.faceSmoothingGroups, index, length);
+        faceSmoothingGroups = array.syncTo(faceSmoothingGroups);
     }
 
     // NOTE: This method is used for unit test purpose only.
-    int[] test_getFaceSmoothingGroups() {
+    int[] test_getShiftedFaceSmoothingGroups() {
         return this.faceSmoothingGroups;
     }
 }
diff -r 3da9153bcf2d -r 0324c269b390 javafx-ui-common/src/javafx/scene/shape/Box.java
--- a/javafx-ui-common/src/javafx/scene/shape/Box.java	Mon Apr 22 17:59:31 2013 +0100
+++ b/javafx-ui-common/src/javafx/scene/shape/Box.java	Mon Apr 22 15:45:02 2013 -0700
@@ -387,8 +387,7 @@
             0, 0, 1, 1, 4, 2, 4, 2, 1, 1, 5, 3, 2, 0, 6, 2, 3, 1, 3, 1, 6, 2, 7, 3,
             0, 0, 4, 1, 2, 2, 2, 2, 4, 1, 6, 3, 1, 0, 3, 1, 5, 2, 5, 2, 3, 1, 7, 3,};
 
-        TriangleMesh mesh = new TriangleMesh(points, texCoords, faces);
-        mesh.setFaceSmoothingGroups(faceSmoothingGroups);
+        TriangleMesh mesh = new TriangleMesh(points, texCoords, faces, faceSmoothingGroups);
 
         return mesh;
     }
diff -r 3da9153bcf2d -r 0324c269b390 javafx-ui-common/src/javafx/scene/shape/Cylinder.java
--- a/javafx-ui-common/src/javafx/scene/shape/Cylinder.java	Mon Apr 22 17:59:31 2013 +0100
+++ b/javafx-ui-common/src/javafx/scene/shape/Cylinder.java	Mon Apr 22 15:45:02 2013 -0700
@@ -489,8 +489,7 @@
             smoothing[i] = 2;
         }
 
-        TriangleMesh m = new TriangleMesh(points, tPoints, faces);
-        m.setFaceSmoothingGroups(smoothing);
+        TriangleMesh m = new TriangleMesh(points, tPoints, faces, smoothing);
 
         return m;
     }
diff -r 3da9153bcf2d -r 0324c269b390 javafx-ui-common/src/javafx/scene/shape/Sphere.java
--- a/javafx-ui-common/src/javafx/scene/shape/Sphere.java	Mon Apr 22 17:59:31 2013 +0100
+++ b/javafx-ui-common/src/javafx/scene/shape/Sphere.java	Mon Apr 22 15:45:02 2013 -0700
@@ -402,8 +402,7 @@
             smoothing[i] = 1;
         }
 
-        TriangleMesh m = new TriangleMesh(points, tPoints, faces);
-        m.setFaceSmoothingGroups(smoothing);
+        TriangleMesh m = new TriangleMesh(points, tPoints, faces, smoothing);
         return m;
     }
 
diff -r 3da9153bcf2d -r 0324c269b390 javafx-ui-common/src/javafx/scene/shape/TriangleMesh.java
--- a/javafx-ui-common/src/javafx/scene/shape/TriangleMesh.java	Mon Apr 22 17:59:31 2013 +0100
+++ b/javafx-ui-common/src/javafx/scene/shape/TriangleMesh.java	Mon Apr 22 15:45:02 2013 -0700
@@ -25,6 +25,10 @@
 
 package javafx.scene.shape;
 
+import com.sun.javafx.collections.FloatArraySyncer;
+import com.sun.javafx.collections.IntegerArraySyncer;
+import javafx.collections.ObservableIntegerArray;
+import javafx.collections.ObservableFloatArray;
 import com.sun.javafx.geom.BaseBounds;
 import com.sun.javafx.geom.BoxBounds;
 import com.sun.javafx.geom.PickRay;
@@ -32,8 +36,9 @@
 import com.sun.javafx.scene.input.PickResultChooser;
 import com.sun.javafx.sg.PGTriangleMesh;
 import com.sun.javafx.tk.Toolkit;
-import javafx.beans.property.ReadOnlyIntegerProperty;
-import javafx.beans.property.ReadOnlyIntegerWrapper;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
 import javafx.geometry.Point2D;
 import javafx.geometry.Point3D;
 import javafx.scene.Node;
@@ -79,29 +84,12 @@
     public static final int NUM_COMPONENTS_PER_TEXCOORD = 2;
     public static final int NUM_COMPONENTS_PER_FACE = 6;
 
-    private float[] points;
-    private float[] texCoords;
-    private int[] faces;
-    private int[] faceSmoothingGroups;
+    private FloatArraySyncer points = new FloatArraySyncer(new ObservableFloatArray(NUM_COMPONENTS_PER_POINT), dirtyProperty());
+    private FloatArraySyncer texCoords = new FloatArraySyncer(new ObservableFloatArray(NUM_COMPONENTS_PER_TEXCOORD), dirtyProperty());
+    private IntegerArraySyncer faces = new IntegerArraySyncer(new ObservableIntegerArray(NUM_COMPONENTS_PER_FACE), dirtyProperty());
+    private IntegerArraySyncer faceSmoothingGroups = new IntegerArraySyncer(new ObservableIntegerArray(), dirtyProperty());
 
-    private boolean pointsDirty = true;
-    private boolean texCoordsDirty = true;
-    private boolean facesDirty = true;
-    private boolean fsgDirty = true;
-
-    // Partial Update constants and variables
-    private static final int RANGE_INDEX = 0;
-    private static final int RANGE_LENGTH = 1;
-    private static final int MAX_RANGE_SIZE = 2;
     private int refCount = 1;
-    private boolean pointUpdateRange = false;   
-    private int[] pointRangeInfos;
-    private boolean texCoordUpdateRange = false;
-    private int[] texCoordRangeInfos;
-    private boolean faceUpdateRange = false;
-    private int[] faceRangeInfos;
-    private boolean fsgUpdateRange = false;
-    private int[] fsgRangeInfos;
 
     private BaseBounds cachedBounds;
 
@@ -122,499 +110,66 @@
      * by NUM_COMPONENTS_PER_FACE)
      */
     public TriangleMesh(float[] points, float[] texCoords, int[] faces) {
-        setPoints(points);
-        setTexCoords(texCoords);
-        setFaces(faces);
+        this.points.get().set(points);
+        this.texCoords.get().set(texCoords);
+        this.faces.get().set(faces);
+    }
+    /**
+     * Creates a new instance of {@code TriangleMesh} class.
+     * TODO: 3D - doc. follows array semantic
+     *
+     * @param points points array (points.length must be divisible by NUM_COMPONENTS_PER_POINT)
+     * @param texCoords texCoords array (texCoords.length must be divisible by
+     * NUM_COMPONENTS_PER_TEXCOORD)
+     * @param faces faces (or triangles) array (faces.length must be divisible
+     * by NUM_COMPONENTS_PER_FACE)
+     */
+    public TriangleMesh(float[] points, float[] texCoords, int[] faces, int[] faceSmoothingGroups) {
+        this.points.get().set(points);
+        this.texCoords.get().set(texCoords);
+        this.faces.get().set(faces);
+        this.faceSmoothingGroups.get().set(faceSmoothingGroups);
     }
 
     /**
-     * The total number of points of this {@code TriangleMesh}
-     */
-    private ReadOnlyIntegerWrapper pointCount;
-
-    final void setPointCount(int value) {
-        pointCountPropertyImpl().set(value);
+     * Gets the {@code ObservableFloatArray} of points of this {@code TriangleMesh}.
+     *
+     * @return {@code ObservableFloatArray} of points where each point is
+     * represented by 3 float values x, y and z, in that order.
+     */    
+    public ObservableFloatArray getPoints() {
+        return points.get();
     }
 
     /**
-     * Retrieve total number of points of this {@code TriangleMesh}
+     * Gets the {@code ObservableFloatArray} of texture coordinates of this {@code TriangleMesh}.
      *
-     * @return the total number of points
-     */
-    public final int getPointCount() {
-        return pointCount == null ? 0 : pointCount.get();
+     * @return {@code ObservableFloatArray} array of texture coordinates
+     * where each texture coordinate is represented by 2 float values: u and v,
+     * in that order
+     */    
+    public ObservableFloatArray getTexCoords() {
+        return texCoords.get();
     }
-
-    public ReadOnlyIntegerProperty pointCountProperty() {
-        return pointCountPropertyImpl().getReadOnlyProperty();
-    }
-
-    private ReadOnlyIntegerWrapper pointCountPropertyImpl() {
-        if (pointCount == null) {
-            pointCount = new ReadOnlyIntegerWrapper(this, "pointCount");
-        }
-        return pointCount;
+ 
+    /**
+     * Gets the {@code ObservableIntegerArray} of faces, indices into the points 
+     * and texCoords arrays, of this  {@code TriangleMesh}
+     *
+     * @return {@code ObservableIntegerArray} of faces where each face is
+     * 6 integers p0, t0, p1, t1, p3, t3, where p0, p1 and p2 are indices of 
+     * points in points {@code ObservableFloatArray} and t0, t1 and t2 are 
+     * indices of texture coordinates in texCoords {@code ObservableFloatArray}.
+     * Both indices are in terms of elements (points or textCoords), not individual
+     * floats.
+     */    
+    public ObservableIntegerArray getFaces() {
+        return faces.get();
     }
 
     /**
-     * The total number of texture coordinates of this {@code TriangleMesh}
-     */
-    private ReadOnlyIntegerWrapper texCoordCount;
-
-    final void setTexCoordCount(int value) {
-        texCoordCountPropertyImpl().set(value);
-    }
-
-    /**
-     * Retrieve total number of texture coordinates of this {@code TriangleMesh}
-     *
-     * @return the total number of texture coordinates
-     */
-    public final int getTexCoordCount() {
-        return texCoordCount == null ? 0 : texCoordCount.get();
-    }
-
-    public ReadOnlyIntegerProperty texCoordCountProperty() {
-        return texCoordCountPropertyImpl().getReadOnlyProperty();
-    }
-
-    private ReadOnlyIntegerWrapper texCoordCountPropertyImpl() {
-        if (texCoordCount == null) {
-            texCoordCount = new ReadOnlyIntegerWrapper(this, "texCoordCount");
-        }
-        return texCoordCount;
-    }
-
-    /**
-     * The total number of faces of this {@code TriangleMesh}
-     */
-    private ReadOnlyIntegerWrapper faceCount;
-
-    final void setFaceCount(int value) {
-        faceCountPropertyImpl().set(value);
-    }
-
-    /**
-     * Retrieve total number of faces of this {@code TriangleMesh}
-     *
-     * @return the total number of faces
-     */
-    public final int getFaceCount() {
-        return faceCount == null ? 0 : faceCount.get();
-    }
-
-    public ReadOnlyIntegerProperty faceCountProperty() {
-        return faceCountPropertyImpl().getReadOnlyProperty();
-    }
-
-    private ReadOnlyIntegerWrapper faceCountPropertyImpl() {
-        if (faceCount == null) {
-            faceCount = new ReadOnlyIntegerWrapper(this, "faceCount");
-        }
-        return faceCount;
-    }
-    
-    /**
-     * The total number of faceSmoothingGroups of this {@code TriangleMesh}
-     */
-    private ReadOnlyIntegerWrapper faceSmoothingGroupCount;
-
-    final void setFaceSmoothingGroupCount(int value) {
-        faceSmoothingGroupCountPropertyImpl().set(value);
-    }
-
-    /**
-     * Retrieve total number of faceSmoothingGroups of this {@code TriangleMesh}
-     *
-     * @return the total number of faceSmoothingGroups
-     */
-    public final int getFaceSmoothingGroupCount() {
-        return faceSmoothingGroupCount == null ? 0 : faceSmoothingGroupCount.get();
-    }
-
-    public ReadOnlyIntegerProperty faceSmoothingGroupCountProperty() {
-        return faceSmoothingGroupCountPropertyImpl().getReadOnlyProperty();
-    }
-
-    private ReadOnlyIntegerWrapper faceSmoothingGroupCountPropertyImpl() {
-        if (faceSmoothingGroupCount == null) {
-            faceSmoothingGroupCount = new ReadOnlyIntegerWrapper(this, "faceSmoothingGroupCount");
-        }
-        return faceSmoothingGroupCount;
-    }
-
-    /**
-     * Sets the points of this {@code TriangleMesh}
-     * 
-     * @param points source array of NUM_COMPONENTS_PER_POINT * n values containing n new points.
-     */
-    public final void setPoints(float[] points) {
-        // Check that points.length is divisible by NUM_COMPONENTS_PER_POINT
-        if ((points.length % NUM_COMPONENTS_PER_POINT) != 0) {
-            throw new IllegalArgumentException("points.length has to be divisible by NUM_COMPONENTS_PER_POINT." 
-                    + " It is to store multiple x, y, and z coordinates of this mesh");
-        }
-
-        if ((this.points == null) || (this.points.length < points.length)) {
-            this.points = new float[points.length];
-        }
-        System.arraycopy(points, 0, this.points, 0, points.length);
-        // Store the valid point count.
-        // Note this.points.length can be bigger than points.length.
-        setPointCount(points.length / NUM_COMPONENTS_PER_POINT);
-
-        pointsDirty = true;
-        setDirty(true);
-    }
-    
-    /**
-     * Sets the points associated with this {@code TriangleMesh}
-     * starting at the specified {@code index} using data in {@code points} 
-     * starting at index {@code start} for {@code length} number of points.
-     * 
-     * @param index the starting destination index in this TriangleMesh's points array
-     * @param points source array of floats containing the new points
-     * @param start starting source index in the points array.
-     * @param length number of point elements to be copied.
-     */
-    public final void setPoints(int index, float[] points,
-                      int start, int length) {
-
-        if (index < 0 || start < 0 || length < 0) {
-            throw new IllegalArgumentException("index, start and length have to be non-zero");
-        }
-        int startOffset = start * NUM_COMPONENTS_PER_POINT;
-        int lengthInFloatUnit = length * NUM_COMPONENTS_PER_POINT;
-        if ((startOffset >= points.length) || ((startOffset + lengthInFloatUnit) > points.length)) {
-            throw new IllegalArgumentException("start or (start + length) is out of range for input points");
-        }
-        int indexOffset = index * NUM_COMPONENTS_PER_POINT;
-        int pointCountInFloatUnit = getPointCount() * NUM_COMPONENTS_PER_POINT;
-        if ((indexOffset >= pointCountInFloatUnit) || 
-                ((indexOffset + lengthInFloatUnit) > pointCountInFloatUnit)) {
-            throw new IllegalArgumentException("index or (index + length) is out of range for this triangle mesh's points");
-        }
-        System.arraycopy(points, startOffset, this.points, indexOffset, lengthInFloatUnit);
-
-        if (pointRangeInfos == null) {
-            pointRangeInfos = new int[MAX_RANGE_SIZE];
-        }
-
-        if (!pointUpdateRange) {
-            pointsDirty = pointUpdateRange = true;
-            pointRangeInfos[RANGE_INDEX] = index;
-            pointRangeInfos[RANGE_LENGTH] = length;
-        } else {
-            pointsDirty = true;
-            int fromIndex = Math.min(pointRangeInfos[RANGE_INDEX], index);
-            int toIndex = Math.max(pointRangeInfos[RANGE_INDEX] + pointRangeInfos[RANGE_LENGTH], index + length);
-            pointRangeInfos[RANGE_INDEX] = fromIndex;
-            pointRangeInfos[RANGE_LENGTH] = toIndex - fromIndex;
-        }
-        
-        setDirty(true);
-    }
-    
-    /**
-     * Gets the points of this {@code TriangleMesh}
-     *
-     * @param points a float array that will receive the points
-     * if it not null and has sufficient capacity.
-     * @return a float array of points
-     */
-    public final float[] getPoints(float[] points) {
-        if (this.points == null) {
-            return null;
-        }
-        int pointCountInFloatUnit = getPointCount() * NUM_COMPONENTS_PER_POINT;
-        if ((points == null) || (pointCountInFloatUnit > points.length)) {
-            points = new float[pointCountInFloatUnit];
-        }
-        System.arraycopy(this.points, 0, points, 0, pointCountInFloatUnit);
-        return points;
-    }
-
-    /**
-     * Gets the points associated with this {@code TriangleMesh} starting at the
-     * specified {@code index} for {@code length} number of points.
-     * 
-     * @param index starting source points index in this {@code TriangleMesh}
-     * @param points destination array that will receive this {@code TriangleMesh}'s points data
-     * @param length number of point elements to be copied
-     * @return a float array of points
-     */
-    public final float[] getPoints(int index, float[] points, int length) {
-        if (index < 0 || length < 0) {
-            throw new IllegalArgumentException("index and length have to be non-zero");
-        }
-
-        int lengthInFloatUnit = length * NUM_COMPONENTS_PER_POINT;
-        if (lengthInFloatUnit > points.length) {
-            throw new IllegalArgumentException("length is out of range for input points");
-        }
-        int indexOffset = index * NUM_COMPONENTS_PER_POINT;
-        int pointCountInFloatUnit = getPointCount() * NUM_COMPONENTS_PER_POINT;
-        if ((indexOffset >= pointCountInFloatUnit) || 
-                ((indexOffset + lengthInFloatUnit) > pointCountInFloatUnit)) {
-            throw new IllegalArgumentException("index or (index + length) is out of range for this triangle mesh's points");
-        }
-
-        if (this.points == null) {
-            return null;
-        }
-        System.arraycopy(this.points, indexOffset, points, 0, lengthInFloatUnit);
-        return points;
-    }
-    
-    /**
-     * Sets the texture coordinates of this {@code TriangleMesh}.
-     * 
-     * @param texCoords source array of NUM_COMPONENTS_PER_TEXCOORD * n values containing n new texCoords.
-     */
-    public final void setTexCoords(float[] texCoords) {
-        // Check that texCoords.length is divisible by NUM_COMPONENTS_PER_TEXCOORD
-        if ((texCoords.length % NUM_COMPONENTS_PER_TEXCOORD) != 0) {
-            throw new IllegalArgumentException("texCoords.length has to be divisible by NUM_COMPONENTS_PER_TEXCOORD."
-                    +" It is to store multiple u and v texture coordinates of this mesh");
-        }
-
-        if ((this.texCoords == null) || (this.texCoords.length < texCoords.length)) {
-            this.texCoords = new float[texCoords.length];
-        }
-        System.arraycopy(texCoords, 0, this.texCoords, 0, texCoords.length);
-        // Store the valid texCoords count.
-        // Note this.texCoords.length can be bigger than texCoords.length.
-        setTexCoordCount(texCoords.length / NUM_COMPONENTS_PER_TEXCOORD);
-
-        texCoordsDirty = true;
-        setDirty(true);
-    }
-
-    /**
-     * Sets the texture coordinates associated with this {@code TriangleMesh}
-     * starting at the specified {@code index} using data in {@code texCoords}
-     * starting at index {@code start} for {@code length} number of texCoords.
-     * 
-     * @param index the starting destination index in this TriangleMesh's texCoords array
-     * @param texCoords an float array containing the new texture coordinates
-     * @param start starting source index in the texture coordinates array
-     * @param length number of texCoord elements to be copied.
-     */
-    public final void setTexCoords(int index, float[] texCoords, int start,
-            int length) {
-        if (index < 0 || start < 0 || length < 0) {
-            throw new IllegalArgumentException("index, start and length have to be non-zero");
-        }
-        int startOffset = start * NUM_COMPONENTS_PER_TEXCOORD;
-        int lengthInFloatUnit = length * NUM_COMPONENTS_PER_TEXCOORD;
-        if ((startOffset >= texCoords.length) || ((startOffset + lengthInFloatUnit) > texCoords.length)) {
-            throw new IllegalArgumentException("start or (start + length) is out of range for input texCoords");
-        }
-        int indexOffset = index * NUM_COMPONENTS_PER_TEXCOORD;
-        int texCoordCountInFloatUnit = getTexCoordCount() * NUM_COMPONENTS_PER_TEXCOORD;
-        if ((indexOffset >= texCoordCountInFloatUnit) || 
-                ((indexOffset + lengthInFloatUnit) > texCoordCountInFloatUnit)) {
-            throw new IllegalArgumentException("index or (index + length) is out of range for this triangle mesh's texCoords");
-        }
-        System.arraycopy(texCoords, startOffset, this.texCoords, indexOffset, lengthInFloatUnit);
-      
-        if (texCoordRangeInfos == null) {
-            texCoordRangeInfos = new int[MAX_RANGE_SIZE];
-        }
-        if (!texCoordUpdateRange) {
-            texCoordsDirty = texCoordUpdateRange = true;
-            texCoordRangeInfos[RANGE_INDEX] = index;
-            texCoordRangeInfos[RANGE_LENGTH] = length;
-        } else {
-            texCoordsDirty = true;
-            int fromIndex = Math.min(texCoordRangeInfos[RANGE_INDEX], index);
-            int toIndex = Math.max(texCoordRangeInfos[RANGE_INDEX] + texCoordRangeInfos[RANGE_LENGTH], index + length);
-            texCoordRangeInfos[RANGE_INDEX] = fromIndex;
-            texCoordRangeInfos[RANGE_LENGTH] = toIndex - fromIndex;            
-        }
-        setDirty(true);
-    }
-
-    /**
-     * Gets the texture coordinates of this {@code TriangleMesh}.
-     *
-     * @param texCoords a float array that will receive the texture coordinates
-     * if it not null and has sufficient capacity
-     * @return a float array of texture coordinates
-     */
-    public final float[] getTexCoords(float[] texCoords) {
-        if (this.texCoords == null) {
-            return null;
-        }
-
-        int texCoordCountInFloatUnit = getTexCoordCount() * NUM_COMPONENTS_PER_TEXCOORD;
-        if ((texCoords == null) || (texCoordCountInFloatUnit > texCoords.length)) {
-            texCoords = new float[texCoordCountInFloatUnit];
-        }
-        System.arraycopy(this.texCoords, 0, texCoords, 0, texCoordCountInFloatUnit);
-        return texCoords;
-    }
-
-    /**
-     * Gets the texture coordinates associated with this {@code TriangleMesh}
-     * starting at the specified {@code index} for {@code length} number of
-     * texCoords.
-     * 
-     * @param index starting source texCoords index in this {@code TriangleMesh}
-     * @param texCoords destination array that will receive this {@code TriangleMesh}'s texCoords data 
-     * @param length number of texCoord elements to be copied
-     * @return a float array of texture coordinates
-     */
-    public final float[] getTexCoords(int index, float[] texCoords, int length) {
-        if (index < 0 || length < 0) {
-            throw new IllegalArgumentException("index and length have to be non-zero");
-        }
-
-        int lengthInFloatUnit = length * NUM_COMPONENTS_PER_TEXCOORD;
-        if (lengthInFloatUnit > texCoords.length) {
-            throw new IllegalArgumentException("length is out of range for input texCoords");
-        }
-        int indexOffset = index * NUM_COMPONENTS_PER_TEXCOORD;
-        int texCoordCountInFloatUnit = getTexCoordCount() * NUM_COMPONENTS_PER_TEXCOORD;
-        if ((indexOffset >= texCoordCountInFloatUnit) || 
-                ((indexOffset + lengthInFloatUnit) > texCoordCountInFloatUnit)) {
-            throw new IllegalArgumentException("index or (index + length) is out of range for this triangle mesh's texCoords");
-        }
-
-        if (this.texCoords == null) {
-            return null;
-        }
-        System.arraycopy(this.texCoords, indexOffset, texCoords, 0, lengthInFloatUnit);
-        return texCoords;
-    }
-
-    /**
-     * Sets the faces, indices into the points and texCoords arrays,
-     * associated with this {@code TriangleMesh}.
-     * 
-     * @param faces source array of NUM_COMPONENTS_PER_FACE * n indices 
-     * (3 point indices and 3 texCood indices) containing n new faces
-     */
-    public final void setFaces(int[] faces) {
-        // Check that faces.length is divisible by NUM_COMPONENTS_PER_FACE
-        if ((faces.length % NUM_COMPONENTS_PER_FACE) != 0) {
-            throw new IllegalArgumentException("faces.length has to be divisible by NUM_COMPONENTS_PER_FACE.");
-        }
-
-        if ((this.faces == null) || (this.faces.length < faces.length)) {
-            this.faces = new int[faces.length];
-        }
-        System.arraycopy(faces, 0, this.faces, 0, faces.length);
-        // Store the valid face count.
-        // Note this.faces.length can be bigger than faces.length.
-        setFaceCount(faces.length / NUM_COMPONENTS_PER_FACE);
-
-        facesDirty = true;
-        setDirty(true);
-    }
-
-    /**
-     * Sets the faces, indices into the points and texCoords arrays, 
-     * associated with this {@code TriangleMesh}
-     * starting at the specified{@code index} using data in {@code faces} 
-     * starting at index {@code start} for {@code length} number of faces.
-     * 
-     * @param index the starting destination index in this TriangleMesh's faces array
-     * @param faces an int array containing the new interleaved vertices
-     * @param start starting source index in the faces array.
-     * @param length number of interleaved vertex elements to be copied
-     */
-    public final void setFaces(int index, int[] faces, int start, int length) {
-        if (index < 0 || start < 0 || length < 0) {
-            throw new IllegalArgumentException("index, start and length have to be non-zero");
-        }
-        int startOffset = start * NUM_COMPONENTS_PER_FACE;
-        int lengthInIntUnit = length * NUM_COMPONENTS_PER_FACE;
-        if ((startOffset >= faces.length) || ((startOffset + lengthInIntUnit) > faces.length)) {
-            throw new IllegalArgumentException("start or (start + length) is out of range for input faces");
-        }
-        int indexOffset = index * NUM_COMPONENTS_PER_FACE;
-        int faceCountInIntUnit = getFaceCount() * NUM_COMPONENTS_PER_FACE;
-        if ((indexOffset >= faceCountInIntUnit) || 
-                ((indexOffset + lengthInIntUnit) > faceCountInIntUnit)) {
-            throw new IllegalArgumentException("index or (index + length) is out of range for this triangle mesh's faces");
-        }
-        System.arraycopy(faces, startOffset, this.faces, indexOffset, lengthInIntUnit);
-
-        if (faceRangeInfos == null) {
-            faceRangeInfos = new int[MAX_RANGE_SIZE];
-        }
-        if (!faceUpdateRange) {
-            facesDirty = faceUpdateRange = true;
-            faceRangeInfos[RANGE_INDEX] = index;
-            faceRangeInfos[RANGE_LENGTH] = length;
-        } else {
-            facesDirty = true;
-            int fromIndex = Math.min(faceRangeInfos[RANGE_INDEX], index);
-            int toIndex = Math.max(faceRangeInfos[RANGE_INDEX] + faceRangeInfos[RANGE_LENGTH], index + length);
-            faceRangeInfos[RANGE_INDEX] = fromIndex;
-            faceRangeInfos[RANGE_LENGTH] = toIndex - fromIndex;
-        }
-        setDirty(true);
-    }
-
-    /**
-     * Gets the faces, indices into the points and texCoords arrays, of this 
-     * {@code TriangleMesh}
-     *
-     * @param faces an int array that will receive the faces if it not null and 
-     * has sufficient capacity.
-     * @return an int array of faces
-     * 
-     */
-    public final int[] getFaces(int[] faces) {
-        if (this.faces == null) {
-            return null;
-        }
-
-        int faceCountInIntUnit = getFaceCount() * NUM_COMPONENTS_PER_FACE;
-        if ((faces == null) || (faceCountInIntUnit > faces.length)) {
-            faces = new int[faceCountInIntUnit];
-        }
-        System.arraycopy(this.faces, 0, faces, 0, faceCountInIntUnit);
-        return faces;
-    }
-
-    /**
-     * Gets the faces, indices into the points and texCoords arrays,
-     * associated with this {@code TriangleMesh} starting at the specified
-     * {@code index} for {@code length} number of faces.
-     * 
-     * @param index starting source faces index in this {@code TriangleMesh}
-     * @param faces destination array that will receive this {@code TriangleMesh}'s faces data
-     * @param length number of face elements to be copied
-     * @return an int array of faces
-     */
-    public final int[] getFaces(int index, int[] faces, int length) {
-        if (index < 0 || length < 0) {
-            throw new IllegalArgumentException("index and length have to be non-zero");
-        }
-
-        int lengthInIntUnit = length * NUM_COMPONENTS_PER_FACE;
-        if (lengthInIntUnit > faces.length) {
-            throw new IllegalArgumentException("length is out of range for input faces");
-        }
-        int indexOffset = index * NUM_COMPONENTS_PER_FACE;
-        int faceCountInIntUnit = getFaceCount() * NUM_COMPONENTS_PER_FACE;
-        if ((indexOffset >= faceCountInIntUnit) || 
-                ((indexOffset + lengthInIntUnit) > faceCountInIntUnit)) {
-            throw new IllegalArgumentException("index or (index + length) is out of range for this triangle mesh's faces");
-        }
-
-        if (this.faces == null) {
-            return null;
-        }
-        System.arraycopy(this.faces, indexOffset, faces, 0, lengthInIntUnit);
-        return faces;
-    }
-    
-    /**
-     * Sets the face smoothing group for each face in this {@code TriangleMesh}
+     * Gets the {@code ObservableIntegerArray} of face smoothing groups 
+     * of this {@code TriangleMesh}.
      * Smoothing affects how a mesh is rendered but it does not effect its
      * geometry. The face smoothing group value is used to control the smoothing
      * between adjacent faces.
@@ -627,155 +182,23 @@
      * when adjacent pair of faces shared a smoothing group. Otherwise the faces
      * are rendered with a hard edge between them.
      *
-     * A null faceSmoothingGroups implies all faces in this mesh have a
+     * An empty faceSmoothingGroups implies all faces in this mesh have a
      * smoothing group value of 1.
      *
-     * Note: If faceSmoothingGroups is not null, faceSmoothingGroups.length must
+     * Note: If faceSmoothingGroups is not empty, faceSmoothingGroups.length must
      * be equal to faces.length/NUM_COMPONENTS_PER_FACE.
-     */
-    public final void setFaceSmoothingGroups(int[] faceSmoothingGroups) {
-        if (faceSmoothingGroups == null) {
-            this.faceSmoothingGroups = null;
-            setFaceSmoothingGroupCount(0);
-        } else {
-            // Check that faceSmoothingGroups.length is 1/NUM_COMPONENTS_PER_FACE of faces.length
-            if (faceSmoothingGroups.length != (faces.length / NUM_COMPONENTS_PER_FACE)) {
-                throw new IllegalArgumentException("faceSmoothingGroups.length has to be equal to (faces.length / NUM_COMPONENTS_PER_FACE).");
-            }
-
-            if ((this.faceSmoothingGroups == null)
-                    || (this.faceSmoothingGroups.length < faceSmoothingGroups.length)) {
-                this.faceSmoothingGroups = new int[faceSmoothingGroups.length];
-            }
-            System.arraycopy(faceSmoothingGroups, 0, this.faceSmoothingGroups, 0, faceSmoothingGroups.length);
-            // Store the valid faceSmoothingGroup count.
-            // Note this.faceSmoothingGroups.length can be bigger than faceSmoothingGroups.length.
-            setFaceSmoothingGroupCount(faceSmoothingGroups.length);
-        }
-
-        fsgDirty = true;
-        setDirty(true);
+     */    
+    public ObservableIntegerArray getFaceSmoothingGroups() {
+        return faceSmoothingGroups.get();
     }
 
-    /**
-     * Sets the faceSmoothingGroups associated with this {@code TriangleMesh}
-     * starting at the specified {@code index} using data in {@code faceSmoothingGroups} 
-     * starting at index {@code start} for {@code length} number of faceSmoothingGroups.
-     * The face smoothing group value is used to control the smoothing
-     * between adjacent faces.
-     *
-     * The face smoothing group is represented by an array of bits and up to 32
-     * unique groups is possible. The face smoothing group value can range from
-     * zero to all 32 groups. A face is said to belong to a group is by having
-     * the associated bit set. A value of 0 implies no smoothing group or hard
-     * edges. A face can have no or more smoothing groups. Smoothing is applied
-     * when adjacent pair of faces shared a smoothing group. Otherwise the faces
-     * are rendered with a hard edge between them.
-     *
-     * @param index the starting destination index in this TriangleMesh's faceSmoothingGroups array
-     * @param points source array of floats containing the new faceSmoothingGroups
-     * @param start starting source index in the faceSmoothingGroups array.
-     * @param length number of faceSmoothingGroup elements to be copied.
-     */
-    public final void setFaceSmoothingGroups(int index, int[] faceSmoothingGroups,
-                      int start, int length) {
-
-        if (index < 0 || start < 0 || length < 0) {
-            throw new IllegalArgumentException("index, start and length have to be non-zero");
-        }
-        
-        if ((start >= faceSmoothingGroups.length) || ((start + length) > faceSmoothingGroups.length)) {
-            throw new IllegalArgumentException("start or (start + length) is out of range for input faceSmoothingGroups");
-        }
-        int fsgCount = getFaceSmoothingGroupCount();
-        if ((index >= fsgCount) || 
-                ((index + length) > fsgCount)) {
-            throw new IllegalArgumentException("index or (index + length) is out of range for this triangle mesh's faceSmoothingGroups");
-        }
-
-        System.arraycopy(faceSmoothingGroups, start, this.faceSmoothingGroups, index, length);
-              
-        if (fsgRangeInfos == null) {
-            fsgRangeInfos = new int[MAX_RANGE_SIZE];
-        }
-         if (!fsgUpdateRange) {
-            fsgDirty = fsgUpdateRange = true;
-            fsgRangeInfos[RANGE_INDEX] = index;
-            fsgRangeInfos[RANGE_LENGTH] = length;
-        } else {
-            fsgDirty = true;
-            int fromIndex = Math.min(fsgRangeInfos[RANGE_INDEX], index);
-            int toIndex = Math.max(fsgRangeInfos[RANGE_INDEX] + fsgRangeInfos[RANGE_LENGTH], index + length);
-            fsgRangeInfos[RANGE_INDEX] = fromIndex;
-            fsgRangeInfos[RANGE_LENGTH] = toIndex - fromIndex;
-        }
-        setDirty(true);
-    }
-
-    /**
-     * Gets the face smoothing group for each face in this {@code TriangleMesh}
-     * @return an int array to smoothing group bits for each face
-     */
-    public final int[] getFaceSmoothingGroups(int[] faceSmoothingGroups) {
-        if (this.faceSmoothingGroups == null) {
-            return null;
-        }
-
-        int fsgCount = getFaceSmoothingGroupCount();
-        if ((faceSmoothingGroups == null) || 
-                (fsgCount > faceSmoothingGroups.length)) {
-            faceSmoothingGroups = new int[fsgCount];
-        }
-        System.arraycopy(this.faceSmoothingGroups, 0, faceSmoothingGroups, 0, fsgCount);
-        return faceSmoothingGroups;
-    }
-
-    /**
-     * Gets the face smoothing group for each face in this {@code TriangleMesh}
-     * starting at the specified {@code index} for {@code length} number of face
-     * smoothing groups.
-     * 
-     * @param index starting source face smoothing groups index in this {@code TriangleMesh}
-     * @param faceSmoothingGroups destination array that will receive this 
-     * {@code TriangleMesh}'s faceSmoothingGroups data
-     * @param length number of faceSmoothingGroup elements to be copied
-     * @return an int array of faceSmoothingGroups
-     */
-    public final int[] getFaceSmoothingGroups(int index, int[] faceSmoothingGroups, int length) {
-        if (index < 0 || length < 0) {
-            throw new IllegalArgumentException("index and length have to be non-zero");
-        }
-
-        if (length > faceSmoothingGroups.length) {
-            throw new IllegalArgumentException("length is out of range for input faceSmoothingGroups");
-        }
-        
-        int fsgCount = getFaceSmoothingGroupCount();
-        if ((index >= fsgCount) || ((index + length) > fsgCount)) {
-            throw new IllegalArgumentException("index or (index + length) is out of range for this triangle mesh's faceSmoothingGroups");
-        }
-
-        if (this.faceSmoothingGroups == null) {
-            return null;
-        }
-        System.arraycopy(this.faceSmoothingGroups, index, faceSmoothingGroups, 0, length);
-        return faceSmoothingGroups;
-    }
-
-    @Override
-    void setDirty(boolean value) {
+    @Override void setDirty(boolean value) {
         super.setDirty(value);
         if (!value) { // false
-            pointsDirty = false;
-            texCoordsDirty = false;
-            facesDirty = false;
-            fsgDirty = false;
-            pointUpdateRange = false;
-            texCoordUpdateRange = false;
-            faceUpdateRange = false;
-            fsgUpdateRange = false;
-            // We don't clear up XXXPartialUpdateInfos array since we will
-            // overwrite every element when we update the array.   
+            points.setDirty(false);
+            texCoords.setDirty(false);
+            faces.setDirty(false);
+            faceSmoothingGroups.setDirty(false);
         }
     }
 
@@ -831,42 +254,18 @@
 
         PGTriangleMesh pgTriMesh = impl_getPGTriangleMesh();
         // sync points 
-        if (pointsDirty) {
-            if (pointUpdateRange) {
-                pgTriMesh.setPoints(points, pointRangeInfos[RANGE_INDEX],
-                        pointRangeInfos[RANGE_LENGTH]);
-            } else {
-                pgTriMesh.setPoints(points);
-            }
+        if (points.isDirty()) {
+            pgTriMesh.syncPoints(points);
         }
-        // sync texCoords
-        if (texCoordsDirty) {
-            if (texCoordUpdateRange) {
-                pgTriMesh.setTexCoords(texCoords, texCoordRangeInfos[RANGE_INDEX],
-                                       texCoordRangeInfos[RANGE_LENGTH]);
-            } else {
-                pgTriMesh.setTexCoords(texCoords);
-            }
+        if (texCoords.isDirty()) {
+            pgTriMesh.syncTexCoords(texCoords);
         }
-        // sync faces
-        if (facesDirty) {
-            if (faceUpdateRange) {
-                pgTriMesh.setFaces(faces, faceRangeInfos[RANGE_INDEX],
-                                   faceRangeInfos[RANGE_LENGTH]);
-            } else {
-                pgTriMesh.setFaces(faces);
-            }
+        if (faces.isDirty()) {
+            pgTriMesh.syncFaces(faces);
         }
-        // sync faceSmoothingGroups
-        if (fsgDirty) {
-            if (fsgUpdateRange) {
-                pgTriMesh.setFaceSmoothingGroups(faceSmoothingGroups, fsgRangeInfos[RANGE_INDEX],
-                                                 fsgRangeInfos[RANGE_LENGTH]);
-            } else {
-                pgTriMesh.setFaceSmoothingGroups(faceSmoothingGroups);
-            }
+        if (faceSmoothingGroups.isDirty()) {
+            pgTriMesh.syncFaceSmoothingGroups(faceSmoothingGroups);
         }
-
         setDirty(false);
     }
 
@@ -875,9 +274,9 @@
         if (isDirty() || cachedBounds == null) {
             cachedBounds = new BoxBounds();
 
-            final double len = points.length;
-            for (int i = 0; i < len; i += 3) {
-                cachedBounds.add(points[i], points[i + 1], points[i + 2]);
+            final double len = points.get().size();
+            for (int i = 0; i < len; i += NUM_COMPONENTS_PER_POINT) {
+                cachedBounds.add(points.get().getValue(i), points.get().getValue(i + 1), points.get().getValue(i + 2));
             }
         }
         return bounds.deriveWithNewBounds(cachedBounds);
@@ -934,13 +333,13 @@
             PickRay pickRay, Point3D origin, Point3D dir, int faceIndex,
             CullFace cullFace, Node candidate, boolean reportFace, PickResultChooser result) {
 
-        final int v0Idx = faces[faceIndex] * 3;
-        final int v1Idx = faces[faceIndex + 2] * 3;
-        final int v2Idx = faces[faceIndex + 4] * 3;
+        final int v0Idx = faces.get().getValue(faceIndex) * NUM_COMPONENTS_PER_POINT;
+        final int v1Idx = faces.get().getValue(faceIndex + 2) * NUM_COMPONENTS_PER_POINT;
+        final int v2Idx = faces.get().getValue(faceIndex + 4) * NUM_COMPONENTS_PER_POINT;
 
-        final Point3D v0 = new Point3D(points[v0Idx], points[v0Idx + 1], points[v0Idx + 2]);
-        final Point3D v1 = new Point3D(points[v1Idx], points[v1Idx + 1], points[v1Idx + 2]);
-        final Point3D v2 = new Point3D(points[v2Idx], points[v2Idx + 1], points[v2Idx + 2]);
+        final Point3D v0 = new Point3D(points.get().getValue(v0Idx), points.get().getValue(v0Idx + 1), points.get().getValue(v0Idx + 2));
+        final Point3D v1 = new Point3D(points.get().getValue(v1Idx), points.get().getValue(v1Idx + 1), points.get().getValue(v1Idx + 2));
+        final Point3D v2 = new Point3D(points.get().getValue(v2Idx), points.get().getValue(v2Idx + 1), points.get().getValue(v2Idx + 2));
 
         final Point3D e1 = v1.subtract(v0);
         final Point3D e2 = v2.subtract(v0);
@@ -1028,13 +427,13 @@
 
             // Obtain the texture triangle
 
-            final int t0Idx = faces[faceIndex + 1] * 2;
-            final int t1Idx = faces[faceIndex + 3] * 2;
-            final int t2Idx = faces[faceIndex + 5] * 2;
+            final int t0Idx = faces.get().getValue(faceIndex + 1) * NUM_COMPONENTS_PER_TEXCOORD;
+            final int t1Idx = faces.get().getValue(faceIndex + 3) * NUM_COMPONENTS_PER_TEXCOORD;
+            final int t2Idx = faces.get().getValue(faceIndex + 5) * NUM_COMPONENTS_PER_TEXCOORD;
 
-            final Point2D u0 = new Point2D(texCoords[t0Idx], texCoords[t0Idx + 1]);
-            final Point2D u1 = new Point2D(texCoords[t1Idx], texCoords[t1Idx + 1]);
-            final Point2D u2 = new Point2D(texCoords[t2Idx], texCoords[t2Idx + 1]);
+            final Point2D u0 = new Point2D(texCoords.get().getValue(t0Idx), texCoords.get().getValue(t0Idx + 1));
+            final Point2D u1 = new Point2D(texCoords.get().getValue(t1Idx), texCoords.get().getValue(t1Idx + 1));
+            final Point2D u2 = new Point2D(texCoords.get().getValue(t2Idx), texCoords.get().getValue(t2Idx + 1));
 
             final Point2D txCentroid = computeCentroid(u0, u1, u2);
 
@@ -1082,7 +481,7 @@
             Node candidate, CullFace cullFace, boolean reportFace) {
 
         boolean found = false;
-        final int size = faces.length;
+        final int size = faces.get().size();
 
         final Vec3d o = pickRay.getOriginNoClone();
         final Point3D origin = new Point3D(o.x, o.y, o.z);
diff -r 3da9153bcf2d -r 0324c269b390 test-stub-toolkit/build-closed.xml
--- a/test-stub-toolkit/build-closed.xml	Mon Apr 22 17:59:31 2013 +0100
+++ b/test-stub-toolkit/build-closed.xml	Mon Apr 22 15:45:02 2013 -0700
@@ -5,6 +5,7 @@
     <property name="runtime.dist.root.dir" value="../../rt-closed" />    
     <property name="rt.dist.root.dir" value="../../rt" />
     <path id="javac.closed.classpath.path" path="
+        ${rt.dist.root.dir}/javafx-beans/dist/javafx-beans.jar:
         ${rt.dist.root.dir}/javafx-common/dist/javafx-common.jar:
         ${rt.dist.root.dir}/javafx-anim/dist/javafx-anim.jar:
         ${rt.dist.root.dir}/javafx-geom/dist/javafx-geom.jar:
diff -r 3da9153bcf2d -r 0324c269b390 test-stub-toolkit/project.properties
--- a/test-stub-toolkit/project.properties	Mon Apr 22 17:59:31 2013 +0100
+++ b/test-stub-toolkit/project.properties	Mon Apr 22 15:45:02 2013 -0700
@@ -1,6 +1,7 @@
 JFXRT_HOME=\
     ${runtime.dist.root.dir}/../artifacts/sdk/rt
 javac.classpath=\
+    ${rt.dist.root.dir}/javafx-beans/dist/javafx-beans.jar:\
     ${rt.dist.root.dir}/javafx-common/dist/javafx-common.jar:\
     ${rt.dist.root.dir}/javafx-anim/dist/javafx-anim.jar:\
     ${rt.dist.root.dir}/javafx-geom/dist/javafx-geom.jar:\
diff -r 3da9153bcf2d -r 0324c269b390 test-stub-toolkit/src/com/sun/javafx/pgstub/StubTriangleMesh.java
--- a/test-stub-toolkit/src/com/sun/javafx/pgstub/StubTriangleMesh.java	Mon Apr 22 17:59:31 2013 +0100
+++ b/test-stub-toolkit/src/com/sun/javafx/pgstub/StubTriangleMesh.java	Mon Apr 22 15:45:02 2013 -0700
@@ -25,99 +25,32 @@
 
 package com.sun.javafx.pgstub;
 
+
+import com.sun.javafx.collections.FloatArraySyncer;
+import com.sun.javafx.collections.IntegerArraySyncer;
 import com.sun.javafx.sg.PGTriangleMesh;
-import javafx.scene.shape.TriangleMesh;
+
 
 public class StubTriangleMesh implements PGTriangleMesh {
 
     private float[] points;
     private float[] texCoords;
     private int[] faces;
-    private int[] smoothingGroups;
+    private int[] faceSmoothingGroups;
 
-
-    @Override
-    public void setPoints(float[] points) {
-        if (points == null) {
-            this.points = null;
-            return;
-        }
-        this.points = new float[points.length];
-        System.arraycopy(points, 0, this.points, 0, points.length);
+    @Override public void syncPoints(FloatArraySyncer array) {
+        points = array.syncTo(points);
+    }
+    
+    @Override public void syncTexCoords(FloatArraySyncer array) {
+        texCoords = array.syncTo(texCoords);
     }
 
-    @Override
-    public void setPoints(float[] points, int index, int length) {
-        if (points == null) {
-            this.points = null;
-            return;
-        }
-
-        int indexOffset = index * TriangleMesh.NUM_COMPONENTS_PER_POINT;
-        int lengthInFloatUnit = length * TriangleMesh.NUM_COMPONENTS_PER_POINT;
-        System.arraycopy(points, indexOffset, this.points, indexOffset, lengthInFloatUnit);
+    @Override public void syncFaces(IntegerArraySyncer array) {
+        faces = array.syncTo(faces);    
     }
 
-    @Override
-    public void setTexCoords(float[] texCoords) {
-        if (texCoords == null) {
-            this.texCoords = null;
-            return;
-        }
-        this.texCoords = new float[texCoords.length];
-        System.arraycopy(texCoords, 0, this.texCoords, 0, texCoords.length);
-    }
-
-    @Override
-    public void setTexCoords(float[] texCoords, int index, int length) {
-        if (texCoords == null) {
-            this.texCoords = null;
-            return;
-        }
-        int indexOffset = index * TriangleMesh.NUM_COMPONENTS_PER_TEXCOORD;
-        int lengthInFloatUnit = length * TriangleMesh.NUM_COMPONENTS_PER_TEXCOORD;
-        System.arraycopy(texCoords, indexOffset, this.texCoords, indexOffset, lengthInFloatUnit);
-    }
-
-    @Override
-    public void setFaces(int[] faces) {
-        if (faces == null) {
-            this.faces = null;
-            return;
-        }
-        this.faces = new int[faces.length];
-        System.arraycopy(faces, 0, this.faces, 0, faces.length);
-    }
-
-    @Override
-    public void setFaces(int[] faces, int index, int length) {
-        if (faces == null) {
-            this.faces = null;
-            return;
-        }
-        int indexOffset = index * TriangleMesh.NUM_COMPONENTS_PER_FACE;
-        int lengthInIntUnit = length * TriangleMesh.NUM_COMPONENTS_PER_FACE;
-        System.arraycopy(faces, indexOffset, this.faces, indexOffset, lengthInIntUnit);
-    }
-
-    @Override
-    public void setFaceSmoothingGroups(int[] faceSmoothingGroups) {
-        if (faceSmoothingGroups == null) {
-            this.smoothingGroups = null;
-            return;
-        }
-        this.smoothingGroups = new int[faceSmoothingGroups.length];
-        System.arraycopy(faceSmoothingGroups, 0, this.smoothingGroups, 0,
-                faceSmoothingGroups.length);
-    }
-
-    @Override
-    public void setFaceSmoothingGroups(int[] faceSmoothingGroups, int index, int length) {
-        if (faceSmoothingGroups == null) {
-            this.smoothingGroups = null;
-            return;
-        }
-
-        System.arraycopy(faceSmoothingGroups, index, this.smoothingGroups, index, length);
+    @Override public void syncFaceSmoothingGroups(IntegerArraySyncer array) {
+        faceSmoothingGroups = array.syncTo(faceSmoothingGroups);
     }
 }
