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