1 /* 2 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.beans.property.adapter; 27 28 import com.sun.javafx.binding.ExpressionHelper; 29 import com.sun.javafx.property.adapter.Disposer; 30 import com.sun.javafx.property.adapter.PropertyDescriptor; 31 import javafx.beans.InvalidationListener; 32 import javafx.beans.property.ObjectProperty; 33 import javafx.beans.value.ChangeListener; 34 import javafx.beans.value.ObservableValue; 35 36 import java.lang.reflect.InvocationTargetException; 37 import java.lang.reflect.UndeclaredThrowableException; 38 39 import java.security.AccessController; 40 import java.security.AccessControlContext; 41 import java.security.PrivilegedAction; 42 43 import sun.reflect.misc.MethodUtil; 44 45 /** 46 * A {@code JavaBeanObjectProperty} provides an adapter between a regular 47 * Java Bean property of type {@code T} and a JavaFX 48 * {@code ObjectProperty<T>}. It cannot be created directly, but a 49 * {@link JavaBeanObjectPropertyBuilder} has to be used. 50 * <p> 51 * As a minimum, the Java Bean must implement a getter and a setter for the 52 * property. If the getter of an instance of this class is called, the property of 53 * the Java Bean is returned. If the setter is called, the value will be passed 54 * to the Java Bean property. If the Java Bean property is bound (i.e. it supports 55 * PropertyChangeListeners), this {@code JavaBeanObjectProperty} will be 56 * aware of changes in the Java Bean. Otherwise it can be notified about 57 * changes by calling {@link #fireValueChangedEvent()}. If the Java Bean property 58 * is also constrained (i.e. it supports VetoableChangeListeners), this 59 * {@code JavaBeanObjectProperty} will reject changes, if it is bound to an 60 * {@link javafx.beans.value.ObservableValue ObservableValue<Object>}. 61 * 62 * @see javafx.beans.property.ObjectProperty 63 * @see JavaBeanObjectPropertyBuilder 64 * 65 * @param T type of the wrapped {@code Object} 66 * @since JavaFX 2.1 67 */ 68 public final class JavaBeanObjectProperty<T> extends ObjectProperty<T> implements JavaBeanProperty<T> { 69 70 private final PropertyDescriptor descriptor; 71 private final PropertyDescriptor.Listener<T> listener; 72 73 private ObservableValue<? extends T> observable = null; 74 private ExpressionHelper<T> helper = null; 75 76 private final AccessControlContext acc = AccessController.getContext(); 77 78 JavaBeanObjectProperty(PropertyDescriptor descriptor, Object bean) { 79 this.descriptor = descriptor; 80 this.listener = descriptor.new Listener<T>(bean, this); 81 descriptor.addListener(listener); 82 Disposer.addRecord(this, new DescriptorListenerCleaner(descriptor, listener)); 83 } 84 85 /** 86 * {@inheritDoc} 87 * 88 * @throws UndeclaredThrowableException if calling the getter of the Java Bean 89 * property throws an {@code IllegalAccessException} or an 90 * {@code InvocationTargetException}. 91 */ 92 @SuppressWarnings("unchecked") 93 @Override 94 public T get() { 95 return AccessController.doPrivileged((PrivilegedAction<T>) () -> { 96 try { 97 return (T)MethodUtil.invoke(descriptor.getGetter(), getBean(), (Object[])null); 98 } catch (IllegalAccessException e) { 99 throw new UndeclaredThrowableException(e); 100 } catch (InvocationTargetException e) { 101 throw new UndeclaredThrowableException(e); 102 } 103 }, acc); 104 } 105 106 /** 107 * {@inheritDoc} 108 * 109 * @throws UndeclaredThrowableException if calling the getter of the Java Bean 110 * property throws an {@code IllegalAccessException} or an 111 * {@code InvocationTargetException}. 112 */ 113 @Override 114 public void set(final T value) { 115 if (isBound()) { 116 throw new RuntimeException("A bound value cannot be set."); 117 } 118 119 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 120 try { 121 MethodUtil.invoke(descriptor.getSetter(), getBean(), new Object[] {value}); 122 ExpressionHelper.fireValueChangedEvent(helper); 123 } catch (IllegalAccessException e) { 124 throw new UndeclaredThrowableException(e); 125 } catch (InvocationTargetException e) { 126 throw new UndeclaredThrowableException(e); 127 } 128 return null; 129 }, acc); 130 } 131 132 133 /** 134 * {@inheritDoc} 135 */ 136 @Override 137 public void bind(ObservableValue<? extends T> observable) { 138 if (observable == null) { 139 throw new NullPointerException("Cannot bind to null"); 140 } 141 142 if (!observable.equals(this.observable)) { 143 unbind(); 144 set(observable.getValue()); 145 this.observable = observable; 146 this.observable.addListener(listener); 147 } 148 } 149 150 /** 151 * {@inheritDoc} 152 */ 153 @Override 154 public void unbind() { 155 if (observable != null) { 156 observable.removeListener(listener); 157 observable = null; 158 } 159 } 160 161 /** 162 * {@inheritDoc} 163 */ 164 @Override 165 public boolean isBound() { 166 return observable != null; 167 } 168 169 /** 170 * {@inheritDoc} 171 */ 172 @Override 173 public Object getBean() { 174 return listener.getBean(); 175 } 176 177 /** 178 * {@inheritDoc} 179 */ 180 @Override 181 public String getName() { 182 return descriptor.getName(); 183 } 184 185 /** 186 * {@inheritDoc} 187 */ 188 @Override 189 public void addListener(ChangeListener<? super T> listener) { 190 helper = ExpressionHelper.addListener(helper, this, listener); 191 } 192 193 /** 194 * {@inheritDoc} 195 */ 196 @Override 197 public void removeListener(ChangeListener<? super T> listener) { 198 helper = ExpressionHelper.removeListener(helper, listener); 199 } 200 201 /** 202 * {@inheritDoc} 203 */ 204 @Override 205 public void addListener(InvalidationListener listener) { 206 helper = ExpressionHelper.addListener(helper, this, listener); 207 } 208 209 /** 210 * {@inheritDoc} 211 */ 212 @Override 213 public void removeListener(InvalidationListener listener) { 214 helper = ExpressionHelper.removeListener(helper, listener); 215 } 216 217 /** 218 * {@inheritDoc} 219 */ 220 @Override 221 public void fireValueChangedEvent() { 222 ExpressionHelper.fireValueChangedEvent(helper); 223 } 224 225 /** 226 * {@inheritDoc} 227 */ 228 @Override 229 public void dispose() { 230 descriptor.removeListener(listener); 231 232 } 233 234 /** 235 * Returns a string representation of this {@code JavaBeanObjectProperty} object. 236 * @return a string representation of this {@code JavaBeanObjectProperty} object. 237 */ 238 @Override 239 public String toString() { 240 final Object bean = getBean(); 241 final String name = getName(); 242 final StringBuilder result = new StringBuilder("ObjectProperty ["); 243 if (bean != null) { 244 result.append("bean: ").append(bean).append(", "); 245 } 246 if ((name != null) && (!name.equals(""))) { 247 result.append("name: ").append(name).append(", "); 248 } 249 if (isBound()) { 250 result.append("bound, "); 251 } 252 result.append("value: ").append(get()); 253 result.append("]"); 254 return result.toString(); 255 } 256 }