001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.beanutils; 018 019import java.beans.BeanInfo; 020import java.beans.IntrospectionException; 021import java.beans.Introspector; 022import java.beans.PropertyDescriptor; 023import java.lang.reflect.Constructor; 024import java.lang.reflect.InvocationTargetException; 025import java.lang.reflect.Method; 026import java.util.AbstractMap; 027import java.util.AbstractSet; 028import java.util.ArrayList; 029import java.util.Collection; 030import java.util.Collections; 031import java.util.HashMap; 032import java.util.Iterator; 033import java.util.Map; 034import java.util.Set; 035 036import org.apache.commons.collections.Transformer; 037import org.apache.commons.collections.keyvalue.AbstractMapEntry; 038 039/** 040 * An implementation of Map for JavaBeans which uses introspection to 041 * get and put properties in the bean. 042 * <p> 043 * If an exception occurs during attempts to get or set a property then the 044 * property is considered non existent in the Map 045 * 046 */ 047public class BeanMap extends AbstractMap<Object, Object> implements Cloneable { 048 049 /** 050 * Map entry used by {@link BeanMap}. 051 */ 052 protected static class Entry extends AbstractMapEntry { 053 private final BeanMap owner; 054 055 /** 056 * Constructs a new <code>Entry</code>. 057 * 058 * @param owner the BeanMap this entry belongs to 059 * @param key the key for this entry 060 * @param value the value for this entry 061 */ 062 protected Entry( final BeanMap owner, final Object key, final Object value ) { 063 super( key, value ); 064 this.owner = owner; 065 } 066 067 /** 068 * Sets the value. 069 * 070 * @param value the new value for the entry 071 * @return the old value for the entry 072 */ 073 @Override 074 public Object setValue(final Object value) { 075 final Object key = getKey(); 076 final Object oldValue = owner.get( key ); 077 078 owner.put( key, value ); 079 final Object newValue = owner.get( key ); 080 super.setValue( newValue ); 081 return oldValue; 082 } 083 } 084 085 /** 086 * An empty array. Used to invoke accessors via reflection. 087 */ 088 public static final Object[] NULL_ARGUMENTS = {}; 089 /** 090 * Maps primitive Class types to transformers. The transformer 091 * transform strings into the appropriate primitive wrapper. 092 * 093 * N.B. private & unmodifiable replacement for the (public & static) defaultTransformers instance. 094 */ 095 private static final Map<Class<? extends Object>, Transformer> typeTransformers = 096 Collections.unmodifiableMap(createTypeTransformers()); 097 /** 098 * This HashMap has been made unmodifiable to prevent issues when 099 * loaded in a shared ClassLoader enviroment. 100 * 101 * @see "http://issues.apache.org/jira/browse/BEANUTILS-112" 102 * @deprecated Use {@link BeanMap#getTypeTransformer(Class)} method 103 */ 104 @Deprecated 105 public static HashMap defaultTransformers = new HashMap() { 106 private static final long serialVersionUID = 1L; 107 @Override 108 public void clear() { 109 throw new UnsupportedOperationException(); 110 } 111 @Override 112 public boolean containsKey(final Object key) { 113 return typeTransformers.containsKey(key); 114 } 115 @Override 116 public boolean containsValue(final Object value) { 117 return typeTransformers.containsValue(value); 118 } 119 @Override 120 public Set entrySet() { 121 return typeTransformers.entrySet(); 122 } 123 @Override 124 public Object get(final Object key) { 125 return typeTransformers.get(key); 126 } 127 @Override 128 public boolean isEmpty() { 129 return false; 130 } 131 @Override 132 public Set keySet() { 133 return typeTransformers.keySet(); 134 } 135 @Override 136 public Object put(final Object key, final Object value) { 137 throw new UnsupportedOperationException(); 138 } 139 @Override 140 public void putAll(final Map m) { 141 throw new UnsupportedOperationException(); 142 } 143 @Override 144 public Object remove(final Object key) { 145 throw new UnsupportedOperationException(); 146 } 147 @Override 148 public int size() { 149 return typeTransformers.size(); 150 } 151 @Override 152 public Collection values() { 153 return typeTransformers.values(); 154 } 155 }; 156 157 private static Map<Class<? extends Object>, Transformer> createTypeTransformers() { 158 final Map<Class<? extends Object>, Transformer> defaultTransformers = 159 new HashMap<>(); 160 defaultTransformers.put( 161 Boolean.TYPE, 162 input -> Boolean.valueOf( input.toString() ) 163 ); 164 defaultTransformers.put( 165 Character.TYPE, 166 input -> Character.valueOf(input.toString().charAt( 0 )) 167 ); 168 defaultTransformers.put( 169 Byte.TYPE, 170 input -> Byte.valueOf( input.toString() ) 171 ); 172 defaultTransformers.put( 173 Short.TYPE, 174 input -> Short.valueOf( input.toString() ) 175 ); 176 defaultTransformers.put( 177 Integer.TYPE, 178 input -> Integer.valueOf( input.toString() ) 179 ); 180 defaultTransformers.put( 181 Long.TYPE, 182 input -> Long.valueOf( input.toString() ) 183 ); 184 defaultTransformers.put( 185 Float.TYPE, 186 input -> Float.valueOf( input.toString() ) 187 ); 188 defaultTransformers.put( 189 Double.TYPE, 190 input -> Double.valueOf( input.toString() ) 191 ); 192 return defaultTransformers; 193 } 194 195 private transient Object bean; 196 197 private transient HashMap<String, Method> readMethods = new HashMap<>(); 198 199 private transient HashMap<String, Method> writeMethods = new HashMap<>(); 200 201 // Constructors 202 //------------------------------------------------------------------------- 203 204 private transient HashMap<String, Class<? extends Object>> types = new HashMap<>(); 205 206 /** 207 * Constructs a new empty <code>BeanMap</code>. 208 */ 209 public BeanMap() { 210 } 211 212 // Map interface 213 //------------------------------------------------------------------------- 214 215 /** 216 * Constructs a new <code>BeanMap</code> that operates on the 217 * specified bean. If the given bean is <code>null</code>, then 218 * this map will be empty. 219 * 220 * @param bean the bean for this map to operate on 221 */ 222 public BeanMap(final Object bean) { 223 this.bean = bean; 224 initialise(); 225 } 226 227 /** 228 * This method reinitializes the bean map to have default values for the 229 * bean's properties. This is accomplished by constructing a new instance 230 * of the bean which the map uses as its underlying data source. This 231 * behavior for <code>clear()</code> differs from the Map contract in that 232 * the mappings are not actually removed from the map (the mappings for a 233 * BeanMap are fixed). 234 */ 235 @Override 236 public void clear() { 237 if (bean == null) { 238 return; 239 } 240 Class<? extends Object> beanClass = null; 241 try { 242 beanClass = bean.getClass(); 243 bean = beanClass.getConstructor().newInstance(); 244 } catch (final Exception e) { 245 throw new UnsupportedOperationException("Could not create new instance of class: " + beanClass, e); 246 } 247 } 248 249 /** 250 * Clone this bean map using the following process: 251 * 252 * <ul> 253 * <li>If there is no underlying bean, return a cloned BeanMap without a 254 * bean. 255 * </li> 256 * <li>Since there is an underlying bean, try to instantiate a new bean of 257 * the same type using Class.newInstance(). 258 * </li> 259 * <li>If the instantiation fails, throw a CloneNotSupportedException 260 * </li> 261 * <li>Clone the bean map and set the newly instantiated bean as the 262 * underlying bean for the bean map. 263 * </li> 264 * <li>Copy each property that is both readable and writable from the 265 * existing object to a cloned bean map. 266 * </li> 267 * <li>If anything fails along the way, throw a 268 * CloneNotSupportedException. 269 * </li> 270 * </ul> 271 * 272 * @return a cloned instance of this bean map 273 * @throws CloneNotSupportedException if the underlying bean 274 * cannot be cloned 275 */ 276 @Override 277 public Object clone() throws CloneNotSupportedException { 278 final BeanMap newMap = (BeanMap) super.clone(); 279 if (bean == null) { 280 // no bean, just an empty bean map at the moment. return a newly 281 // cloned and empty bean map. 282 return newMap; 283 } 284 Object newBean = null; 285 final Class<? extends Object> beanClass = bean.getClass(); // Cannot throw Exception 286 try { 287 newBean = beanClass.getConstructor().newInstance(); 288 } catch (final Exception e) { 289 // unable to instantiate 290 final CloneNotSupportedException cnse = new CloneNotSupportedException( 291 "Unable to instantiate the underlying bean \"" + beanClass.getName() + "\": " + e); 292 cnse.initCause(e); 293 throw cnse; 294 } 295 try { 296 newMap.setBean(newBean); 297 } catch (final Exception exception) { 298 final CloneNotSupportedException cnse = new CloneNotSupportedException("Unable to set bean in the cloned bean map: " + exception); 299 cnse.initCause(exception); 300 throw cnse; 301 } 302 try { 303 for (final Object key : readMethods.keySet()) { 304 if (getWriteMethod(key) != null) { 305 newMap.put(key, get(key)); 306 } 307 } 308 } catch (final Exception exception) { 309 final CloneNotSupportedException cnse = new CloneNotSupportedException("Unable to copy bean values to cloned bean map: " + exception); 310 cnse.initCause(exception); 311 throw cnse; 312 } 313 314 return newMap; 315 } 316 317 /** 318 * Returns true if the bean defines a property with the given name. 319 * <p> 320 * The given name must be a <code>String</code>; if not, this method 321 * returns false. This method will also return false if the bean 322 * does not define a property with that name. 323 * <p> 324 * Write-only properties will not be matched as the test operates against 325 * property read methods. 326 * 327 * @param name the name of the property to check 328 * @return false if the given name is null or is not a <code>String</code>; 329 * false if the bean does not define a property with that name; or 330 * true if the bean does define a property with that name 331 */ 332 @Override 333 public boolean containsKey(final Object name) { 334 final Method method = getReadMethod(name); 335 return method != null; 336 } 337 338 /** 339 * Returns true if the bean defines a property whose current value is 340 * the given object. 341 * 342 * @param value the value to check 343 * @return false true if the bean has at least one property whose 344 * current value is that object, false otherwise 345 */ 346 @Override 347 public boolean containsValue(final Object value) { 348 // use default implementation 349 return super.containsValue(value); 350 } 351 352 /** 353 * Converts the given value to the given type. First, reflection is 354 * is used to find a public constructor declared by the given class 355 * that takes one argument, which must be the precise type of the 356 * given value. If such a constructor is found, a new object is 357 * created by passing the given value to that constructor, and the 358 * newly constructed object is returned.<P> 359 * 360 * If no such constructor exists, and the given type is a primitive 361 * type, then the given value is converted to a string using its 362 * {@link Object#toString() toString()} method, and that string is 363 * parsed into the correct primitive type using, for instance, 364 * {@link Integer#valueOf(String)} to convert the string into an 365 * <code>int</code>.<P> 366 * 367 * If no special constructor exists and the given type is not a 368 * primitive type, this method returns the original value. 369 * 370 * @param newType the type to convert the value to 371 * @param value the value to convert 372 * @return the converted value 373 * @throws NumberFormatException if newType is a primitive type, and 374 * the string representation of the given value cannot be converted 375 * to that type 376 * @throws InstantiationException if the constructor found with 377 * reflection raises it 378 * @throws InvocationTargetException if the constructor found with 379 * reflection raises it 380 * @throws IllegalAccessException never 381 * @throws IllegalArgumentException never 382 */ 383 protected Object convertType( final Class<?> newType, final Object value ) 384 throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 385 386 // try call constructor 387 final Class<?>[] types = { value.getClass() }; 388 try { 389 final Constructor<?> constructor = newType.getConstructor( types ); 390 final Object[] arguments = { value }; 391 return constructor.newInstance( arguments ); 392 } 393 catch ( final NoSuchMethodException e ) { 394 // try using the transformers 395 final Transformer transformer = getTypeTransformer( newType ); 396 if ( transformer != null ) { 397 return transformer.transform( value ); 398 } 399 return value; 400 } 401 } 402 403 /** 404 * Creates an array of parameters to pass to the given mutator method. 405 * If the given object is not the right type to pass to the method 406 * directly, it will be converted using {@link #convertType(Class,Object)}. 407 * 408 * @param method the mutator method 409 * @param value the value to pass to the mutator method 410 * @return an array containing one object that is either the given value 411 * or a transformed value 412 * @throws IllegalAccessException if {@link #convertType(Class,Object)} 413 * raises it 414 * @throws IllegalArgumentException if any other exception is raised 415 * by {@link #convertType(Class,Object)} 416 * @throws ClassCastException if an error occurs creating the method args 417 */ 418 protected Object[] createWriteMethodArguments( final Method method, Object value ) 419 throws IllegalAccessException, ClassCastException { 420 try { 421 if ( value != null ) { 422 final Class<? extends Object>[] types = method.getParameterTypes(); 423 if ( types != null && types.length > 0 ) { 424 final Class<? extends Object> paramType = types[0]; 425 if ( ! paramType.isAssignableFrom( value.getClass() ) ) { 426 value = convertType( paramType, value ); 427 } 428 } 429 } 430 return new Object[] { value }; 431 } 432 catch ( final InvocationTargetException | InstantiationException e ) { 433 throw new IllegalArgumentException(e.getMessage(), e); 434 } 435 } 436 437 /** 438 * Convenience method for getting an iterator over the entries. 439 * 440 * @return an iterator over the entries 441 */ 442 public Iterator<Map.Entry<Object, Object>> entryIterator() { 443 final Iterator<String> iter = keyIterator(); 444 return new Iterator<Map.Entry<Object, Object>>() { 445 @Override 446 public boolean hasNext() { 447 return iter.hasNext(); 448 } 449 @Override 450 public Map.Entry<Object, Object> next() { 451 final Object key = iter.next(); 452 final Object value = get(key); 453 @SuppressWarnings("unchecked") 454 final 455 // This should not cause any problems; the key is actually a 456 // string, but it does no harm to expose it as Object 457 Map.Entry<Object, Object> tmpEntry = new Entry( BeanMap.this, key, value ); 458 return tmpEntry; 459 } 460 @Override 461 public void remove() { 462 throw new UnsupportedOperationException( "remove() not supported for BeanMap" ); 463 } 464 }; 465 } 466 467 /** 468 * Gets a Set of MapEntry objects that are the mappings for this BeanMap. 469 * <p> 470 * Each MapEntry can be set but not removed. 471 * 472 * @return the unmodifiable set of mappings 473 */ 474 @Override 475 public Set<Map.Entry<Object, Object>> entrySet() { 476 return Collections.unmodifiableSet(new AbstractSet<Map.Entry<Object, Object>>() { 477 @Override 478 public Iterator<Map.Entry<Object, Object>> iterator() { 479 return entryIterator(); 480 } 481 @Override 482 public int size() { 483 return BeanMap.this.readMethods.size(); 484 } 485 }); 486 } 487 488 /** 489 * Called during a successful {@link #put(Object,Object)} operation. 490 * Default implementation does nothing. Override to be notified of 491 * property changes in the bean caused by this map. 492 * 493 * @param key the name of the property that changed 494 * @param oldValue the old value for that property 495 * @param newValue the new value for that property 496 */ 497 protected void firePropertyChange( final Object key, final Object oldValue, final Object newValue ) { 498 } 499 500 /** 501 * Returns the value of the bean's property with the given name. 502 * <p> 503 * The given name must be a {@link String} and must not be 504 * null; otherwise, this method returns <code>null</code>. 505 * If the bean defines a property with the given name, the value of 506 * that property is returned. Otherwise, <code>null</code> is 507 * returned. 508 * <p> 509 * Write-only properties will not be matched as the test operates against 510 * property read methods. 511 * 512 * @param name the name of the property whose value to return 513 * @return the value of the property with that name 514 */ 515 @Override 516 public Object get(final Object name) { 517 if ( bean != null ) { 518 final Method method = getReadMethod( name ); 519 if ( method != null ) { 520 try { 521 return method.invoke( bean, NULL_ARGUMENTS ); 522 } 523 catch ( final IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException e ) { 524 logWarn( e ); 525 } 526 } 527 } 528 return null; 529 } 530 531 /** 532 * Returns the bean currently being operated on. The return value may 533 * be null if this map is empty. 534 * 535 * @return the bean being operated on by this map 536 */ 537 public Object getBean() { 538 return bean; 539 } 540 541 // Helper methods 542 //------------------------------------------------------------------------- 543 544 /** 545 * Returns the accessor for the property with the given name. 546 * 547 * @param name the name of the property 548 * @return null if the name is null; null if the name is not a 549 * {@link String}; null if no such property exists; or the accessor 550 * method for that property 551 */ 552 protected Method getReadMethod( final Object name ) { 553 return readMethods.get( name ); 554 } 555 556 /** 557 * Returns the accessor for the property with the given name. 558 * 559 * @param name the name of the property 560 * @return the accessor method for the property, or null 561 */ 562 public Method getReadMethod(final String name) { 563 return readMethods.get(name); 564 } 565 566 /** 567 * Returns the type of the property with the given name. 568 * 569 * @param name the name of the property 570 * @return the type of the property, or <code>null</code> if no such 571 * property exists 572 */ 573 public Class<?> getType(final String name) { 574 return types.get( name ); 575 } 576 577 /** 578 * Returns a transformer for the given primitive type. 579 * 580 * @param aType the primitive type whose transformer to return 581 * @return a transformer that will convert strings into that type, 582 * or null if the given type is not a primitive type 583 */ 584 protected Transformer getTypeTransformer( final Class<?> aType ) { 585 return typeTransformers.get( aType ); 586 } 587 588 // Properties 589 //------------------------------------------------------------------------- 590 591 /** 592 * Returns the mutator for the property with the given name. 593 * 594 * @param name the name of the 595 * @return null if the name is null; null if the name is not a 596 * {@link String}; null if no such property exists; null if the 597 * property is read-only; or the mutator method for that property 598 */ 599 protected Method getWriteMethod( final Object name ) { 600 return writeMethods.get( name ); 601 } 602 603 /** 604 * Returns the mutator for the property with the given name. 605 * 606 * @param name the name of the property 607 * @return the mutator method for the property, or null 608 */ 609 public Method getWriteMethod(final String name) { 610 return writeMethods.get(name); 611 } 612 613 private void initialise() { 614 if(getBean() == null) { 615 return; 616 } 617 618 final Class<? extends Object> beanClass = getBean().getClass(); 619 try { 620 //BeanInfo beanInfo = Introspector.getBeanInfo( bean, null ); 621 final BeanInfo beanInfo = Introspector.getBeanInfo( beanClass ); 622 final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); 623 if ( propertyDescriptors != null ) { 624 for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) { 625 if ( propertyDescriptor != null ) { 626 final String name = propertyDescriptor.getName(); 627 final Method readMethod = propertyDescriptor.getReadMethod(); 628 final Method writeMethod = propertyDescriptor.getWriteMethod(); 629 final Class<? extends Object> aType = propertyDescriptor.getPropertyType(); 630 631 if ( readMethod != null ) { 632 readMethods.put( name, readMethod ); 633 } 634 if ( writeMethod != null ) { 635 writeMethods.put( name, writeMethod ); 636 } 637 types.put( name, aType ); 638 } 639 } 640 } 641 } 642 catch ( final IntrospectionException e ) { 643 logWarn( e ); 644 } 645 } 646 647 /** 648 * Convenience method for getting an iterator over the keys. 649 * <p> 650 * Write-only properties will not be returned in the iterator. 651 * 652 * @return an iterator over the keys 653 */ 654 public Iterator<String> keyIterator() { 655 return readMethods.keySet().iterator(); 656 } 657 658 // Implementation methods 659 //------------------------------------------------------------------------- 660 661 /** 662 * Get the keys for this BeanMap. 663 * <p> 664 * Write-only properties are <strong>not</strong> included in the returned set of 665 * property names, although it is possible to set their value and to get 666 * their type. 667 * 668 * @return BeanMap keys. The Set returned by this method is not 669 * modifiable. 670 */ 671 @SuppressWarnings({ "unchecked", "rawtypes" }) 672 // The set actually contains strings; however, because it cannot be 673 // modified there is no danger in selling it as Set<Object> 674 @Override 675 public Set<Object> keySet() { 676 return Collections.unmodifiableSet((Set) readMethods.keySet()); 677 } 678 679 /** 680 * Logs the given exception to <code>System.out</code>. Used to display 681 * warnings while accessing/mutating the bean. 682 * 683 * @param ex the exception to log 684 */ 685 protected void logInfo(final Exception ex) { 686 // Deliberately do not use LOG4J or Commons Logging to avoid dependencies 687 System.out.println( "INFO: Exception: " + ex ); 688 } 689 690 /** 691 * Logs the given exception to <code>System.err</code>. Used to display 692 * errors while accessing/mutating the bean. 693 * 694 * @param ex the exception to log 695 */ 696 protected void logWarn(final Exception ex) { 697 // Deliberately do not use LOG4J or Commons Logging to avoid dependencies 698 System.out.println( "WARN: Exception: " + ex ); 699 ex.printStackTrace(); 700 } 701 702 /** 703 * Sets the bean property with the given name to the given value. 704 * 705 * @param name the name of the property to set 706 * @param value the value to set that property to 707 * @return the previous value of that property 708 * @throws IllegalArgumentException if the given name is null; 709 * if the given name is not a {@link String}; if the bean doesn't 710 * define a property with that name; or if the bean property with 711 * that name is read-only 712 * @throws ClassCastException if an error occurs creating the method args 713 */ 714 @Override 715 public Object put(final Object name, final Object value) throws IllegalArgumentException, ClassCastException { 716 if ( bean != null ) { 717 final Object oldValue = get( name ); 718 final Method method = getWriteMethod( name ); 719 if ( method == null ) { 720 throw new IllegalArgumentException( "The bean of type: "+ 721 bean.getClass().getName() + " has no property called: " + name ); 722 } 723 try { 724 final Object[] arguments = createWriteMethodArguments( method, value ); 725 method.invoke( bean, arguments ); 726 727 final Object newValue = get( name ); 728 firePropertyChange( name, oldValue, newValue ); 729 } 730 catch ( final InvocationTargetException | IllegalAccessException e ) { 731 throw new IllegalArgumentException(e.getMessage(), e); 732 } 733 return oldValue; 734 } 735 return null; 736 } 737 738 /** 739 * Puts all of the writable properties from the given BeanMap into this 740 * BeanMap. Read-only and Write-only properties will be ignored. 741 * 742 * @param map the BeanMap whose properties to put 743 */ 744 public void putAllWriteable(final BeanMap map) { 745 for (final Object key : map.readMethods.keySet()) { 746 if (getWriteMethod(key) != null) { 747 put(key, map.get(key)); 748 } 749 } 750 } 751 752 // Implementation classes 753 //------------------------------------------------------------------------- 754 755 /** 756 * Reinitializes this bean. Called during {@link #setBean(Object)}. 757 * Does introspection to find properties. 758 */ 759 protected void reinitialise() { 760 readMethods.clear(); 761 writeMethods.clear(); 762 types.clear(); 763 initialise(); 764 } 765 766 /** 767 * Sets the bean to be operated on by this map. The given value may 768 * be null, in which case this map will be empty. 769 * 770 * @param newBean the new bean to operate on 771 */ 772 public void setBean( final Object newBean ) { 773 bean = newBean; 774 reinitialise(); 775 } 776 777 /** 778 * Returns the number of properties defined by the bean. 779 * 780 * @return the number of properties defined by the bean 781 */ 782 @Override 783 public int size() { 784 return readMethods.size(); 785 } 786 787 /** 788 * Renders a string representation of this object. 789 * @return a <code>String</code> representation of this object 790 */ 791 @Override 792 public String toString() { 793 return "BeanMap<" + String.valueOf(bean) + ">"; 794 } 795 796 /** 797 * Convenience method for getting an iterator over the values. 798 * 799 * @return an iterator over the values 800 */ 801 public Iterator<Object> valueIterator() { 802 final Iterator<?> iter = keyIterator(); 803 return new Iterator<Object>() { 804 @Override 805 public boolean hasNext() { 806 return iter.hasNext(); 807 } 808 @Override 809 public Object next() { 810 final Object key = iter.next(); 811 return get(key); 812 } 813 @Override 814 public void remove() { 815 throw new UnsupportedOperationException( "remove() not supported for BeanMap" ); 816 } 817 }; 818 } 819 820 /** 821 * Returns the values for the BeanMap. 822 * 823 * @return values for the BeanMap. The returned collection is not 824 * modifiable. 825 */ 826 @Override 827 public Collection<Object> values() { 828 final ArrayList<Object> answer = new ArrayList<>( readMethods.size() ); 829 for ( final Iterator<Object> iter = valueIterator(); iter.hasNext(); ) { 830 answer.add( iter.next() ); 831 } 832 return Collections.unmodifiableList(answer); 833 } 834}