1 /* 2 * Copyright (c) 2010, 2017, 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 com.sun.javafx.property; 27 28 import static java.security.AccessController.doPrivileged; 29 30 import java.lang.reflect.Method; 31 import java.lang.reflect.Modifier; 32 import java.security.PrivilegedAction; 33 34 import javafx.beans.property.ReadOnlyProperty; 35 36 import sun.reflect.misc.ReflectUtil; 37 38 /** 39 * A handle to a specific property defined on some {@link Bean}. 40 */ 41 public final class PropertyReference<T> { 42 private String name; 43 private Method getter; 44 private Method setter; 45 private Method propertyGetter; 46 private Class<?> clazz; 47 private Class<?> type; 48 private boolean reflected = false; 49 50 // uses reflection to implement the get / set methods 51 /** 52 * Creates a new {@code PropertyReference} for a property of a bean. 53 * 54 * @param clazz 55 * The class of the {@link Bean} that contains the property 56 * @param name 57 * The name of the property 58 * @throws NullPointerException 59 * if {@code clazz} or {@code name} are null 60 * @throws IllegalArgumentException 61 * if {@code name} is an empty {@code String} 62 */ 63 public PropertyReference(Class<?> clazz, String name) { 64 if (name == null) 65 throw new NullPointerException("Name must be specified"); 66 if (name.trim().length() == 0) 67 throw new IllegalArgumentException("Name must be specified"); 68 if (clazz == null) 69 throw new NullPointerException("Class must be specified"); 70 ReflectUtil.checkPackageAccess(clazz); 71 this.name = name; 72 this.clazz = clazz; 73 } 74 75 /** 76 * Can be used to determine if a property can be set. 77 * 78 * @return {@code true}, if the property can be set, {@code false} otherwise 79 */ 80 public boolean isWritable() { 81 reflect(); 82 return setter != null; 83 } 84 85 /** 86 * Can be used to determine if a property can be get. 87 * 88 * @return {@code true}, if the property can be get, {@code false} otherwise 89 */ 90 public boolean isReadable() { 91 reflect(); 92 return getter != null; 93 } 94 95 /** 96 * Can be used to determine if a property provides an implementation of 97 * {@link javafx.beans.value.ObservableValue}. 98 * 99 * @return 100 */ 101 public boolean hasProperty() { 102 reflect(); 103 return propertyGetter != null; 104 } 105 106 /** 107 * Returns the name of the property. 108 * 109 * @return name of the propery 110 */ 111 public String getName() { 112 return name; 113 } 114 115 /** 116 * Returns the class of the {@link Bean} that contains the property. 117 * 118 * @return the class of the {@link Bean} 119 */ 120 public Class<?> getContainingClass() { 121 return clazz; 122 } 123 124 /** 125 * Returns the type of the property. This type is only evaluated correctly 126 * if a getter or a setter was found. 127 * 128 * @return the type of the property 129 */ 130 public Class<?> getType() { 131 reflect(); 132 return type; 133 } 134 135 /** 136 * Set the property to a new value. 137 * 138 * @param bean 139 * The {@link Bean} instance for which the property should be set 140 * @param value 141 * The new value 142 * @throws IllegalStateException 143 * if the property is not writable 144 */ 145 public void set(Object bean, T value) { 146 if (!isWritable()) 147 throw new IllegalStateException( 148 "Cannot write to readonly property " + name); 149 assert setter != null; 150 try { 151 MethodHelper.invoke(setter, bean, new Object[] {value}); 152 } catch (Exception ex) { 153 throw new RuntimeException(ex); 154 } 155 } 156 157 /** 158 * Get the value of the property. 159 * 160 * @param bean 161 * The {@code Bean} instance for which the property should be 162 * read 163 * @return The value of the property 164 * @throws IllegalStateException 165 * if the property is not readable 166 */ 167 @SuppressWarnings("unchecked") 168 public T get(Object bean) { 169 if (!isReadable()) 170 throw new IllegalStateException( 171 "Cannot read from unreadable property " + name); 172 assert getter != null; 173 try { 174 return (T)MethodHelper.invoke(getter, bean, (Object[])null); 175 } catch (Exception ex) { 176 throw new RuntimeException(ex); 177 } 178 } 179 180 /** 181 * Get the {@link javafx.beans.value.ObservableValue} implementation of the 182 * property. 183 * 184 * @param bean 185 * The {@code Bean} instance for which the property should be 186 * read 187 * @return The {@code ObservableValue} of the property 188 * @throws IllegalStateException 189 * if the property does not provide an implementation 190 */ 191 @SuppressWarnings("unchecked") 192 public ReadOnlyProperty<T> getProperty(Object bean) { 193 if (!hasProperty()) 194 throw new IllegalStateException("Cannot get property " + name); 195 assert propertyGetter != null; 196 try { 197 return (ReadOnlyProperty<T>)MethodHelper.invoke(propertyGetter, bean, (Object[])null); 198 } catch (Exception ex) { 199 throw new RuntimeException(ex); 200 } 201 } 202 203 /** 204 * {@inheritDoc} 205 */ 206 @Override 207 public String toString() { 208 return name; 209 } 210 211 private void reflect() { 212 // If both the getter and setter are null then we have not reflected 213 // on this property before 214 if (!reflected) { 215 reflected = true; 216 try { 217 // Since we use it in several places, construct the 218 // first-letter-capitalized version of name 219 final String properName = name.length() == 1 ? 220 name.substring(0, 1).toUpperCase() : 221 Character.toUpperCase(name.charAt(0)) 222 + name.substring(1); 223 224 // Now look for the getter. It will be named either 225 // "get" + name with the first letter of name 226 // capitalized, or it will be named "is" + name with 227 // the first letter of the name capitalized. However it 228 // is only named with "is" as a prefix if the type is 229 // boolean. 230 type = null; 231 // first we check for getXXX 232 String getterName = "get" + properName; 233 try { 234 final Method m = clazz.getMethod(getterName); 235 if (Modifier.isPublic(m.getModifiers())) { 236 getter = m; 237 } 238 } catch (NoSuchMethodException ex) { 239 // This is a legitimate error 240 } 241 242 // Then if it wasn't found we look for isXXX 243 if (getter == null) { 244 getterName = "is" + properName; 245 try { 246 final Method m = clazz.getMethod(getterName); 247 if (Modifier.isPublic(m.getModifiers())) { 248 getter = m; 249 } 250 } catch (NoSuchMethodException ex) { 251 // This is a legitimate error 252 } 253 } 254 255 // Now attempt to look for the setter. It is simply 256 // "set" + name with the first letter of name 257 // capitalized. 258 final String setterName = "set" + properName; 259 260 // If we found the getter, we can get the type 261 // and the setter easily. 262 if (getter != null) { 263 type = getter.getReturnType(); 264 try { 265 final Method m = clazz.getMethod(setterName, type); 266 if (Modifier.isPublic(m.getModifiers())) { 267 setter = m; 268 } 269 } catch (NoSuchMethodException ex) { 270 // This is a legitimate error 271 } 272 } else { // no getter found 273 final Method[] methods = clazz.getMethods(); 274 for (final Method m : methods) { 275 final Class<?>[] parameters = m.getParameterTypes(); 276 if (setterName.equals(m.getName()) 277 && (parameters.length == 1) 278 && Modifier.isPublic(m.getModifiers())) 279 { 280 setter = m; 281 type = parameters[0]; 282 break; 283 } 284 } 285 } 286 287 // Now attempt to look for the property-getter. 288 final String propertyGetterName = name + "Property"; 289 try { 290 final Method m = clazz.getMethod(propertyGetterName); 291 if (Modifier.isPublic(m.getModifiers())) { 292 propertyGetter = m; 293 } else 294 propertyGetter = null; 295 } catch (NoSuchMethodException ex) { 296 // This is a legitimate error 297 } 298 } catch (RuntimeException e) { 299 System.err.println("Failed to introspect property " + name); 300 } 301 } 302 } 303 304 /** 305 * {@inheritDoc} 306 */ 307 @Override 308 public boolean equals(Object obj) { 309 if (this == obj) { 310 return true; 311 } 312 if (!(obj instanceof PropertyReference)) { 313 return false; 314 } 315 final PropertyReference<?> other = (PropertyReference<?>) obj; 316 if (this.name != other.name 317 && (this.name == null || !this.name.equals(other.name))) { 318 return false; 319 } 320 if (this.clazz != other.clazz 321 && (this.clazz == null || !this.clazz.equals(other.clazz))) { 322 return false; 323 } 324 return true; 325 } 326 327 /** 328 * {@inheritDoc} 329 */ 330 @Override 331 public int hashCode() { 332 int hash = 5; 333 hash = 97 * hash + (this.name != null ? this.name.hashCode() : 0); 334 hash = 97 * hash + (this.clazz != null ? this.clazz.hashCode() : 0); 335 return hash; 336 } 337 }