View Javadoc
1 /* 2 * Scope: a generic MVC framework. 3 * Copyright (c) 2000-2002, The Scope team 4 * All rights reserved. 5 * 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * Neither the name "Scope" nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 29 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 * 35 * 36 * $Id: BeansPropertyManager.java,v 1.10 2002/09/12 10:51:03 ludovicc Exp $ 37 */ 38 package org.scopemvc.model.beans; 39 40 41 import java.beans.IndexedPropertyDescriptor; 42 import java.beans.PropertyDescriptor; 43 import java.lang.reflect.InvocationTargetException; 44 import java.lang.reflect.Method; 45 import java.util.Iterator; 46 import java.util.List; 47 import org.apache.commons.logging.Log; 48 import org.apache.commons.logging.LogFactory; 49 import org.scopemvc.core.IntIndexSelector; 50 import org.scopemvc.core.PropertyManager; 51 import org.scopemvc.core.Selector; 52 import org.scopemvc.core.StringIndexSelector; 53 import org.scopemvc.util.Debug; 54 import org.scopemvc.model.util.ArraySelectorIterator; 55 import org.scopemvc.model.util.CompoundSelectorIterator; 56 import org.scopemvc.model.util.IntIndexSelectorIterator; 57 58 /*** 59 * <P> 60 * 61 * BeansPropertyManager is a {@link org.scopemvc.core.PropertyManager} that 62 * handles the properties of JavaBean model objects. </P> 63 * 64 * @author <A HREF="mailto:smeyfroi@users.sourceforge.net">Steve Meyfroidt</A> 65 * @created 05 September 2002 66 * @version $Revision: 1.10 $ $Date: 2002/09/12 10:51:03 $ 67 * @todo Use commons-beanutils functionality (ludovicc) 68 */ 69 public class BeansPropertyManager extends PropertyManager { 70 71 private static final Log LOG = LogFactory.getLog(BeansPropertyManager.class); 72 73 74 /*** 75 * <P> 76 * 77 * Return the value of the property identified by the passed {@link 78 * Selector}. If the passed Selector is null, return the model object 79 * itself. </P> 80 * 81 * @param inModel model to get property from 82 * @param inSelector identify the property to be returned or null for the 83 * model object itself. 84 * @return value of selected property 85 * @throws Exception If the value of the property could not be retrieved 86 */ 87 public Object get(Object inModel, Selector inSelector) throws Exception { 88 if (LOG.isDebugEnabled()) { 89 LOG.debug("get: " + inModel + ", " + inSelector); 90 } 91 92 if (inModel == null) { 93 // throw new IllegalArgumentException("Can't get from a null model."); 94 return null; 95 } 96 97 if (inSelector == null) { 98 return inModel; 99 } 100 101 try { 102 Accessor accessor = findTerminalAccessor(inModel, inSelector); 103 accessor.traverseProperty(); 104 return accessor.model; 105 } catch (NullPropertyException e) { 106 LOG.warn("Could not locate the property using selector " + inSelector + " in model " + inModel); 107 return null; 108 } 109 } 110 111 112 /*** 113 * <P> 114 * 115 * Is a property read-only? If the passed Selector is null then is the model 116 * object as a whole read-only? </P> <P> 117 * 118 * Enforcement of the access state must be implemented by the model itself 119 * by using the DynamicReadOnly interface. </P> 120 * 121 * @param inModel model object to test the property on. 122 * @param inSelector The property to test or null to test the whole model 123 * object. 124 * @return whether the property is read-only or not. 125 * @throws Exception If the read-only state of the property could not be 126 * tested 127 * @see DynamicReadOnly 128 */ 129 public boolean isReadOnly(Object inModel, Selector inSelector) 130 throws Exception { 131 if (inModel == null) { 132 throw new IllegalArgumentException("Can't ask isReadOnly for a null model."); 133 } 134 135 if (inSelector == null) { 136 return true; 137 // throw new ModelException(inModel, "Can't test read-only for model"); 138 } 139 140 Accessor accessor; 141 try { 142 accessor = findTerminalAccessor(inModel, inSelector); 143 } catch (Exception e) { 144 return true; 145 } 146 147 if (accessor.descriptor == null) { 148 // List elements always mutable by default 149 if (accessor.model instanceof DynamicReadOnly) { 150 return ((DynamicReadOnly) accessor.model).isPropertyReadOnly(accessor.selector); 151 } 152 return false; 153 } else if (accessor.isGetterIndexed) { 154 // Indexed setter 155 return (((IndexedPropertyDescriptor) accessor.descriptor).getIndexedWriteMethod() == null); 156 } else { 157 // Normal setter 158 if (accessor.model instanceof DynamicReadOnly) { 159 if (((DynamicReadOnly) accessor.model).isPropertyReadOnly(accessor.selector)) { 160 return true; 161 } 162 } 163 return (accessor.descriptor.getWriteMethod() == null); 164 } 165 } 166 167 168 /*** 169 * Return the Class of a property. 170 * 171 * @param inModel model to test the property for. 172 * @param inSelector property to test. 173 * @return Class of property. Never null. 174 * @throws Exception If the class of the property could not be retrieved 175 */ 176 public Class getPropertyClass(Object inModel, Selector inSelector) 177 throws Exception { 178 if (inModel == null) { 179 throw new IllegalArgumentException("Can't getPropertyClass for a null model."); 180 } 181 182 if (inSelector == null) { 183 return inModel.getClass(); 184 } 185 Accessor accessor = findTerminalAccessor(inModel, inSelector); 186 if (accessor.descriptor == null) { 187 // List 188 return Object.class; 189 // TODO: should test type of Object[] 190 } else if (accessor.isGetterIndexed) { 191 // Indexed getter 192 Method getter = ((IndexedPropertyDescriptor) accessor.descriptor).getIndexedReadMethod(); 193 if (getter == null) { 194 throw new IllegalArgumentException("No indexed getter for: " + inSelector + " in model " + inModel); 195 } 196 return getter.getReturnType(); 197 } else { 198 // Normal getter 199 Method getter = accessor.descriptor.getReadMethod(); 200 if (getter == null) { 201 throw new IllegalArgumentException("No getter for: " + inSelector + " in model " + inModel); 202 } 203 return getter.getReturnType(); 204 } 205 } 206 207 208 /*** 209 * <P> 210 * 211 * Return an Iterator that iterates over Selectors for all properties of the 212 * passed model object. </P> 213 * 214 * @param inModel model to make an Iterator for. 215 * @return Iterator that iterates over Selectors for all properties of the 216 * passed model object. 217 */ 218 public Iterator getSelectorIterator(Object inModel) { 219 if (inModel == null) { 220 throw new IllegalArgumentException("Can't getSelectorIterator for a null model."); 221 } 222 223 if (inModel instanceof Object[]) { 224 return new IntIndexSelectorIterator(0, ((Object[]) inModel).length - 1); 225 } 226 227 PropertyDescriptor[] descriptors = BeanInfos.getBeanInfo(inModel.getClass()).getPropertyDescriptors(); 228 Selector[] selectors = new Selector[descriptors.length]; 229 for (int i = 0; i < descriptors.length; ++i) { 230 selectors[i] = Selector.fromString(descriptors[i].getName()); 231 // TODO: cache these! 232 } 233 Iterator result = new ArraySelectorIterator(selectors); 234 235 if (inModel instanceof List) { 236 return new CompoundSelectorIterator(result, new IntIndexSelectorIterator(0, ((List) inModel).size() - 1)); 237 } else { 238 return result; 239 } 240 } 241 242 243 /*** 244 * <P> 245 * 246 * Tries to find a Selector that would get() a property equals() to the 247 * passed Object. If the model is a java.util.List, call indexOf() to search 248 * contents first. For Object[] search through contents. Else get an 249 * Iterator over all properties and iterate over to find a match. </P> <P> 250 * 251 * Note: doesn't search through JavaBeans indexed properties for a match. 252 * </P> 253 * 254 * @param inProperty the property Object to find. 255 * @param inModel the model to get the property from. 256 * @return a Selector that would return an Object equals() to the passed 257 * property, or null if not found. 258 */ 259 public Selector getSelectorFor(Object inModel, Object inProperty) { 260 if (inModel == null) { 261 throw new IllegalArgumentException("Can't getSelectorFor for a null model."); 262 } 263 264 if (inProperty == null) { 265 return null; 266 } 267 268 if (inModel instanceof java.util.List) { 269 int i = ((java.util.List) inModel).indexOf(inProperty); 270 if (i < 0) { 271 return null; 272 } 273 return Selector.fromInt(i); 274 } 275 276 if (inModel instanceof Object[]) { 277 Object[] array = (Object[]) inModel; 278 for (int i = array.length - 1; i >= 0; --i) { 279 Object o = array[i]; 280 if (o != null && o.equals(inProperty)) { 281 return Selector.fromInt(i); 282 } 283 } 284 return null; 285 } 286 287 Iterator i = getSelectorIterator(inModel); 288 if (Debug.ON) { 289 Debug.assertTrue(i != null, "null Iterator"); 290 } 291 while (i.hasNext()) { 292 Object o = i.next(); 293 if (Debug.ON) { 294 Debug.assertTrue(o == null || o instanceof Selector, "Iterator doesn't contain Selector: " + o); 295 } 296 Selector s = (Selector) o; 297 try { 298 o = get(inModel, s); 299 if (o != null && o.equals(inProperty)) { 300 return s; 301 } 302 } catch (Exception e) { 303 // ignore that property and carry on 304 } 305 } 306 return null; 307 } 308 309 310 /*** 311 * <P> 312 * 313 * Set the value of the property identified by a {@link Selector} in the 314 * passed model object to a new value. </P> <P> 315 * 316 * The implementation should not set the value if the new value has the same 317 * Object reference as the original. It could also avoid setting the value 318 * if the new value is equivalent to the old value, and the value is of an 319 * immutable Class (like Integer, String). Otherwise the property must be 320 * set to the new value, even if it equals() the old value. </P> <P> 321 * 322 * Usually, a {@link ModelChangeEvent} for {@link 323 * ModelChangeEvent#VALUE_CHANGED} should be broadcast by the model when the 324 * property is set so that interested listeners know that the model's state 325 * has changed. </P> <P> 326 * 327 * If the property is a sub-model object then the parent model should be 328 * registered as a {@link ModelChangeListener} to be able to propagate 329 * events properly. This propagation is partially implemented in {@link 330 * org.scopemvc.model.basic.BasicModel} but it relies on child Models being 331 * listened to by their parent. (Note: deregister from the old Model then 332 * register with the new one). See the sample code for examples using {@link 333 * org.scopemvc.model.basic.BasicModel#listenNewSubmodel} and {@link 334 * org.scopemvc.model.basic.BasicModel#unlistenOldSubmodel}. </P> 335 * 336 * @param inModel model to set the property on. 337 * @param inSelector identify the property to be set. Can't be null. 338 * @param inValue the value to set the property to. 339 * @throws Exception if the value could not be set in the model 340 * @todo Could add convenience methods for primitive types? (smefroy) 341 */ 342 public void set(Object inModel, Selector inSelector, Object inValue) 343 throws Exception { 344 345 if (inModel == null) { 346 throw new IllegalArgumentException("Can't set for a null model."); 347 } 348 349 if (inSelector == null) { 350 throw new IllegalArgumentException("Can't set value of model (passed Selector is null)"); 351 } 352 353 Accessor accessor; 354 try { 355 accessor = findTerminalAccessor(inModel, inSelector); 356 if (Debug.ON) { 357 Debug.assertTrue(accessor.selector != null, "null accessor.selector"); 358 } 359 if (LOG.isDebugEnabled()) { 360 LOG.debug("set: accessor: " + accessor + ", value: " + inValue); 361 } 362 } catch (NullPropertyException e) { 363 throw new UnsupportedOperationException("Can't set property with selector: " + Selector.asString(inSelector) + " in model: " + inModel); 364 } 365 366 try { 367 368 if (accessor.descriptor == null) { 369 // java.util.List or Object[] 370 if (LOG.isDebugEnabled()) { 371 LOG.debug("set: int indexed property"); 372 } 373 if (Debug.ON) { 374 Debug.assertTrue(accessor.selector instanceof IntIndexSelector); 375 } 376 int index = ((IntIndexSelector) accessor.selector).getIndex(); 377 Object model = accessor.model; 378 if (model instanceof List) { 379 ((List) model).set(index, inValue); 380 } else if (model instanceof Object[]) { 381 ((Object[]) model)[index] = inValue; 382 } else { 383 throw new IllegalArgumentException("Can't access properties using int index (expected List or Object[]) with selector: " + Selector.asString(inSelector) + " in model: " + inModel); 384 } 385 386 } else if (accessor.isGetterIndexed) { 387 // Indexed setter 388 if (LOG.isDebugEnabled()) { 389 LOG.debug("set: indexed"); 390 } 391 if (Debug.ON) { 392 Debug.assertTrue(accessor.descriptor instanceof IndexedPropertyDescriptor); 393 } 394 Method setter = ((IndexedPropertyDescriptor) accessor.descriptor).getIndexedWriteMethod(); 395 if (setter == null) { 396 throw new IllegalArgumentException("Can't find indexed setter for selector: " + Selector.asString(inSelector) + " in model: " + inModel); 397 } 398 if (Debug.ON) { 399 Debug.assertTrue(accessor.selector instanceof StringIndexSelector); 400 } 401 if (Debug.ON) { 402 Debug.assertTrue(accessor.selector.getNext() instanceof IntIndexSelector, "not IntIndexSelector: " + accessor.selector); 403 } 404 Integer index = new Integer(((IntIndexSelector) accessor.selector.getNext()).getIndex()); 405 Object[] params = {index, inValue}; 406 setter.invoke(accessor.model, params); 407 408 } else { 409 // Normal setter 410 Method setter = ((PropertyDescriptor) accessor.descriptor).getWriteMethod(); 411 if (LOG.isDebugEnabled()) { 412 LOG.debug("set: normal: " + setter); 413 } 414 if (setter == null) { 415 throw new IllegalArgumentException("Can't find setter for selector: " + Selector.asString(inSelector) + " in model: " + inModel); 416 } 417 Object[] params = {inValue}; 418 setter.invoke(accessor.model, params); 419 } 420 421 } catch (InvocationTargetException e) { 422 if (LOG.isDebugEnabled()) { 423 LOG.debug("set: can't invoke setter for selector: " + Selector.asString(inSelector) + " in model: " + inModel, e); 424 } 425 if (e.getTargetException() instanceof Exception) { 426 throw (Exception) e.getTargetException(); 427 } else { 428 throw e; 429 } 430 } catch (IllegalAccessException e1) { 431 if (LOG.isDebugEnabled()) { 432 LOG.debug("set: no accessible setter for selector: " + Selector.asString(inSelector) + " in model: " + inModel, e1); 433 } 434 throw new IllegalArgumentException("Illegal access to setter for selector: " + Selector.asString(inSelector) + " in model: " + inModel); 435 } catch (IndexOutOfBoundsException e2) { 436 if (LOG.isDebugEnabled()) { 437 LOG.debug("set: IndexOutOfBoundsException: " + inSelector, e2); 438 } 439 throw new IllegalArgumentException("Can't access property for selector: " + Selector.asString(inSelector) + " in model: " + inModel); 440 } catch (NullPointerException e3) { 441 if (LOG.isDebugEnabled()) { 442 LOG.debug("set: NullPointerException: " + inSelector, e3); 443 } 444 throw new IllegalArgumentException("Can't access property for selector: " + Selector.asString(inSelector) + " in model: " + inModel); 445 } 446 } 447 448 449 /*** 450 * <P> 451 * 452 * Set the value of the property identified by a {@link Selector} in the 453 * passed model object to a new value. </P> <P> 454 * 455 * The implementation should not set the value if the new value has the same 456 * Object reference as the original. It could also avoid setting the value 457 * if the new value is equivalent to the old value, and the value is of an 458 * immutable Class (like Integer, String). Otherwise the property must be 459 * set to the new value, even if it equals() the old value. </P> <P> 460 * 461 * Usually, a {@link ModelChangeEvent} for {@link 462 * ModelChangeEvent#VALUE_CHANGED} should be broadcast by the model when the 463 * property is set so that interested listeners know that the model's state 464 * has changed. </P> <P> 465 * 466 * If the property is a sub-model object then the parent model should be 467 * registered as a {@link ModelChangeListener} to be able to propagate 468 * events properly. This propagation is partially implemented in {@link 469 * org.scopemvc.model.basic.BasicModel} but it relies on child Models being 470 * listened to by their parent. (Note: deregister from the old Model then 471 * register with the new one). See the sample code for examples using {@link 472 * org.scopemvc.model.basic.BasicModel#listenNewSubmodel} and {@link 473 * org.scopemvc.model.basic.BasicModel#unlistenOldSubmodel}. </P> 474 * 475 * @param inModel model to set the property on. 476 * @param inSelector identify the property to be set. Can't be null. 477 * @return TODO: Describe the Return Value 478 * @throws Exception if the value could not be set in the model 479 */ 480 public boolean hasProperty(Object inModel, Selector inSelector) { 481 if (inModel == null) { 482 throw new IllegalArgumentException("Can't ask hasProperty for a null model."); 483 } 484 485 if (inSelector == null) { 486 return true; 487 } 488 try { 489 Accessor accessor = findTerminalAccessor(inModel, inSelector); 490 accessor.traverseProperty(); 491 return true; 492 } catch (Exception e) { 493 return false; 494 // ***** this is a bit nasty 495 } 496 } 497 498 499 // ------------------------ Working directly with JavaBeans properties ------------------------ 500 501 /*** 502 * Burrow down through submodels to find the terminal accessor (which could 503 * be a JavaBeans indexed property) from the passed model and selector. 504 * 505 * @param inModel model to get the accessor on. 506 * @param inSelector identify the property. Can't be null. 507 * @return The terminal accessor 508 * @throws Exception if the Accessor could not be found for any reason 509 */ 510 Accessor findTerminalAccessor(Object inModel, Selector inSelector) 511 throws Exception { 512 if (LOG.isDebugEnabled()) { 513 LOG.debug("findTerminalAccessor: " + inModel + ", " + inSelector); 514 } 515 if (Debug.ON) { 516 Debug.assertTrue(inModel != null, "null model"); 517 } 518 if (Debug.ON) { 519 Debug.assertTrue(inSelector != null, "null Selector"); 520 } 521 522 Accessor accessor = new Accessor(inModel, inSelector); 523 accessor.findProperty(); 524 while (!accessor.isAtTerminal()) { 525 if (LOG.isDebugEnabled()) { 526 LOG.debug("findTerminalAccessor: not at terminal: " + accessor.selector); 527 } 528 accessor.traverseProperty(); 529 accessor.findProperty(); 530 } 531 if (LOG.isDebugEnabled()) { 532 LOG.debug("findTerminalAccessor: found terminal: " + accessor.selector); 533 } 534 return accessor; 535 } 536 537 }

This page was automatically generated by Maven