From 7f27eb3ee202fe48cf67469603d72c541af9b5c5 Mon Sep 17 00:00:00 2001 From: Stian Soiland-Reyes Date: Wed, 21 Sep 2016 16:19:14 +0000 Subject: [PATCH 01/18] [maven-release-plugin] copy for tag BEANUTILS_1_9_3_RC3 git-svn-id: https://svn.apache.org/repos/asf/commons/proper/beanutils/tags/BEANUTILS_1_9_3_RC3@1761784 13f79535-47bb-0310-9956-ffa450edef68 From a83c1eae04c9c99360c3ba4e6a46fd89dc5407ef Mon Sep 17 00:00:00 2001 From: Stian Soiland-Reyes Date: Sun, 25 Sep 2016 14:56:44 +0000 Subject: [PATCH 02/18] Release BeanUtils 1.9.3 (RC3) https://lists.apache.org/thread.html/bf9f5cf14a51035a801b134711463872d0ab370372282751c203c0e7@%3Cdev.commons.apache.org%3E git-svn-id: https://svn.apache.org/repos/asf/commons/proper/beanutils/tags/BEANUTILS_1_9_3@1762214 13f79535-47bb-0310-9956-ffa450edef68 From 62e82ad92cf4818709d6044aaf257b73d42659a4 Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Wed, 5 Jun 2019 20:38:37 -0400 Subject: [PATCH 03/18] BEANUTILS-520: mitigation for CVE-2014-0114 --- pom.xml | 10 ++++ src/changes/changes.xml | 6 ++ .../commons/beanutils/PropertyUtilsBean.java | 1 + .../BeanIntrospectionDataTestCase.java | 1 + .../beanutils/bugs/Jira157TestCase.java | 5 ++ .../beanutils/bugs/Jira520TestCase.java | 55 +++++++++++++++++++ 6 files changed, 78 insertions(+) create mode 100644 src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java diff --git a/pom.xml b/pom.xml index 86130b4b6..2126fe969 100644 --- a/pom.xml +++ b/pom.xml @@ -195,6 +195,12 @@ +0 The Apache Software Foundation + + chtompki + Rob Tompkins + chtompki@apache.org + The Apache Software Foundation + @@ -298,6 +304,10 @@ Bernhard Seebass + + Melloware + + diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 56178979f..bf3ba3269 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -29,6 +29,12 @@ + + + BeanUtils mitigate CVE-2014-0114. + + + Update dependency from JUnit 3.8.1 to 4.12. diff --git a/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java b/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java index 5e76d97b6..36eb7f57b 100644 --- a/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java +++ b/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java @@ -188,6 +188,7 @@ public void setResolver(final Resolver resolver) { public final void resetBeanIntrospectors() { introspectors.clear(); introspectors.add(DefaultBeanIntrospector.INSTANCE); + introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS); } /** diff --git a/src/test/java/org/apache/commons/beanutils/BeanIntrospectionDataTestCase.java b/src/test/java/org/apache/commons/beanutils/BeanIntrospectionDataTestCase.java index e2085932b..95ad65e3d 100644 --- a/src/test/java/org/apache/commons/beanutils/BeanIntrospectionDataTestCase.java +++ b/src/test/java/org/apache/commons/beanutils/BeanIntrospectionDataTestCase.java @@ -42,6 +42,7 @@ public class BeanIntrospectionDataTestCase extends TestCase { */ private static PropertyDescriptor[] fetchDescriptors() { final PropertyUtilsBean pub = new PropertyUtilsBean(); + pub.removeBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS); pub.addBeanIntrospector(new FluentPropertyBeanIntrospector()); return pub.getPropertyDescriptors(BEAN_CLASS); } diff --git a/src/test/java/org/apache/commons/beanutils/bugs/Jira157TestCase.java b/src/test/java/org/apache/commons/beanutils/bugs/Jira157TestCase.java index 79d71aadd..869d630ec 100644 --- a/src/test/java/org/apache/commons/beanutils/bugs/Jira157TestCase.java +++ b/src/test/java/org/apache/commons/beanutils/bugs/Jira157TestCase.java @@ -24,6 +24,8 @@ import junit.framework.TestSuite; import org.apache.commons.beanutils.BeanUtils; +import org.apache.commons.beanutils.BeanUtilsBean; +import org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -74,6 +76,9 @@ public static Test suite() { @Override protected void setUp() throws Exception { super.setUp(); + BeanUtilsBean custom = new BeanUtilsBean(); + custom.getPropertyUtils().removeBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS); + BeanUtilsBean.setInstance(custom); } /** diff --git a/src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java b/src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java new file mode 100644 index 000000000..ab64bcd2a --- /dev/null +++ b/src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.beanutils.bugs; + +import org.apache.commons.beanutils.AlphaBean; +import org.apache.commons.beanutils.BeanUtilsBean; +import org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector; + +import junit.framework.TestCase; + +/** + * Fix CVE: https://nvd.nist.gov/vuln/detail/CVE-2014-0114 + * + * @see https://issues.apache.org/jira/browse/BEANUTILS-520 + */ +public class Jira520TestCase extends TestCase { + /** + * By default opt-in to security that does not allow access to "class". + */ + public void testSuppressClassPropertyByDefault() throws Exception { + final BeanUtilsBean bub = new BeanUtilsBean(); + final AlphaBean bean = new AlphaBean(); + try { + bub.getProperty(bean, "class"); + fail("Could access class property!"); + } catch (final NoSuchMethodException ex) { + // ok + } + } + + /** + * Allow opt-out to make your app less secure but allow access to "class". + */ + public void testAllowAccessToClassProperty() throws Exception { + final BeanUtilsBean bub = new BeanUtilsBean(); + bub.getPropertyUtils().removeBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS); + final AlphaBean bean = new AlphaBean(); + String result = bub.getProperty(bean, "class"); + assertEquals("Class property should have been accessed", "class org.apache.commons.beanutils.AlphaBean", result); + } +} \ No newline at end of file From 0166c87931b5f28e907f4756adb796c038a7cee1 Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Wed, 5 Jun 2019 20:39:42 -0400 Subject: [PATCH 04/18] (chore) upversion 1.9.3 -> 1.9.4-SNAPSHOT, parent: 41 -> 47 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2126fe969..c34d7258e 100644 --- a/pom.xml +++ b/pom.xml @@ -19,12 +19,12 @@ org.apache.commons commons-parent - 41 + 47 4.0.0 commons-beanutils commons-beanutils - 1.9.3 + 1.9.4-SNAPSHOT Apache Commons BeanUtils 2000 From b35966cf339e45f7c336214adc6f9d490a2ba7f6 Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Wed, 5 Jun 2019 20:39:42 -0400 Subject: [PATCH 05/18] (chore) upversion 1.9.3 -> 1.9.4-SNAPSHOT, parent: 41 -> 48 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2126fe969..162898716 100644 --- a/pom.xml +++ b/pom.xml @@ -19,12 +19,12 @@ org.apache.commons commons-parent - 41 + 48 4.0.0 commons-beanutils commons-beanutils - 1.9.3 + 1.9.4-SNAPSHOT Apache Commons BeanUtils 2000 From 896ae946049ecca5e2d451570342197ae77eac21 Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Tue, 11 Jun 2019 21:19:50 -0400 Subject: [PATCH 06/18] Preparing for release 1.9.4 --- RELEASE-NOTES.txt | 605 +++++++++++++++++++++------------------- pom.xml | 74 ++++- src/changes/changes.xml | 11 +- 3 files changed, 400 insertions(+), 290 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 8c36d976e..a42d9dc0e 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,287 +1,318 @@ - Apache Commons BeanUtils 1.9.3 - RELEASE NOTES - -The Apache Commons team is pleased to announce the release of Apache -Commons BeanUtils 1.9.3 - -Apache Commons BeanUtils provides an easy-to-use but flexible wrapper around -reflection and introspection. - -This is a bug fix release, which also improves the tests for building on Java -8. - -Note that Java 8 and later no longer support indexed bean properties on -java.util.List, only on arrays like String[]. (BEANUTILS-492). This affects -PropertyUtils.getPropertyType() and PropertyUtils.getPropertyDescriptor(); -their javadoc have therefore been updated to reflect this change in the JDK. - - -Changes in this version include: - -Fixed Bugs: - -* BEANUTILS-477: Changed log level in FluentPropertyBeanIntrospector -* BEANUTILS-492: Fixed exception when setting indexed properties on DynaBeans. - Thanks to Bernhard Seebass. -* BEANUTILS-470: Precision lost when converting BigDecimal Thanks to Tommy - Tynjä. -* BEANUTILS-465: Indexed List Setters fixed. Thanks to Daniel Atallah. - -Changes: -* BEANUTILS-433: Update dependency from JUnit 3.8.1 to 4.12. - Thanks to Benedikt Ritter, Gary Gregory. -* BEANUTILS-469: Update commons-logging from 1.1.1 to 1.2. - Thanks to Gary Gregory. -* BEANUTILS-474: FluentPropertyBeanIntrospector does not use the same naming - algorithm as DefaultBeanIntrospector. Thanks to Michael Grove. -* BEANUTILS-490: Update Java requirement from Java 5 to 6. - Thanks to Gary Gregory. -* BEANUTILS-482: Update commons-collections from 3.2.1 to 3.2.2 - (CVE-2015-4852). Thanks to Gary Gregory. -* BEANUTILS-490: Update java requirement to Java 6. Thanks to Gary Gregory. -* BEANUTILS-492: IndexedPropertyDescriptor tests now pass on Java 8. - Thanks to Stian Soiland-Reyes. -* BEANUTILS-495: DateConverterTestBase fails on M/d/yy in Java 9. - Thanks to Stian Soiland-Reyes. -* BEANUTILS-496: testGetDescriptorInvalidBoolean fails on Java 9. - Thanks to Stian Soiland-Reyes. - - -Historical list of changes: http://commons.apache.org/proper/commons-beanutils/changes-report.html - -For complete information on Apache Commons BeanUtils, including instructions on -how to submit bug reports, patches, or suggestions for improvement, see the -Apache Apache Commons BeanUtils website: - -https://commons.apache.org/proper/commons-beanutils/ - ------------------------------------------------------------------------------ - - Commons BeanUtils Package - Version 1.9.2 - Release Notes - -INTRODUCTION: -============ - -This document contains the release notes for this version of the Commons -BeanUtils package, and highlights changes since the previous version. - -For more information on Commons BeanUtils, see -o http://commons.apache.org/beanutils/ - -Release 1.9.2 mainly addresses a potential security issue when accessing -properties in an uncontrolled way. In a nutshell, if an application that uses -Commons BeanUtils passes property paths from an external source directly to -the getProperty() method of BeanUtilsBean, an attacker can access the class -loader via the class property available on all Java objects. - -In version 1.9.2 now a special BeanIntrospector class was added which allows -suppressing this property. Note that this BeanIntrospector is NOT enabled by -default! Commons BeanUtils is a low-level library, and on this layer it cannot -be decided whether access to a certain property is legal or not. Therefore, -an application has to activate this suppressing BeanIntrospector explicitly. -This can be done with the following lines of code: - -BeanUtilsBean bub = new BeanUtilsBean(); -bub.getPropertyUtils().addBeanIntrospector( - SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS); - -Now all access to properties has to be done via the specially configured -BeanUtilsBean instance. More information about this issue can be found at -https://issues.apache.org/jira/browse/BEANUTILS-463 or in section 2.5 of the -user's guide. - -BUGFIXES in version 1.9.2 -========================= -* [BEANUTILS-458] - BaseLocaleConverter.checkConversionResult() no longer throws a - ConversionException if the result of a conversion is null. - -New features in version 1.9.2 -============================= -* [BEANUTILS-463] - Added new SuppressPropertiesBeanIntrospector class to deal with a potential - class loader vulnerability. - ------------------------------------------------------------------------------ - - Release Notes for version 1.9.1 - -Release 1.9.1 is a bug fix release which addresses a problem with the new -feature of custom introspection introduced with release 1.9.0. It is fully -binary compatible with the previous release. The minimum required Java version -is 1.5. - -BUGFIXES in version 1.9.1 -========================= -* [BEANUTILS-456] - For PropertyDescriptors obtained via custom introspection now additional - information is stored to prevent that write methods are lost during - garbage collection. - ------------------------------------------------------------------------------ - - Release Notes for version 1.9.0 - -Release 1.9.0 contains some bug fixes and improvements that have accumulated -after the 1.8.3 release. The most obvious change is that the new version now -requires JDK 1.5 or higher, and that language features introduced with Java 5 -(mainly generics) are used. A new feature has been introduced, too: the support -for customizing bean introspection. - -Compatibility with 1.8.3 -======================== -Adding generics to the BeanUtils API has been done in a backwards compatible -way. This means that after type erasure the resulting classes look the same as -in the previous version. A drawback of this approach is that sometimes it is -not possible to use the logically correct type parameters because then -backwards compatibility would be broken. One example is the BeanMap class: The -class is now a Map while its keys actually are strings. -However, implementing Map would change the signatures of some -methods in an incompatible way. More details about limitations of the -generification can be found at -https://issues.apache.org/jira/browse/BEANUTILS-452 - -One exception from the compatibility rule is the ResultSetIterator class which -now implements the Iterator interface. This causes a change in the -return value of its next() method. ResultSetIterator is used internally as the -iterator implementation within ResultSetDynaClass (it is probably a mistake that -it is public). So chances are minimal that this change affects existing code. - -Another change which may affect compatibility is [BEANUTILS-379] (details can -be found at https://issues.apache.org/jira/browse/BEANUTILS-379). Older -versions of BeanUtils contained some classes that were copied from Commons -Collections. These classes have now been removed, and a dependency to Commons -Collections has been added; the collections jar now has to be contained in the -classpath, too. - -Except for the change on ResultSetIterator and the additional dependency to -Commons Collections, Commons BeanUtils 1.9.0 is fully binary compatible with -the previous version 1.8.3. - -Changes on Converters -===================== -The convert() method in the Converter interface now uses a type parameter in -the following way: - - T convert(Class type, Object value); - -This makes it possible to access the converter's result in a type-safe way. -Applying generics in this way revealed some inconsistencies in the Converter -implementations. There were situations in which converters could return a -result object of a different type as was requested. This was not a problem -before because the result type was just Object. Now the compiler complains if -a converter's result is not compatible with the desired target type. - -Because of that Converter implementations have been made more strict. A -converter now checks the passed in target type, and if it cannot handle it, -throws a ConversionException. This prevents unexpected results and makes -converters more reliable (it could be considered a bug that a converter returns -a result object of a different data type as the passed in target type). In a -typical scenario, when converters are accessed via ConvertUtils, this change -should not cause any problems because the converters are only called for the -data types they have been registered for. But if converters are used directly, -they might now throw ConversionExceptions when they did not in a previous -version. - -BUGFIXES in version 1.9.0 -========================= -* [BEANUTILS-454] - BeanUtilsBean.copyProperties() no longer throws a ConversionException for - null properties of certain data types. This fixes a regression introduced in - version 1.8.0. The issue is related to [BEANUTILS-387]. -* [BEANUTILS-411] - BeanUtilsBean.setProperty throws IllegalArgumentException if getter of nested - property returns null. -* [BEANUTILS-408] - MethodUtils.invokeMethod() throws NullPointerException when args==null. -* [BEANUTILS-426] - ConstructorUtils.invokeConstructor(Class klass, Object arg) throws - NullPointerException when arg==null. -* [BEANUTILS-380] - BeanMap methods should initialize the root cause of exceptions that are - thrown when running on JDK 1.4+. -* [BEANUTILS-379] - Remove copied Collection classes. -* [BEANUTILS-378] - BeanMap does not work in osgi (fixed by BEANUTILS-378). -* [BEANUTILS-381] - MethodUtils getMatchingAccessibleMethod() does not correctly handle - inheritance and method overloading. - -New features in version 1.9.0 -============================= -* [BEANUTILS-425] - Support customization of introspection mechanism. -* [BEANUTILS-428] - Provide a BeanIntrospector implementation which supports properties in a - fluent API. -* [BEANUTILS-455] - WrapDynaBeans can now be configured to use a specific instance of - PropertyUtilsBean for introspection or property access. - -Other changes in version 1.9.0 -============================== -* [BEANUTILS-452] - Add generics. -* [BEANUTILS-449] - LocaleConverters do not take the target type into account. -* [BEANUTILS-448] - LocaleConverters do not check their default value. -* [BEANUTILS-447] - LazyDynaList.toArray() is not conform to the contract defined by the - Collection interface. -* [BEANUTILS-446] - Some of the converters ignore the passed in target type. -* [BEANUTILS-445] - Converters can return an invalid result object if a default value is set. -* [BEANUTILS-441] - Replace UnmodifiableSet.decorate with Collections.unModifiableSet. -* [BEANUTILS-436] - Replace package.html with package-info.java. -* [BEANUTILS-438] - Add @Deprecated and @Override Annotations. -* [BEANUTILS-437] - Replace Date and Revision SVN keywords with Id. -* [BEANUTILS-431] - Remove @author tags and move missing authors to pom.xml. -* [BEANUTILS-432] - Switch to Java 1.5. -* [BEANUTILS-429] - Delete trailing white spaces and white spaces on empty lines from all files. -* [BEANUTILS-427] - Configure Checkstyle to check for trailing white spaces and white spaces on - empty lines. - ------------------------------------------------------------------------------ - - Release Notes for version 1.8.3 - -Compatibility with 1.8.2 -======================== -BeanUtils 1.8.3 is binary compatible release with Beanutils 1.8.2, containing only bug fixes. - -BeanUtils 1.8.3 requires a minimum of JDK 1.3. - -Memory Leak -=========== -A memory leak was found in BeanUtils 1.7.0 (see BEANUTILS-291) which was fixed -in BeanUtils 1.8.0 for JDK 1.5+. - -Testing of BeanUtils 1.8.1 revealed that the leak still appears to exist -in IBM's JDK 1.6 implementation. - - -see http://issues.apache.org/jira/browse/BEANUTILS-291 - http://issues.apache.org/jira/browse/BEANUTILS-366 - - -BUGS FIXED: -=========== - -The following is a list of the bugs fixed in this release, with their Jira issue number: - - * [BEANUTILS-373] - MethodUtils is not thread safe because WeakFastHashMap which uses WeakHashMap is not thread-safe - * [BEANUTILS-371] - Add constructors which have useColumnLabel parameter to ResultSetDynaClass and RowSetDynaClass - + Apache Commons BeanUtils 1.9.4 + RELEASE NOTES + +The Apache Commons BeanUtils team is pleased to announce the release of Apache Commons BeanUtils 1.9.4 + +Apache Commons BeanUtils provides an easy-to-use but flexible wrapper around reflection and introspection. + +The primary reason for this release is a bugfix for CVE-2014-0114. More specifically, our goal with +BEANUTILS-520 is to set the default behaviour of the BeanUtilsBean to not allow class level access. The goal +in doing this now is to bring 1.9.X into alignment with the same behaviour of the 2.X version line in +regards to security. + +If one would like to opt out of the default behaviour, one could follow the example set out in the +test class available in src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java. + +Changes in this version include: + +Fixed Bugs: +o BEANUTILS-520: BeanUtils mitigate CVE-2014-0114. Thanks to Melloware. + + +Historical list of changes: https://commons.apache.org/proper/commons-beanutils/changes-report.html + +For complete information on Apache Commons BeanUtils, including instructions on how to submit bug reports, +patches, or suggestions for improvement, see the Apache Apache Commons BeanUtils website: + +https://commons.apache.org/proper/commons-beanutils/ + +----------------------------------------------------------------------------- + + Apache Commons BeanUtils 1.9.3 + RELEASE NOTES + +The Apache Commons team is pleased to announce the release of Apache +Commons BeanUtils 1.9.3 + +Apache Commons BeanUtils provides an easy-to-use but flexible wrapper around +reflection and introspection. + +This is a bug fix release, which also improves the tests for building on Java +8. + +Note that Java 8 and later no longer support indexed bean properties on +java.util.List, only on arrays like String[]. (BEANUTILS-492). This affects +PropertyUtils.getPropertyType() and PropertyUtils.getPropertyDescriptor(); +their javadoc have therefore been updated to reflect this change in the JDK. + + +Changes in this version include: + +Fixed Bugs: + +* BEANUTILS-477: Changed log level in FluentPropertyBeanIntrospector +* BEANUTILS-492: Fixed exception when setting indexed properties on DynaBeans. + Thanks to Bernhard Seebass. +* BEANUTILS-470: Precision lost when converting BigDecimal Thanks to Tommy + Tynjä. +* BEANUTILS-465: Indexed List Setters fixed. Thanks to Daniel Atallah. + +Changes: +* BEANUTILS-433: Update dependency from JUnit 3.8.1 to 4.12. + Thanks to Benedikt Ritter, Gary Gregory. +* BEANUTILS-469: Update commons-logging from 1.1.1 to 1.2. + Thanks to Gary Gregory. +* BEANUTILS-474: FluentPropertyBeanIntrospector does not use the same naming + algorithm as DefaultBeanIntrospector. Thanks to Michael Grove. +* BEANUTILS-490: Update Java requirement from Java 5 to 6. + Thanks to Gary Gregory. +* BEANUTILS-482: Update commons-collections from 3.2.1 to 3.2.2 + (CVE-2015-4852). Thanks to Gary Gregory. +* BEANUTILS-490: Update java requirement to Java 6. Thanks to Gary Gregory. +* BEANUTILS-492: IndexedPropertyDescriptor tests now pass on Java 8. + Thanks to Stian Soiland-Reyes. +* BEANUTILS-495: DateConverterTestBase fails on M/d/yy in Java 9. + Thanks to Stian Soiland-Reyes. +* BEANUTILS-496: testGetDescriptorInvalidBoolean fails on Java 9. + Thanks to Stian Soiland-Reyes. + + +Historical list of changes: http://commons.apache.org/proper/commons-beanutils/changes-report.html + +For complete information on Apache Commons BeanUtils, including instructions on +how to submit bug reports, patches, or suggestions for improvement, see the +Apache Apache Commons BeanUtils website: + +https://commons.apache.org/proper/commons-beanutils/ + +----------------------------------------------------------------------------- + + Commons BeanUtils Package + Version 1.9.2 + Release Notes + +INTRODUCTION: +============ + +This document contains the release notes for this version of the Commons +BeanUtils package, and highlights changes since the previous version. + +For more information on Commons BeanUtils, see +o http://commons.apache.org/beanutils/ + +Release 1.9.2 mainly addresses a potential security issue when accessing +properties in an uncontrolled way. In a nutshell, if an application that uses +Commons BeanUtils passes property paths from an external source directly to +the getProperty() method of BeanUtilsBean, an attacker can access the class +loader via the class property available on all Java objects. + +In version 1.9.2 now a special BeanIntrospector class was added which allows +suppressing this property. Note that this BeanIntrospector is NOT enabled by +default! Commons BeanUtils is a low-level library, and on this layer it cannot +be decided whether access to a certain property is legal or not. Therefore, +an application has to activate this suppressing BeanIntrospector explicitly. +This can be done with the following lines of code: + +BeanUtilsBean bub = new BeanUtilsBean(); +bub.getPropertyUtils().addBeanIntrospector( + SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS); + +Now all access to properties has to be done via the specially configured +BeanUtilsBean instance. More information about this issue can be found at +https://issues.apache.org/jira/browse/BEANUTILS-463 or in section 2.5 of the +user's guide. + +BUGFIXES in version 1.9.2 +========================= +* [BEANUTILS-458] + BaseLocaleConverter.checkConversionResult() no longer throws a + ConversionException if the result of a conversion is null. + +New features in version 1.9.2 +============================= +* [BEANUTILS-463] + Added new SuppressPropertiesBeanIntrospector class to deal with a potential + class loader vulnerability. + +----------------------------------------------------------------------------- + + Release Notes for version 1.9.1 + +Release 1.9.1 is a bug fix release which addresses a problem with the new +feature of custom introspection introduced with release 1.9.0. It is fully +binary compatible with the previous release. The minimum required Java version +is 1.5. + +BUGFIXES in version 1.9.1 +========================= +* [BEANUTILS-456] + For PropertyDescriptors obtained via custom introspection now additional + information is stored to prevent that write methods are lost during + garbage collection. + +----------------------------------------------------------------------------- + + Release Notes for version 1.9.0 + +Release 1.9.0 contains some bug fixes and improvements that have accumulated +after the 1.8.3 release. The most obvious change is that the new version now +requires JDK 1.5 or higher, and that language features introduced with Java 5 +(mainly generics) are used. A new feature has been introduced, too: the support +for customizing bean introspection. + +Compatibility with 1.8.3 +======================== +Adding generics to the BeanUtils API has been done in a backwards compatible +way. This means that after type erasure the resulting classes look the same as +in the previous version. A drawback of this approach is that sometimes it is +not possible to use the logically correct type parameters because then +backwards compatibility would be broken. One example is the BeanMap class: The +class is now a Map while its keys actually are strings. +However, implementing Map would change the signatures of some +methods in an incompatible way. More details about limitations of the +generification can be found at +https://issues.apache.org/jira/browse/BEANUTILS-452 + +One exception from the compatibility rule is the ResultSetIterator class which +now implements the Iterator interface. This causes a change in the +return value of its next() method. ResultSetIterator is used internally as the +iterator implementation within ResultSetDynaClass (it is probably a mistake that +it is public). So chances are minimal that this change affects existing code. + +Another change which may affect compatibility is [BEANUTILS-379] (details can +be found at https://issues.apache.org/jira/browse/BEANUTILS-379). Older +versions of BeanUtils contained some classes that were copied from Commons +Collections. These classes have now been removed, and a dependency to Commons +Collections has been added; the collections jar now has to be contained in the +classpath, too. + +Except for the change on ResultSetIterator and the additional dependency to +Commons Collections, Commons BeanUtils 1.9.0 is fully binary compatible with +the previous version 1.8.3. + +Changes on Converters +===================== +The convert() method in the Converter interface now uses a type parameter in +the following way: + + T convert(Class type, Object value); + +This makes it possible to access the converter's result in a type-safe way. +Applying generics in this way revealed some inconsistencies in the Converter +implementations. There were situations in which converters could return a +result object of a different type as was requested. This was not a problem +before because the result type was just Object. Now the compiler complains if +a converter's result is not compatible with the desired target type. + +Because of that Converter implementations have been made more strict. A +converter now checks the passed in target type, and if it cannot handle it, +throws a ConversionException. This prevents unexpected results and makes +converters more reliable (it could be considered a bug that a converter returns +a result object of a different data type as the passed in target type). In a +typical scenario, when converters are accessed via ConvertUtils, this change +should not cause any problems because the converters are only called for the +data types they have been registered for. But if converters are used directly, +they might now throw ConversionExceptions when they did not in a previous +version. + +BUGFIXES in version 1.9.0 +========================= +* [BEANUTILS-454] + BeanUtilsBean.copyProperties() no longer throws a ConversionException for + null properties of certain data types. This fixes a regression introduced in + version 1.8.0. The issue is related to [BEANUTILS-387]. +* [BEANUTILS-411] + BeanUtilsBean.setProperty throws IllegalArgumentException if getter of nested + property returns null. +* [BEANUTILS-408] + MethodUtils.invokeMethod() throws NullPointerException when args==null. +* [BEANUTILS-426] + ConstructorUtils.invokeConstructor(Class klass, Object arg) throws + NullPointerException when arg==null. +* [BEANUTILS-380] + BeanMap methods should initialize the root cause of exceptions that are + thrown when running on JDK 1.4+. +* [BEANUTILS-379] + Remove copied Collection classes. +* [BEANUTILS-378] + BeanMap does not work in osgi (fixed by BEANUTILS-378). +* [BEANUTILS-381] + MethodUtils getMatchingAccessibleMethod() does not correctly handle + inheritance and method overloading. + +New features in version 1.9.0 +============================= +* [BEANUTILS-425] + Support customization of introspection mechanism. +* [BEANUTILS-428] + Provide a BeanIntrospector implementation which supports properties in a + fluent API. +* [BEANUTILS-455] + WrapDynaBeans can now be configured to use a specific instance of + PropertyUtilsBean for introspection or property access. + +Other changes in version 1.9.0 +============================== +* [BEANUTILS-452] + Add generics. +* [BEANUTILS-449] + LocaleConverters do not take the target type into account. +* [BEANUTILS-448] + LocaleConverters do not check their default value. +* [BEANUTILS-447] + LazyDynaList.toArray() is not conform to the contract defined by the + Collection interface. +* [BEANUTILS-446] + Some of the converters ignore the passed in target type. +* [BEANUTILS-445] + Converters can return an invalid result object if a default value is set. +* [BEANUTILS-441] + Replace UnmodifiableSet.decorate with Collections.unModifiableSet. +* [BEANUTILS-436] + Replace package.html with package-info.java. +* [BEANUTILS-438] + Add @Deprecated and @Override Annotations. +* [BEANUTILS-437] + Replace Date and Revision SVN keywords with Id. +* [BEANUTILS-431] + Remove @author tags and move missing authors to pom.xml. +* [BEANUTILS-432] + Switch to Java 1.5. +* [BEANUTILS-429] + Delete trailing white spaces and white spaces on empty lines from all files. +* [BEANUTILS-427] + Configure Checkstyle to check for trailing white spaces and white spaces on + empty lines. + +----------------------------------------------------------------------------- + + Release Notes for version 1.8.3 + +Compatibility with 1.8.2 +======================== +BeanUtils 1.8.3 is binary compatible release with Beanutils 1.8.2, containing only bug fixes. + +BeanUtils 1.8.3 requires a minimum of JDK 1.3. + +Memory Leak +=========== +A memory leak was found in BeanUtils 1.7.0 (see BEANUTILS-291) which was fixed +in BeanUtils 1.8.0 for JDK 1.5+. + +Testing of BeanUtils 1.8.1 revealed that the leak still appears to exist +in IBM's JDK 1.6 implementation. + + +see http://issues.apache.org/jira/browse/BEANUTILS-291 + http://issues.apache.org/jira/browse/BEANUTILS-366 + + +BUGS FIXED: +=========== + +The following is a list of the bugs fixed in this release, with their Jira issue number: + + * [BEANUTILS-373] - MethodUtils is not thread safe because WeakFastHashMap which uses WeakHashMap is not thread-safe + * [BEANUTILS-371] - Add constructors which have useColumnLabel parameter to ResultSetDynaClass and RowSetDynaClass + + diff --git a/pom.xml b/pom.xml index 162898716..8314455c7 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 4.0.0 commons-beanutils commons-beanutils - 1.9.4-SNAPSHOT + 1.9.4 Apache Commons BeanUtils 2000 @@ -35,12 +35,38 @@ 1.6 1.6 beanutils - 1.9.3 + 1.9.4 BEANUTILS 12310460 -Xmx50M + + false + + https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-beanutils + site-content + + 3.0.0 + 8.21 + + 3.1.10 + + 0.8.2 + + + false + + 0.13.0 + false + + + 1.9.3 + RC1 + true + scm:svn:https://dist.apache.org/repos/dist/dev/commons/${commons.componentid} + Rob Tompkins + B6E73D84EA4FCC47166087253FAAD2CD5ECBB314 @@ -442,4 +468,48 @@ + + + + setup-checkout + + + site-content + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + prepare-checkout + + run + + pre-site + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/changes/changes.xml b/src/changes/changes.xml index bf3ba3269..0d77b8bc7 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -29,7 +29,16 @@ - + BeanUtils mitigate CVE-2014-0114. From 78624c6d28f2a08f622cdc3a4f8fc125fffb5ded Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Sat, 15 Jun 2019 11:28:14 -0400 Subject: [PATCH 07/18] (site) download page 1.9.3 -> 1.9.4 --- src/site/xdoc/download_beanutils.xml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/site/xdoc/download_beanutils.xml b/src/site/xdoc/download_beanutils.xml index af4a1a967..48f45f6a3 100644 --- a/src/site/xdoc/download_beanutils.xml +++ b/src/site/xdoc/download_beanutils.xml @@ -102,7 +102,7 @@ limitations under the License. It is essential that you verify the integrity of downloaded files, preferably using the PGP signature (*.asc files); - failing that using the MD5 hash (*.md5 checksum files). + failing that using the SHA512 hash (*.sha512 checksum files).

The KEYS @@ -111,32 +111,32 @@ limitations under the License.

-
+
- - - + + + - - - + + +
commons-beanutils-1.9.3-bin.tar.gzmd5pgpcommons-beanutils-1.9.4-bin.tar.gzsha512pgp
commons-beanutils-1.9.3-bin.zipmd5pgpcommons-beanutils-1.9.4-bin.zipsha512pgp
- - - + + + - - - + + +
commons-beanutils-1.9.3-src.tar.gzmd5pgpcommons-beanutils-1.9.4-src.tar.gzsha512pgp
commons-beanutils-1.9.3-src.zipmd5pgpcommons-beanutils-1.9.4-src.zipsha512pgp
From e476c8674eceda50ec8945ad90d93a517ec52a39 Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Sat, 15 Jun 2019 12:37:13 -0400 Subject: [PATCH 08/18] (fix) commons-parent needs to be 47, for java 7 to work --- pom.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8314455c7..cdc3fc3cc 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ org.apache.commons commons-parent - 48 + 47 4.0.0 commons-beanutils @@ -50,6 +50,8 @@ 3.0.0 8.21 + 3.8 + 3.1.10 0.8.2 From 3f7f276dd720b7b95ed59387d9ac4561b2acc20b Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Fri, 19 Jul 2019 18:02:39 -0400 Subject: [PATCH 09/18] (docs) release and CVE documentation --- RELEASE-NOTES.txt | 2 +- src/changes/changes.xml | 2 +- src/site/site.xml | 5 +++++ src/site/xdoc/index.xml | 40 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index a42d9dc0e..5c36257cd 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -16,7 +16,7 @@ test class available in src/test/java/org/apache/commons/beanutils/bugs/Jira520T Changes in this version include: Fixed Bugs: -o BEANUTILS-520: BeanUtils mitigate CVE-2014-0114. Thanks to Melloware. +o BEANUTILS-520: BeanUtils mitigation of CVE-2014-0114. (CVE-2019-10086 for commons-beanutils). Thanks to Melloware. Historical list of changes: https://commons.apache.org/proper/commons-beanutils/changes-report.html diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0d77b8bc7..fc21a894e 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -40,7 +40,7 @@ If one would like to opt out of the default behaviour, one could follow the example set out in the test class available in src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java."> - BeanUtils mitigate CVE-2014-0114. + BeanUtils mitigation of CVE-2014-0114. (CVE-2019-10086 for commons-beanutils). diff --git a/src/site/site.xml b/src/site/site.xml index b8c8cd6b2..8e993de71 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -41,6 +41,11 @@ + + + + + diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index a1dfa5896..791ba3b27 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -90,6 +90,46 @@ Bean Collections has an additional dependency on
+

+ The latest BeanUtils release is available to download + here.

+ 1.9.4

+ CVE-2019-10086. Apache Commons Beanutils does not suppresses + the class property in bean introspection by default.

+ Severity. Medium

+ Vendor. The Apache Software Foundation

+ Versions Affected. All versions commons-beanutils-1.9.3 and before.

+ Description. In version 1.9.2, a special BeanIntrospector class was added which allows suppressing the ability for + an attacker to access the classloader via the class property available on all Java objects. We, however were not + using this by default characteristic of the PropertyUtilsBean.

+ Mitigation. Upgrade to commons-beanutils-1.9.4

+ Credit. This was discovered by Melloware (https://melloware.com/).

+ Example. + /** +* Example usage after 1.9.4 +*/ +public void testSuppressClassPropertyByDefault() throws Exception { + final BeanUtilsBean bub = new BeanUtilsBean(); + final AlphaBean bean = new AlphaBean(); + try { + bub.getProperty(bean, "class"); + fail("Could access class property!"); + } catch (final NoSuchMethodException ex) { + // ok + } +} + +/** +* Example usage to restore 1.9.3 behaviour +*/ +public void testAllowAccessToClassProperty() throws Exception { + final BeanUtilsBean bub = new BeanUtilsBean(); + bub.getPropertyUtils().removeBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS); + final AlphaBean bean = new AlphaBean(); + String result = bub.getProperty(bean, "class"); + assertEquals("Class property should have been accessed", "class org.apache.commons.beanutils2.AlphaBean", result); +} +

BeanUtils 1.9.x releases are binary compatible (with a minor exception described in the release notes) with version 1.8.3 and require a minimum of From e55b3db346c3ae3c78a4622bb1141db381049911 Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Mon, 22 Jul 2019 23:00:52 -0400 Subject: [PATCH 10/18] (update) 1.9.4 site updates --- src/site/site.xml | 8 ++++---- src/site/xdoc/index.xml | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/site/site.xml b/src/site/site.xml index 8e993de71..b708074f0 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -36,10 +36,10 @@

- - - - + + + + diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index 791ba3b27..bc7508a64 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -94,6 +94,10 @@ Bean Collections has an additional dependency on The latest BeanUtils release is available to download here.

1.9.4

+ CVE-2019-10086. Apache Commons Beanutils does not suppresses the class property in bean introspection by default.

Severity. Medium

From 9d4dce7e9c4596220cc26338e4f0b06993c1dfc2 Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Sun, 28 Jul 2019 18:11:21 -0400 Subject: [PATCH 11/18] (udpate) NOTICE.txt 2016 -> 2019 --- NOTICE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NOTICE.txt b/NOTICE.txt index 2440504f5..e1529d40c 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Apache Commons BeanUtils -Copyright 2000-2016 The Apache Software Foundation +Copyright 2000-2019 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). From 32ceb2c92512d44f97638805e2f3fd9d70dfcfc6 Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Sun, 28 Jul 2019 18:12:17 -0400 Subject: [PATCH 12/18] (update) RC1 -> RC2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cdc3fc3cc..1a4c70d26 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ 1.9.3 - RC1 + RC2 true scm:svn:https://dist.apache.org/repos/dist/dev/commons/${commons.componentid} Rob Tompkins From dd8e3b5935bc32531dfe8821a8561209b3b8d2b3 Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Tue, 13 Aug 2019 20:38:51 -0400 Subject: [PATCH 13/18] (docs) rework from Sebb on CVE description --- src/site/xdoc/index.xml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index bc7508a64..75083ed96 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -103,14 +103,20 @@ Bean Collections has an additional dependency on Severity. Medium

Vendor. The Apache Software Foundation

Versions Affected. All versions commons-beanutils-1.9.3 and before.

- Description. In version 1.9.2, a special BeanIntrospector class was added which allows suppressing the ability for - an attacker to access the classloader via the class property available on all Java objects. We, however were not - using this by default characteristic of the PropertyUtilsBean.

+ Description. A special BeanIntrospector class was added in version 1.9.2. + This can be used to stop attackers from using the class property of + Java objects to get access to the classloader. + However this protection was not enabled by default. + PropertyUtilsBean (and consequently BeanUtilsBean) now disallows class + level property access by default, thus protecting against + CVE-2014-0114.

Mitigation. Upgrade to commons-beanutils-1.9.4

Credit. This was discovered by Melloware (https://melloware.com/).

Example. /** -* Example usage after 1.9.4 +* Example displaying the new default behaviour such that +* it is not possible to access class level properties utilizing the +* BeanUtilsBean, which in turn utilizes the PropertyUtilsBean. */ public void testSuppressClassPropertyByDefault() throws Exception { final BeanUtilsBean bub = new BeanUtilsBean(); @@ -124,7 +130,9 @@ public void testSuppressClassPropertyByDefault() throws Exception { } /** -* Example usage to restore 1.9.3 behaviour +* Example showing how by which one would use to revert to the +* behaviour prior to the 1.9.4 release where class level properties were accessible by +* the BeanUtilsBean and the PropertyUtilsBean. */ public void testAllowAccessToClassProperty() throws Exception { final BeanUtilsBean bub = new BeanUtilsBean(); From c9bbfb7b16e89ac9cf68998db7ddd796f4f81932 Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Thu, 15 Aug 2019 08:57:08 -0400 Subject: [PATCH 14/18] (docs) updates to site, scm location, sha256 hash --- pom.xml | 6 +++--- src/site/xdoc/download_beanutils.xml | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 1a4c70d26..8f1ebc064 100644 --- a/pom.xml +++ b/pom.xml @@ -79,9 +79,9 @@ - scm:svn:http://svn.apache.org/repos/asf/commons/proper/beanutils/tags/BEANUTILS_1_9_3_RC3 - scm:svn:https://svn.apache.org/repos/asf/commons/proper/beanutils/tags/BEANUTILS_1_9_3_RC3 - http://svn.apache.org/viewvc/commons/proper/beanutils/tags/BEANUTILS_1_9_3_RC3 + scm:git:https://gitbox.apache.org/repos/asf?p=commons-beanutils.git + scm:git:https://gitbox.apache.org/repos/asf?p=commons-beanutils.git + https://gitbox.apache.org/repos/asf?p=commons-beanutils.git diff --git a/src/site/xdoc/download_beanutils.xml b/src/site/xdoc/download_beanutils.xml index 48f45f6a3..397bea322 100644 --- a/src/site/xdoc/download_beanutils.xml +++ b/src/site/xdoc/download_beanutils.xml @@ -102,7 +102,7 @@ limitations under the License. It is essential that you verify the integrity of downloaded files, preferably using the PGP signature (*.asc files); - failing that using the SHA512 hash (*.sha512 checksum files). + failing that using the SHA256 hash (*.sha256 checksum files).

The KEYS @@ -116,12 +116,12 @@ limitations under the License. - + - +
commons-beanutils-1.9.4-bin.tar.gzsha512sha256 pgp
commons-beanutils-1.9.4-bin.zipsha512sha256 pgp
@@ -130,12 +130,12 @@ limitations under the License. - + - +
commons-beanutils-1.9.4-src.tar.gzsha512sha256 pgp
commons-beanutils-1.9.4-src.zipsha512sha256 pgp
From 94efe397328f5466620ddc96d65b15f280d609fb Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Thu, 15 Aug 2019 08:57:44 -0400 Subject: [PATCH 15/18] (docs) minor formatting nit --- src/site/xdoc/index.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml index 75083ed96..e8150e284 100644 --- a/src/site/xdoc/index.xml +++ b/src/site/xdoc/index.xml @@ -92,7 +92,7 @@ Bean Collections has an additional dependency on

The latest BeanUtils release is available to download - here.

+ here.
1.9.4

  • Release Notes
  • From 783410a5875617e48212047cf0f2f532a325b7cf Mon Sep 17 00:00:00 2001 From: Rob Tompkins Date: Thu, 15 Aug 2019 09:20:17 -0400 Subject: [PATCH 16/18] (docs) source-repository.html -> scm.html --- src/site/site.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/site/site.xml b/src/site/site.xml index b708074f0..ab617e44e 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -29,7 +29,7 @@ - +
From 50264c92fc9209086bd0f6482d1d451f1bb69a81 Mon Sep 17 00:00:00 2001 From: Sebb Date: Thu, 15 Aug 2019 15:09:32 +0100 Subject: [PATCH 17/18] Ensure we generate sha256 hash references --- pom.xml | 3 +++ src/site/xdoc/download_beanutils.xml | 16 +++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 8f1ebc064..2ed61812d 100644 --- a/pom.xml +++ b/pom.xml @@ -36,6 +36,9 @@ 1.6 beanutils 1.9.4 + + 1.10 + sha256 BEANUTILS 12310460 diff --git a/src/site/xdoc/download_beanutils.xml b/src/site/xdoc/download_beanutils.xml index 397bea322..85c74405f 100644 --- a/src/site/xdoc/download_beanutils.xml +++ b/src/site/xdoc/download_beanutils.xml @@ -26,22 +26,24 @@ limitations under the License. | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates | +======================================================================+ | | - | 1) Re-generate using: mvn commons:download-page | + | 1) Re-generate using: mvn commons-build:download-page | | | | 2) Set the following properties in the component's pom: | - | - commons.componentid (required, alphabetic, lower case) | + | - commons.componentid (required, alphabetic, lower case) | | - commons.release.version (required) | | - commons.release.name (required) | | - commons.binary.suffix (optional) | | (defaults to "-bin", set to "" for pre-maven2 releases) | | - commons.release.desc (optional) | | - commons.release.subdir (optional) | + | - commons.release.hash (optional, lowercase, default sha512) | | | - | - commons.release.2/3.version (conditional) | - | - commons.release.2/3.name (conditional) | - | - commons.release.2/3.binary.suffix (optional) | - | - commons.release.2/3.desc (optional) | - | - commons.release.2/3.subdir (optional) | + | - commons.release.[234].version (conditional) | + | - commons.release.[234].name (conditional) | + | - commons.release.[234].binary.suffix (optional) | + | - commons.release.[234].desc (optional) | + | - commons.release.[234].subdir (optional) | + | - commons.release.[234].hash (optional, lowercase, [sha512])| | | | 3) Example Properties | | (commons.release.name inherited by parent: | From e47388703f9b627cc06bc672628bcc572189ed2d Mon Sep 17 00:00:00 2001 From: Carroll Chiou Date: Wed, 1 Sep 2021 17:47:35 -0600 Subject: [PATCH 18/18] BEANUTILS-539/BEANUTILS-509: 1.X line, switch WeakFastHashMap for ConcurrentWeakHashMap --- .../apache/commons/beanutils/BeanUtils.java | 28 +- .../beanutils/ConcurrentWeakKeyHashMap.java | 1449 +++++++++++++++++ .../commons/beanutils/ConvertUtilsBean.java | 6 +- .../commons/beanutils/PropertyUtilsBean.java | 10 +- .../commons/beanutils/WeakFastHashMap.java | 760 --------- .../locale/LocaleConvertUtilsBean.java | 18 - 6 files changed, 1456 insertions(+), 815 deletions(-) create mode 100644 src/main/java/org/apache/commons/beanutils/ConcurrentWeakKeyHashMap.java delete mode 100644 src/main/java/org/apache/commons/beanutils/WeakFastHashMap.java diff --git a/src/main/java/org/apache/commons/beanutils/BeanUtils.java b/src/main/java/org/apache/commons/beanutils/BeanUtils.java index e3f8a421e..396720265 100644 --- a/src/main/java/org/apache/commons/beanutils/BeanUtils.java +++ b/src/main/java/org/apache/commons/beanutils/BeanUtils.java @@ -474,32 +474,6 @@ public static boolean initCause(final Throwable throwable, final Throwable cause * @since 1.8.0 */ public static Map createCache() { - return new WeakFastHashMap(); - } - - /** - * Return whether a Map is fast - * @param map The map - * @return Whether it is fast or not. - * @since 1.8.0 - */ - public static boolean getCacheFast(final Map map) { - if (map instanceof WeakFastHashMap) { - return ((WeakFastHashMap) map).getFast(); - } else { - return false; - } - } - - /** - * Set whether fast on a Map - * @param map The map - * @param fast Whether it should be fast or not. - * @since 1.8.0 - */ - public static void setCacheFast(final Map map, final boolean fast) { - if (map instanceof WeakFastHashMap) { - ((WeakFastHashMap)map).setFast(fast); - } + return new ConcurrentWeakKeyHashMap(); } } diff --git a/src/main/java/org/apache/commons/beanutils/ConcurrentWeakKeyHashMap.java b/src/main/java/org/apache/commons/beanutils/ConcurrentWeakKeyHashMap.java new file mode 100644 index 000000000..49f45aebf --- /dev/null +++ b/src/main/java/org/apache/commons/beanutils/ConcurrentWeakKeyHashMap.java @@ -0,0 +1,1449 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.beanutils; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantLock; + + +/** + * An alternative weak-key {@link ConcurrentMap} which is similar to {@link java.util.concurrent.ConcurrentHashMap}. + *

+ * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * @see Creative Commons + * @param the type of keys maintained by this map + * @param the type of mapped values + * @author The Netty Project + * @author Doug Lea + * @author Jason T. Greene + * @author Trustin Lee + * @since 2.0 + */ +public final class ConcurrentWeakKeyHashMap extends AbstractMap implements ConcurrentMap { + + /* + * The basic strategy is to subdivide the table among Segments, + * each of which itself is a concurrently readable hash table. + */ + + /** + * The default initial capacity for this table, used when not otherwise specified in a constructor. + */ + static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The default load factor for this table, used when not otherwise specified in a constructor. + */ + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * The default concurrency level for this table, used when not otherwise specified in a constructor. + */ + static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + /** + * The maximum capacity, used if a higher value is implicitly specified by either of the constructors with + * arguments. MUST be a power of two <= 1<<30 to ensure that entries are indexable using integers. + */ + static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The maximum number of segments to allow; used to bound constructor arguments. + */ + static final int MAX_SEGMENTS = 1 << 16; // slightly conservative + + /** + * Number of unsynchronized retries in size and containsValue methods before resorting to locking. This is used to + * avoid unbounded retries if tables undergo continuous modification which would make it impossible to obtain an + * accurate result. + */ + static final int RETRIES_BEFORE_LOCK = 2; + + /* ---------------- Fields -------------- */ + + /** + * Mask value for indexing into segments. The upper bits of a key's hash code are used to choose the segment. + */ + final int segmentMask; + + /** + * Shift value for indexing within segments. + */ + final int segmentShift; + + /** + * The segments, each of which is a specialized hash table + */ + final Segment[] segments; + + Set keySet; + Set> entrySet; + Collection values; + + /* ---------------- Small Utilities -------------- */ + + /** + * Applies a supplemental hash function to a given hashCode, which defends against poor quality hash functions. + * This is critical because ConcurrentReferenceHashMap uses power-of-two length hash tables, that otherwise + * encounter collisions for hashCodes that do not differ in lower or upper bits. + */ + private static int hash(int h) { + // Spread bits to regularize both segment and index locations, + // using variant of single-word Wang/Jenkins hash. + h += h << 15 ^ 0xffffcd7d; + h ^= h >>> 10; + h += h << 3; + h ^= h >>> 6; + h += (h << 2) + (h << 14); + return h ^ h >>> 16; + } + + /** + * Returns the segment that should be used for key with given hash. + * + * @param hash the hash code for the key + * @return the segment + */ + final Segment segmentFor(int hash) { + return segments[hash >>> segmentShift & segmentMask]; + } + + private int hashOf(Object key) { + return hash(key.hashCode()); + } + + /* ---------------- Inner Classes -------------- */ + + /** + * A weak-key reference which stores the key hash needed for reclamation. + */ + static final class WeakKeyReference extends WeakReference { + + final int hash; + + WeakKeyReference(K key, int hash, ReferenceQueue refQueue) { + super(key, refQueue); + this.hash = hash; + } + + public final int keyHash() { + return hash; + } + + public final Object keyRef() { + return this; + } + } + + /** + * ConcurrentReferenceHashMap list entry. Note that this is never exported out as a user-visible Map.Entry. + * Because the value field is volatile, not final, it is legal wrt the Java Memory Model for an unsynchronized + * reader to see null instead of initial value when read via a data race. Although a reordering leading to this is + * not likely to ever actually occur, the Segment.readValueUnderLock method is used as a backup in case a null + * (pre-initialized) value is ever seen in an unsynchronized access method. + */ + static final class HashEntry { + final Object keyRef; + final int hash; + volatile Object valueRef; + final HashEntry next; + + HashEntry( + K key, int hash, HashEntry next, V value, + ReferenceQueue refQueue) { + this.hash = hash; + this.next = next; + this.keyRef = new WeakKeyReference(key, hash, refQueue); + this.valueRef = value; + } + + @SuppressWarnings("unchecked") + final K key() { + return ((WeakReference) keyRef).get(); + } + + final V value() { + return dereferenceValue(valueRef); + } + + @SuppressWarnings("unchecked") + final V dereferenceValue(Object value) { + if (value instanceof WeakKeyReference) { + return ((Reference) value).get(); + } + + return (V) value; + } + + final void setValue(V value) { + this.valueRef = value; + } + + @SuppressWarnings("unchecked") + static HashEntry[] newArray(int i) { + return new HashEntry[i]; + } + } + + /** + * Segments are specialized versions of hash tables. This subclasses from ReentrantLock opportunistically, just to + * simplify some locking and avoid separate construction. + */ + static final class Segment extends ReentrantLock { + /* + * Segments maintain a table of entry lists that are ALWAYS kept in a + * consistent state, so can be read without locking. Next fields of + * nodes are immutable (final). All list additions are performed at the + * front of each bin. This makes it easy to check changes, and also fast + * to traverse. When nodes would otherwise be changed, new nodes are + * created to replace them. This works well for hash tables since the + * bin lists tend to be short. (The average length is less than two for + * the default load factor threshold.) + * + * Read operations can thus proceed without locking, but rely on + * selected uses of volatiles to ensure that completed write operations + * performed by other threads are noticed. For most purposes, the + * "count" field, tracking the number of elements, serves as that + * volatile variable ensuring visibility. This is convenient because + * this field needs to be read in many read operations anyway: + * + * - All (unsynchronized) read operations must first read the + * "count" field, and should not look at table entries if + * it is 0. + * + * - All (synchronized) write operations should write to + * the "count" field after structurally changing any bin. + * The operations must not take any action that could even + * momentarily cause a concurrent read operation to see + * inconsistent data. This is made easier by the nature of + * the read operations in Map. For example, no operation + * can reveal that the table has grown but the threshold + * has not yet been updated, so there are no atomicity + * requirements for this with respect to reads. + * + * As a guide, all critical volatile reads and writes to the count field + * are marked in code comments. + */ + + private static final long serialVersionUID = -8328104880676891126L; + + /** + * The number of elements in this segment's region. + */ + transient volatile int count; + + /** + * Number of updates that alter the size of the table. This is used during bulk-read methods to make sure they + * see a consistent snapshot: If modCounts change during a traversal of segments computing size or checking + * containsValue, then we might have an inconsistent view of state so (usually) must retry. + */ + int modCount; + + /** + * The table is rehashed when its size exceeds this threshold. (The value of this field is always (capacity + * * loadFactor).) + */ + int threshold; + + /** + * The per-segment table. + */ + transient volatile HashEntry[] table; + + /** + * The load factor for the hash table. Even though this value is same for all segments, it is replicated to + * avoid needing links to outer object. + */ + final float loadFactor; + + /** + * The collected weak-key reference queue for this segment. This should be (re)initialized whenever table is + * assigned, + */ + transient volatile ReferenceQueue refQueue; + + Segment(int initialCapacity, float lf) { + loadFactor = lf; + setTable(HashEntry.newArray(initialCapacity)); + } + + @SuppressWarnings("unchecked") + static Segment[] newArray(int i) { + return new Segment[i]; + } + + private boolean keyEq(Object src, Object dest) { + return src.equals(dest); + } + + /** + * Sets table to new HashEntry array. Call only while holding lock or in constructor. + */ + void setTable(HashEntry[] newTable) { + threshold = (int) (newTable.length * loadFactor); + table = newTable; + refQueue = new ReferenceQueue(); + } + + /** + * Returns properly casted first entry of bin for given hash. + */ + HashEntry getFirst(int hash) { + HashEntry[] tab = table; + return tab[hash & tab.length - 1]; + } + + HashEntry newHashEntry( + K key, int hash, HashEntry next, V value) { + return new HashEntry( + key, hash, next, value, refQueue); + } + + /** + * Reads value field of an entry under lock. Called if value field ever appears to be null. This is possible + * only if a compiler happens to reorder a HashEntry initialization with its table assignment, which is legal + * under memory model but is not known to ever occur. + */ + V readValueUnderLock(HashEntry e) { + lock(); + try { + removeStale(); + return e.value(); + } finally { + unlock(); + } + } + + /* Specialized implementations of map methods */ + + V get(Object key, int hash) { + if (count != 0) { // read-volatile + HashEntry e = getFirst(hash); + while (e != null) { + if (e.hash == hash && keyEq(key, e.key())) { + Object opaque = e.valueRef; + if (opaque != null) { + return e.dereferenceValue(opaque); + } + + return readValueUnderLock(e); // recheck + } + e = e.next; + } + } + return null; + } + + boolean containsKey(Object key, int hash) { + if (count != 0) { // read-volatile + HashEntry e = getFirst(hash); + while (e != null) { + if (e.hash == hash && keyEq(key, e.key())) { + return true; + } + e = e.next; + } + } + return false; + } + + boolean containsValue(Object value) { + if (count != 0) { // read-volatile + HashEntry[] tab = table; + int len = tab.length; + for (int i = 0; i < len; i++) { + for (HashEntry e = tab[i]; e != null; e = e.next) { + Object opaque = e.valueRef; + V v; + + if (opaque == null) { + v = readValueUnderLock(e); // recheck + } else { + v = e.dereferenceValue(opaque); + } + + if (value.equals(v)) { + return true; + } + } + } + } + return false; + } + + boolean replace(K key, int hash, V oldValue, V newValue) { + lock(); + try { + removeStale(); + HashEntry e = getFirst(hash); + while (e != null && (e.hash != hash || !keyEq(key, e.key()))) { + e = e.next; + } + + boolean replaced = false; + if (e != null && oldValue.equals(e.value())) { + replaced = true; + e.setValue(newValue); + } + return replaced; + } finally { + unlock(); + } + } + + V replace(K key, int hash, V newValue) { + lock(); + try { + removeStale(); + HashEntry e = getFirst(hash); + while (e != null && (e.hash != hash || !keyEq(key, e.key()))) { + e = e.next; + } + + V oldValue = null; + if (e != null) { + oldValue = e.value(); + e.setValue(newValue); + } + return oldValue; + } finally { + unlock(); + } + } + + V put(K key, int hash, V value, boolean onlyIfAbsent) { + lock(); + try { + removeStale(); + int c = count; + if (c++ > threshold) { // ensure capacity + int reduced = rehash(); + if (reduced > 0) { + count = (c -= reduced) - 1; // write-volatile + } + } + + HashEntry[] tab = table; + int index = hash & tab.length - 1; + HashEntry first = tab[index]; + HashEntry e = first; + while (e != null && (e.hash != hash || !keyEq(key, e.key()))) { + e = e.next; + } + + V oldValue; + if (e != null) { + oldValue = e.value(); + if (!onlyIfAbsent) { + e.setValue(value); + } + } else { + oldValue = null; + ++modCount; + tab[index] = newHashEntry(key, hash, first, value); + count = c; // write-volatile + } + return oldValue; + } finally { + unlock(); + } + } + + int rehash() { + HashEntry[] oldTable = table; + int oldCapacity = oldTable.length; + if (oldCapacity >= MAXIMUM_CAPACITY) { + return 0; + } + + /* + * Reclassify nodes in each list to new Map. Because we are using + * power-of-two expansion, the elements from each bin must either + * stay at same index, or move with a power of two offset. We + * eliminate unnecessary node creation by catching cases where old + * nodes can be reused because their next fields won't change. + * Statistically, at the default threshold, only about one-sixth of + * them need cloning when a table doubles. The nodes they replace + * will be garbage collectable as soon as they are no longer + * referenced by any reader thread that may be in the midst of + * traversing table right now. + */ + + HashEntry[] newTable = HashEntry.newArray(oldCapacity << 1); + threshold = (int) (newTable.length * loadFactor); + int sizeMask = newTable.length - 1; + int reduce = 0; + for (int i = 0; i < oldCapacity; i++) { + // We need to guarantee that any existing reads of old Map can + // proceed. So we cannot yet null out each bin. + HashEntry e = oldTable[i]; + + if (e != null) { + HashEntry next = e.next; + int idx = e.hash & sizeMask; + + // Single node on list + if (next == null) { + newTable[idx] = e; + } else { + // Reuse trailing consecutive sequence at same slot + HashEntry lastRun = e; + int lastIdx = idx; + for (HashEntry last = next; last != null; last = last.next) { + int k = last.hash & sizeMask; + if (k != lastIdx) { + lastIdx = k; + lastRun = last; + } + } + newTable[lastIdx] = lastRun; + // Clone all remaining nodes + for (HashEntry p = e; p != lastRun; p = p.next) { + // Skip GC'd weak references + K key = p.key(); + if (key == null) { + reduce++; + continue; + } + int k = p.hash & sizeMask; + HashEntry n = newTable[k]; + newTable[k] = newHashEntry(key, p.hash, n, p.value()); + } + } + } + } + table = newTable; + return reduce; + } + + /** + * Remove; match on key only if value null, else match both. + */ + V remove(Object key, int hash, Object value, boolean refRemove) { + lock(); + try { + if (!refRemove) { + removeStale(); + } + int c = count - 1; + HashEntry[] tab = table; + int index = hash & tab.length - 1; + HashEntry first = tab[index]; + HashEntry e = first; + // a reference remove operation compares the Reference instance + while (e != null && key != e.keyRef && + (refRemove || hash != e.hash || !keyEq(key, e.key()))) { + e = e.next; + } + + V oldValue = null; + if (e != null) { + V v = e.value(); + if (value == null || value.equals(v)) { + oldValue = v; + // All entries following removed node can stay in list, + // but all preceding ones need to be cloned. + ++modCount; + HashEntry newFirst = e.next; + for (HashEntry p = first; p != e; p = p.next) { + K pKey = p.key(); + if (pKey == null) { // Skip GC'd keys + c--; + continue; + } + + newFirst = newHashEntry( + pKey, p.hash, newFirst, p.value()); + } + tab[index] = newFirst; + count = c; // write-volatile + } + } + return oldValue; + } finally { + unlock(); + } + } + + @SuppressWarnings("rawtypes") + final void removeStale() { + WeakKeyReference ref; + while ((ref = (WeakKeyReference) refQueue.poll()) != null) { + remove(ref.keyRef(), ref.keyHash(), null, true); + } + } + + void clear() { + if (count != 0) { + lock(); + try { + HashEntry[] tab = table; + for (int i = 0; i < tab.length; i++) { + tab[i] = null; + } + ++modCount; + // replace the reference queue to avoid unnecessary stale + // cleanups + refQueue = new ReferenceQueue(); + count = 0; // write-volatile + } finally { + unlock(); + } + } + } + } + + /* ---------------- Public operations -------------- */ + + /** + * Creates a new, empty map with the specified initial capacity, load factor and concurrency level. + * + * @param initialCapacity the initial capacity. The implementation performs internal sizing to accommodate this + * many elements. + * @param loadFactor the load factor threshold, used to control resizing. Resizing may be performed when the + * average number of elements per bin exceeds this threshold. + * @param concurrencyLevel the estimated number of concurrently updating threads. The implementation performs + * internal sizing to try to accommodate this many threads. + * @throws IllegalArgumentException if the initial capacity is negative or the load factor or concurrencyLevel are + * nonpositive. + */ + public ConcurrentWeakKeyHashMap( + int initialCapacity, float loadFactor, int concurrencyLevel) { + if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) { + throw new IllegalArgumentException(); + } + + if (concurrencyLevel > MAX_SEGMENTS) { + concurrencyLevel = MAX_SEGMENTS; + } + + // Find power-of-two sizes best matching arguments + int sshift = 0; + int ssize = 1; + while (ssize < concurrencyLevel) { + ++sshift; + ssize <<= 1; + } + segmentShift = 32 - sshift; + segmentMask = ssize - 1; + this.segments = Segment.newArray(ssize); + + if (initialCapacity > MAXIMUM_CAPACITY) { + initialCapacity = MAXIMUM_CAPACITY; + } + int c = initialCapacity / ssize; + if (c * ssize < initialCapacity) { + ++c; + } + int cap = 1; + while (cap < c) { + cap <<= 1; + } + + for (int i = 0; i < this.segments.length; ++i) { + this.segments[i] = new Segment(cap, loadFactor); + } + } + + /** + * Creates a new, empty map with the specified initial capacity and load factor and with the default reference types + * (weak keys, strong values), and concurrencyLevel (16). + * + * @param initialCapacity The implementation performs internal sizing to accommodate this many elements. + * @param loadFactor the load factor threshold, used to control resizing. Resizing may be performed when the + * average number of elements per bin exceeds this threshold. + * @throws IllegalArgumentException if the initial capacity of elements is negative or the load factor is + * nonpositive + */ + public ConcurrentWeakKeyHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL); + } + + /** + * Creates a new, empty map with the specified initial capacity, and with default reference types (weak keys, strong + * values), load factor (0.75) and concurrencyLevel (16). + * + * @param initialCapacity the initial capacity. The implementation performs internal sizing to accommodate this many + * elements. + * @throws IllegalArgumentException if the initial capacity of elements is negative. + */ + public ConcurrentWeakKeyHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); + } + + /** + * Creates a new, empty map with a default initial capacity (16), reference types (weak keys, strong values), + * default load factor (0.75) and concurrencyLevel (16). + */ + public ConcurrentWeakKeyHashMap() { + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); + } + + /** + * Creates a new map with the same mappings as the given map. The map is created with a capacity of 1.5 times the + * number of mappings in the given map or 16 (whichever is greater), and a default load factor (0.75) and + * concurrencyLevel (16). + * + * @param m the map + */ + public ConcurrentWeakKeyHashMap(Map m) { + this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, + DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR, + DEFAULT_CONCURRENCY_LEVEL); + putAll(m); + } + + /** + * Returns true if this map contains no key-value mappings. + * + * @return true if this map contains no key-value mappings + */ + @Override + public boolean isEmpty() { + final Segment[] segments = this.segments; + /* + * We keep track of per-segment modCounts to avoid ABA problems in which + * an element in one segment was added and in another removed during + * traversal, in which case the table was never actually empty at any + * point. Note the similar use of modCounts in the size() and + * containsValue() methods, which are the only other methods also + * susceptible to ABA problems. + */ + int[] mc = new int[segments.length]; + int mcsum = 0; + for (int i = 0; i < segments.length; ++i) { + if (segments[i].count != 0) { + return false; + } else { + mcsum += mc[i] = segments[i].modCount; + } + } + // If mcsum happens to be zero, then we know we got a snapshot before + // any modifications at all were made. This is probably common enough + // to bother tracking. + if (mcsum != 0) { + for (int i = 0; i < segments.length; ++i) { + if (segments[i].count != 0 || mc[i] != segments[i].modCount) { + return false; + } + } + } + return true; + } + + /** + * Returns the number of key-value mappings in this map. If the map contains more than Integer.MAX_VALUE + * elements, returns Integer.MAX_VALUE. + * + * @return the number of key-value mappings in this map + */ + @Override + public int size() { + final Segment[] segments = this.segments; + long sum = 0; + long check = 0; + int[] mc = new int[segments.length]; + // Try a few times to get accurate count. On failure due to continuous + // async changes in table, resort to locking. + for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { + check = 0; + sum = 0; + int mcsum = 0; + for (int i = 0; i < segments.length; ++i) { + sum += segments[i].count; + mcsum += mc[i] = segments[i].modCount; + } + if (mcsum != 0) { + for (int i = 0; i < segments.length; ++i) { + check += segments[i].count; + if (mc[i] != segments[i].modCount) { + check = -1; // force retry + break; + } + } + } + if (check == sum) { + break; + } + } + if (check != sum) { // Resort to locking all segments + sum = 0; + for (int i = 0; i < segments.length; ++i) { + segments[i].lock(); + } + try { + for (int i = 0; i < segments.length; ++i) { + sum += segments[i].count; + } + } finally { + for (int i = 0; i < segments.length; ++i) { + segments[i].unlock(); + } + } + } + if (sum > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } else { + return (int) sum; + } + } + + /** + * Returns the value to which the specified key is mapped, or {@code null} if this map contains no mapping for the + * key. + *

More formally, if this map contains a mapping from a key {@code k} to a value {@code v} such that {@code + * key.equals(k)}, then this method returns {@code v}; otherwise it returns {@code null}. (There can be at most one + * such mapping.)

+ * + * @throws NullPointerException if the specified key is null + */ + @Override + public V get(Object key) { + int hash = hashOf(key); + return segmentFor(hash).get(key, hash); + } + + /** + * Tests if the specified object is a key in this table. + * + * @param key possible key + * @return true if and only if the specified object is a key in this table, as determined by the + * equals method; false otherwise. + * @throws NullPointerException if the specified key is null + */ + @Override + public boolean containsKey(Object key) { + int hash = hashOf(key); + return segmentFor(hash).containsKey(key, hash); + } + + /** + * Returns true if this map maps one or more keys to the specified value. Note: This method requires a + * full internal traversal of the hash table, and so is much slower than method containsKey. + * + * @param value value whose presence in this map is to be tested + * @return true if this map maps one or more keys to the specified value + * @throws NullPointerException if the specified value is null + */ + + @Override + public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException(); + } + + // See explanation of modCount use above + + final Segment[] segments = this.segments; + int[] mc = new int[segments.length]; + + // Try a few times without locking + for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { + int mcsum = 0; + for (int i = 0; i < segments.length; ++i) { + mcsum += mc[i] = segments[i].modCount; + if (segments[i].containsValue(value)) { + return true; + } + } + boolean cleanSweep = true; + if (mcsum != 0) { + for (int i = 0; i < segments.length; ++i) { + if (mc[i] != segments[i].modCount) { + cleanSweep = false; + break; + } + } + } + if (cleanSweep) { + return false; + } + } + // Resort to locking all segments + for (int i = 0; i < segments.length; ++i) { + segments[i].lock(); + } + boolean found = false; + try { + for (int i = 0; i < segments.length; ++i) { + if (segments[i].containsValue(value)) { + found = true; + break; + } + } + } finally { + for (int i = 0; i < segments.length; ++i) { + segments[i].unlock(); + } + } + return found; + } + + /** + * Legacy method testing if some key maps into the specified value in this table. This method is identical in + * functionality to {@link #containsValue}, and exists solely to ensure full compatibility with class {@link + * Hashtable}, which supported this method prior to introduction of the Java Collections framework. + * + * @param value a value to search for + * @return true if and only if some key maps to the value argument in this table as + * determined by the equals method; false otherwise + * @throws NullPointerException if the specified value is null + */ + public boolean contains(Object value) { + return containsValue(value); + } + + /** + * Maps the specified key to the specified value in this table. Neither the key nor the value can be null. + *

The value can be retrieved by calling the get method with a key that is equal to the + * original key.

+ * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with key, or null if there was no mapping for + * key + * @throws NullPointerException if the specified key or value is null + */ + @Override + public V put(K key, V value) { + if (value == null) { + throw new NullPointerException(); + } + int hash = hashOf(key); + return segmentFor(hash).put(key, hash, value, false); + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, or null if there was no mapping for the + * key + * @throws NullPointerException if the specified key or value is null + */ + @Override + public V putIfAbsent(K key, V value) { + if (value == null) { + throw new NullPointerException(); + } + int hash = hashOf(key); + return segmentFor(hash).put(key, hash, value, true); + } + + /** + * Copies all of the mappings from the specified map to this one. These mappings replace any mappings that this map + * had for any of the keys currently in the specified map. + * + * @param m mappings to be stored in this map + */ + @Override + public void putAll(Map m) { + for (Map.Entry e : m.entrySet()) { + put(e.getKey(), e.getValue()); + } + } + + /** + * Removes the key (and its corresponding value) from this map. This method does nothing if the key is not in the + * map. + * + * @param key the key that needs to be removed + * @return the previous value associated with key, or null if there was no mapping for + * key + * @throws NullPointerException if the specified key is null + */ + @Override + public V remove(Object key) { + int hash = hashOf(key); + return segmentFor(hash).remove(key, hash, null, false); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if the specified key is null + */ + @Override + public boolean remove(Object key, Object value) { + int hash = hashOf(key); + if (value == null) { + return false; + } + return segmentFor(hash).remove(key, hash, value, false) != null; + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if any of the arguments are null + */ + @Override + public boolean replace(K key, V oldValue, V newValue) { + if (oldValue == null || newValue == null) { + throw new NullPointerException(); + } + int hash = hashOf(key); + return segmentFor(hash).replace(key, hash, oldValue, newValue); + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, or null if there was no mapping for the + * key + * @throws NullPointerException if the specified key or value is null + */ + @Override + public V replace(K key, V value) { + if (value == null) { + throw new NullPointerException(); + } + int hash = hashOf(key); + return segmentFor(hash).replace(key, hash, value); + } + + /** + * Removes all of the mappings from this map. + */ + @Override + public void clear() { + for (int i = 0; i < segments.length; ++i) { + segments[i].clear(); + } + } + + /** + * Removes any stale entries whose keys have been finalized. Use of this method is normally not necessary since + * stale entries are automatically removed lazily, when blocking operations are required. However, there are some + * cases where this operation should be performed eagerly, such as cleaning up old references to a ClassLoader in a + * multi-classloader environment. + *

+ * Note: this method will acquire locks, one at a time, across all segments of this table, so if it is to be used, + * it should be used sparingly.

+ */ + public void purgeStaleEntries() { + for (int i = 0; i < segments.length; ++i) { + segments[i].removeStale(); + } + } + + /** + * Returns a {@link Set} view of the keys contained in this map. The set is backed by the map, so changes to the + * map are reflected in the set, and vice-versa. The set supports element removal, which removes the corresponding + * mapping from this map, via the Iterator.remove, Set.remove, removeAll, + * retainAll, and clear operations. It does not support the add or + * addAll operations. + *

The view's iterator is a "weakly consistent" iterator that will never throw {@link + * ConcurrentModificationException}, and guarantees to traverse elements as they existed upon construction of the + * iterator, and may (but is not guaranteed to) reflect any modifications subsequent to construction.

+ */ + @Override + public Set keySet() { + Set ks = keySet; + return ks != null ? ks : (keySet = new KeySet()); + } + + /** + * Returns a {@link Collection} view of the values contained in this map. The collection is backed by the map, so + * changes to the map are reflected in the collection, and vice-versa. The collection supports element removal, + * which removes the corresponding mapping from this map, via the Iterator.remove, + * Collection.remove, removeAll, retainAll, and clear operations. + * It does not support the add or addAll operations. + *

The view's iterator is a "weakly consistent" iterator that will never throw {@link + * ConcurrentModificationException}, and guarantees to traverse elements as they existed upon construction of the + * iterator, and may (but is not guaranteed to) reflect any modifications subsequent to construction.

+ */ + @Override + public Collection values() { + Collection vs = values; + return vs != null ? vs : (values = new Values()); + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. The set is backed by the map, so changes to the + * map are reflected in the set, and vice-versa. The set supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, Set.remove, removeAll, + * retainAll, and clear operations. It does not support the add or + * addAll operations. + *

The view's iterator is a "weakly consistent" iterator that will never throw {@link + * ConcurrentModificationException}, and guarantees to traverse elements as they existed upon construction of the + * iterator, and may (but is not guaranteed to) reflect any modifications subsequent to construction.

+ */ + @Override + public Set> entrySet() { + Set> es = entrySet; + return es != null ? es : (entrySet = new EntrySet()); + } + + /** + * Returns an enumeration of the keys in this table. + * + * @return an enumeration of the keys in this table + * @see #keySet() + */ + public Enumeration keys() { + return new KeyIterator(); + } + + /** + * Returns an enumeration of the values in this table. + * + * @return an enumeration of the values in this table + * @see #values() + */ + public Enumeration elements() { + return new ValueIterator(); + } + + /* ---------------- Iterator Support -------------- */ + + abstract class HashIterator { + int nextSegmentIndex; + int nextTableIndex; + HashEntry[] currentTable; + HashEntry nextEntry; + HashEntry lastReturned; + K currentKey; // Strong reference to weak key (prevents gc) + + HashIterator() { + nextSegmentIndex = segments.length - 1; + nextTableIndex = -1; + advance(); + } + + public void rewind() { + nextSegmentIndex = segments.length - 1; + nextTableIndex = -1; + currentTable = null; + nextEntry = null; + lastReturned = null; + currentKey = null; + advance(); + } + + public boolean hasMoreElements() { + return hasNext(); + } + + final void advance() { + if (nextEntry != null && (nextEntry = nextEntry.next) != null) { + return; + } + + while (nextTableIndex >= 0) { + if ((nextEntry = currentTable[nextTableIndex--]) != null) { + return; + } + } + + while (nextSegmentIndex >= 0) { + Segment seg = segments[nextSegmentIndex--]; + if (seg.count != 0) { + currentTable = seg.table; + for (int j = currentTable.length - 1; j >= 0; --j) { + if ((nextEntry = currentTable[j]) != null) { + nextTableIndex = j - 1; + return; + } + } + } + } + } + + public boolean hasNext() { + while (nextEntry != null) { + if (nextEntry.key() != null) { + return true; + } + advance(); + } + + return false; + } + + HashEntry nextEntry() { + do { + if (nextEntry == null) { + throw new NoSuchElementException(); + } + + lastReturned = nextEntry; + currentKey = lastReturned.key(); + advance(); + } while (currentKey == null); // Skip GC'd keys + + return lastReturned; + } + + public void remove() { + if (lastReturned == null) { + throw new IllegalStateException(); + } + ConcurrentWeakKeyHashMap.this.remove(currentKey); + lastReturned = null; + } + } + + final class KeyIterator + extends HashIterator implements ReusableIterator, Enumeration { + + @Override + public K next() { + return super.nextEntry().key(); + } + + @Override + public K nextElement() { + return super.nextEntry().key(); + } + } + + final class ValueIterator + extends HashIterator implements ReusableIterator, Enumeration { + + @Override + public V next() { + return super.nextEntry().value(); + } + + @Override + public V nextElement() { + return super.nextEntry().value(); + } + } + + /* + * This class is needed for JDK5 compatibility. + */ + static class SimpleEntry implements Entry { + + private final K key; + + private V value; + + public SimpleEntry(K key, V value) { + this.key = key; + this.value = value; + + } + + public SimpleEntry(Entry entry) { + this.key = entry.getKey(); + this.value = entry.getValue(); + + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public V setValue(V value) { + V oldValue = this.value; + this.value = value; + return oldValue; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + @SuppressWarnings("rawtypes") + Map.Entry e = (Map.Entry) o; + return eq(key, e.getKey()) && eq(value, e.getValue()); + } + + @Override + public int hashCode() { + return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); + } + + @Override + public String toString() { + return key + "=" + value; + } + + private static boolean eq(Object o1, Object o2) { + return o1 == null ? o2 == null : o1.equals(o2); + } + } + + /** + * Custom Entry class used by EntryIterator.next(), that relays setValue changes to the underlying map. + */ + final class WriteThroughEntry extends SimpleEntry { + + WriteThroughEntry(K k, V v) { + super(k, v); + } + + /** + * Set our entry's value and write through to the map. The value to return is somewhat arbitrary here. Since a + * WriteThroughEntry does not necessarily track asynchronous changes, the most recent "previous" value could be + * different from what we return (or could even have been removed in which case the put will re-establish). We + * do not and can not guarantee more. + */ + @Override + public V setValue(V value) { + + if (value == null) { + throw new NullPointerException(); + } + V v = super.setValue(value); + ConcurrentWeakKeyHashMap.this.put(getKey(), value); + return v; + } + + } + + final class EntryIterator extends HashIterator implements + ReusableIterator> { + @Override + public Map.Entry next() { + HashEntry e = super.nextEntry(); + return new WriteThroughEntry(e.key(), e.value()); + } + } + + final class KeySet extends AbstractSet { + @Override + public Iterator iterator() { + + return new KeyIterator(); + } + + @Override + public int size() { + return ConcurrentWeakKeyHashMap.this.size(); + } + + @Override + public boolean isEmpty() { + return ConcurrentWeakKeyHashMap.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return ConcurrentWeakKeyHashMap.this.containsKey(o); + } + + @Override + public boolean remove(Object o) { + return ConcurrentWeakKeyHashMap.this.remove(o) != null; + + } + + @Override + public void clear() { + ConcurrentWeakKeyHashMap.this.clear(); + } + } + + final class Values extends AbstractCollection { + @Override + public Iterator iterator() { + return new ValueIterator(); + } + + @Override + public int size() { + return ConcurrentWeakKeyHashMap.this.size(); + } + + @Override + public boolean isEmpty() { + return ConcurrentWeakKeyHashMap.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return ConcurrentWeakKeyHashMap.this.containsValue(o); + } + + @Override + public void clear() { + ConcurrentWeakKeyHashMap.this.clear(); + } + } + + final class EntrySet extends AbstractSet> { + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + Map.Entry e = (Map.Entry) o; + V v = ConcurrentWeakKeyHashMap.this.get(e.getKey()); + return v != null && v.equals(e.getValue()); + } + + @Override + public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + Map.Entry e = (Map.Entry) o; + return ConcurrentWeakKeyHashMap.this.remove(e.getKey(), e.getValue()); + } + + @Override + public int size() { + return ConcurrentWeakKeyHashMap.this.size(); + } + + @Override + public boolean isEmpty() { + return ConcurrentWeakKeyHashMap.this.isEmpty(); + } + + @Override + public void clear() { + ConcurrentWeakKeyHashMap.this.clear(); + } + } + + public static interface ReusableIterator extends Iterator { + void rewind(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/apache/commons/beanutils/ConvertUtilsBean.java b/src/main/java/org/apache/commons/beanutils/ConvertUtilsBean.java index d83746260..8cd36d62f 100644 --- a/src/main/java/org/apache/commons/beanutils/ConvertUtilsBean.java +++ b/src/main/java/org/apache/commons/beanutils/ConvertUtilsBean.java @@ -145,8 +145,8 @@ protected static ConvertUtilsBean getInstance() { * The set of {@link Converter}s that can be used to convert Strings * into objects of a specified Class, keyed by the destination Class. */ - private final WeakFastHashMap, Converter> converters = - new WeakFastHashMap, Converter>(); + private final ConcurrentWeakKeyHashMap, Converter> converters = + new ConcurrentWeakKeyHashMap, Converter>(); /** * The Log instance for this class. @@ -157,9 +157,7 @@ protected static ConvertUtilsBean getInstance() { /** Construct a bean with standard converters registered */ public ConvertUtilsBean() { - converters.setFast(false); deregister(); - converters.setFast(true); } // --------------------------------------------------------- Public Methods diff --git a/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java b/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java index 36eb7f57b..4f2d975d6 100644 --- a/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java +++ b/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java @@ -113,8 +113,8 @@ protected static PropertyUtilsBean getInstance() { * The cache of PropertyDescriptor arrays for beans we have already * introspected, keyed by the java.lang.Class of this object. */ - private WeakFastHashMap, BeanIntrospectionData> descriptorsCache = null; - private WeakFastHashMap, FastHashMap> mappedDescriptorsCache = null; + private ConcurrentWeakKeyHashMap, BeanIntrospectionData> descriptorsCache; + private ConcurrentWeakKeyHashMap, FastHashMap> mappedDescriptorsCache; /** An empty object array */ private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; @@ -129,10 +129,8 @@ protected static PropertyUtilsBean getInstance() { /** Base constructor */ public PropertyUtilsBean() { - descriptorsCache = new WeakFastHashMap, BeanIntrospectionData>(); - descriptorsCache.setFast(true); - mappedDescriptorsCache = new WeakFastHashMap, FastHashMap>(); - mappedDescriptorsCache.setFast(true); + descriptorsCache = new ConcurrentWeakKeyHashMap, BeanIntrospectionData>(); + mappedDescriptorsCache = new ConcurrentWeakKeyHashMap, FastHashMap>(); introspectors = new CopyOnWriteArrayList(); resetBeanIntrospectors(); } diff --git a/src/main/java/org/apache/commons/beanutils/WeakFastHashMap.java b/src/main/java/org/apache/commons/beanutils/WeakFastHashMap.java deleted file mode 100644 index ea0d2dff4..000000000 --- a/src/main/java/org/apache/commons/beanutils/WeakFastHashMap.java +++ /dev/null @@ -1,760 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.beanutils; - -import java.util.Collection; -import java.util.ConcurrentModificationException; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.WeakHashMap; - -/** - *

A customized implementation of java.util.HashMap designed - * to operate in a multithreaded environment where the large majority of - * method calls are read-only, instead of structural changes. When operating - * in "fast" mode, read calls are non-synchronized and write calls perform the - * following steps:

- *
    - *
  • Clone the existing collection - *
  • Perform the modification on the clone - *
  • Replace the existing collection with the (modified) clone - *
- *

When first created, objects of this class default to "slow" mode, where - * all accesses of any type are synchronized but no cloning takes place. This - * is appropriate for initially populating the collection, followed by a switch - * to "fast" mode (by calling setFast(true)) after initialization - * is complete.

- * - *

NOTE: If you are creating and accessing a - * HashMap only within a single thread, you should use - * java.util.HashMap directly (with no synchronization), for - * maximum performance.

- * - *

NOTE: This class is not cross-platform. - * Using it may cause unexpected failures on some architectures. - * It suffers from the same problems as the double-checked locking idiom. - * In particular, the instruction that clones the internal collection and the - * instruction that sets the internal reference to the clone can be executed - * or perceived out-of-order. This means that any read operation might fail - * unexpectedly, as it may be reading the state of the internal collection - * before the internal collection is fully formed. - * For more information on the double-checked locking idiom, see the - * - * Double-Checked Locking Idiom Is Broken Declaration.

- * - * @since Commons Collections 1.0 - * @version $Id$ - */ -class WeakFastHashMap extends HashMap { - - /** - * The underlying map we are managing. - */ - private Map map = null; - - /** - * Are we currently operating in "fast" mode? - */ - private boolean fast = false; - - // Constructors - // ---------------------------------------------------------------------- - - /** - * Construct an empty map. - */ - public WeakFastHashMap() { - super(); - this.map = createMap(); - } - - /** - * Construct an empty map with the specified capacity. - * - * @param capacity the initial capacity of the empty map - */ - public WeakFastHashMap(final int capacity) { - super(); - this.map = createMap(capacity); - } - - /** - * Construct an empty map with the specified capacity and load factor. - * - * @param capacity the initial capacity of the empty map - * @param factor the load factor of the new map - */ - public WeakFastHashMap(final int capacity, final float factor) { - super(); - this.map = createMap(capacity, factor); - } - - /** - * Construct a new map with the same mappings as the specified map. - * - * @param map the map whose mappings are to be copied - */ - public WeakFastHashMap(final Map map) { - super(); - this.map = createMap(map); - } - - - // Property access - // ---------------------------------------------------------------------- - - /** - * Returns true if this map is operating in fast mode. - * - * @return true if this map is operating in fast mode - */ - public boolean getFast() { - return (this.fast); - } - - /** - * Sets whether this map is operating in fast mode. - * - * @param fast true if this map should operate in fast mode - */ - public void setFast(final boolean fast) { - this.fast = fast; - } - - - // Map access - // ---------------------------------------------------------------------- - // These methods can forward straight to the wrapped Map in 'fast' mode. - // (because they are query methods) - - /** - * Return the value to which this map maps the specified key. Returns - * null if the map contains no mapping for this key, or if - * there is a mapping with a value of null. Use the - * containsKey() method to disambiguate these cases. - * - * @param key the key whose value is to be returned - * @return the value mapped to that key, or null - */ - @Override - public V get(final Object key) { - if (fast) { - return (map.get(key)); - } else { - synchronized (map) { - return (map.get(key)); - } - } - } - - /** - * Return the number of key-value mappings in this map. - * - * @return the current size of the map - */ - @Override - public int size() { - if (fast) { - return (map.size()); - } else { - synchronized (map) { - return (map.size()); - } - } - } - - /** - * Return true if this map contains no mappings. - * - * @return is the map currently empty - */ - @Override - public boolean isEmpty() { - if (fast) { - return (map.isEmpty()); - } else { - synchronized (map) { - return (map.isEmpty()); - } - } - } - - /** - * Return true if this map contains a mapping for the - * specified key. - * - * @param key the key to be searched for - * @return true if the map contains the key - */ - @Override - public boolean containsKey(final Object key) { - if (fast) { - return (map.containsKey(key)); - } else { - synchronized (map) { - return (map.containsKey(key)); - } - } - } - - /** - * Return true if this map contains one or more keys mapping - * to the specified value. - * - * @param value the value to be searched for - * @return true if the map contains the value - */ - @Override - public boolean containsValue(final Object value) { - if (fast) { - return (map.containsValue(value)); - } else { - synchronized (map) { - return (map.containsValue(value)); - } - } - } - - // Map modification - // ---------------------------------------------------------------------- - // These methods perform special behaviour in 'fast' mode. - // The map is cloned, updated and then assigned back. - // See the comments at the top as to why this won't always work. - - /** - * Associate the specified value with the specified key in this map. - * If the map previously contained a mapping for this key, the old - * value is replaced and returned. - * - * @param key the key with which the value is to be associated - * @param value the value to be associated with this key - * @return the value previously mapped to the key, or null - */ - @Override - public V put(final K key, final V value) { - if (fast) { - synchronized (this) { - final Map temp = cloneMap(map); - final V result = temp.put(key, value); - map = temp; - return (result); - } - } else { - synchronized (map) { - return (map.put(key, value)); - } - } - } - - /** - * Copy all of the mappings from the specified map to this one, replacing - * any mappings with the same keys. - * - * @param in the map whose mappings are to be copied - */ - @Override - public void putAll(final Map in) { - if (fast) { - synchronized (this) { - final Map temp = cloneMap(map); - temp.putAll(in); - map = temp; - } - } else { - synchronized (map) { - map.putAll(in); - } - } - } - - /** - * Remove any mapping for this key, and return any previously - * mapped value. - * - * @param key the key whose mapping is to be removed - * @return the value removed, or null - */ - @Override - public V remove(final Object key) { - if (fast) { - synchronized (this) { - final Map temp = cloneMap(map); - final V result = temp.remove(key); - map = temp; - return (result); - } - } else { - synchronized (map) { - return (map.remove(key)); - } - } - } - - /** - * Remove all mappings from this map. - */ - @Override - public void clear() { - if (fast) { - synchronized (this) { - map = createMap(); - } - } else { - synchronized (map) { - map.clear(); - } - } - } - - // Basic object methods - // ---------------------------------------------------------------------- - - /** - * Compare the specified object with this list for equality. This - * implementation uses exactly the code that is used to define the - * list equals function in the documentation for the - * Map.equals method. - * - * @param o the object to be compared to this list - * @return true if the two maps are equal - */ - @Override - public boolean equals(final Object o) { - // Simple tests that require no synchronization - if (o == this) { - return (true); - } else if (!(o instanceof Map)) { - return (false); - } - final Map mo = (Map) o; - - // Compare the two maps for equality - if (fast) { - if (mo.size() != map.size()) { - return (false); - } - for (final Map.Entry e : map.entrySet()) { - final K key = e.getKey(); - final V value = e.getValue(); - if (value == null) { - if (!(mo.get(key) == null && mo.containsKey(key))) { - return (false); - } - } else { - if (!value.equals(mo.get(key))) { - return (false); - } - } - } - return (true); - - } else { - synchronized (map) { - if (mo.size() != map.size()) { - return (false); - } - for (final Map.Entry e : map.entrySet()) { - final K key = e.getKey(); - final V value = e.getValue(); - if (value == null) { - if (!(mo.get(key) == null && mo.containsKey(key))) { - return (false); - } - } else { - if (!value.equals(mo.get(key))) { - return (false); - } - } - } - return (true); - } - } - } - - /** - * Return the hash code value for this map. This implementation uses - * exactly the code that is used to define the list hash function in the - * documentation for the Map.hashCode method. - * - * @return suitable integer hash code - */ - @Override - public int hashCode() { - if (fast) { - int h = 0; - for (final Map.Entry e : map.entrySet()) { - h += e.hashCode(); - } - return (h); - } else { - synchronized (map) { - int h = 0; - for (final Map.Entry e : map.entrySet()) { - h += e.hashCode(); - } - return (h); - } - } - } - - /** - * Return a shallow copy of this FastHashMap instance. - * The keys and values themselves are not copied. - * - * @return a clone of this map - */ - @Override - public Object clone() { - WeakFastHashMap results = null; - if (fast) { - results = new WeakFastHashMap(map); - } else { - synchronized (map) { - results = new WeakFastHashMap(map); - } - } - results.setFast(getFast()); - return (results); - } - - // Map views - // ---------------------------------------------------------------------- - - /** - * Return a collection view of the mappings contained in this map. Each - * element in the returned collection is a Map.Entry. - * @return the set of map Map entries - */ - @Override - public Set> entrySet() { - return new EntrySet(); - } - - /** - * Return a set view of the keys contained in this map. - * @return the set of the Map's keys - */ - @Override - public Set keySet() { - return new KeySet(); - } - - /** - * Return a collection view of the values contained in this map. - * @return the set of the Map's values - */ - @Override - public Collection values() { - return new Values(); - } - - // Abstractions on Map creations (for subclasses such as WeakFastHashMap) - // ---------------------------------------------------------------------- - - protected Map createMap() { - return new WeakHashMap(); - } - - protected Map createMap(final int capacity) { - return new WeakHashMap(capacity); - } - - protected Map createMap(final int capacity, final float factor) { - return new WeakHashMap(capacity, factor); - } - - protected Map createMap(final Map map) { - return new WeakHashMap(map); - } - - protected Map cloneMap(final Map map) { - return createMap(map); - } - - // Map view inner classes - // ---------------------------------------------------------------------- - - /** - * Abstract collection implementation shared by keySet(), values() and entrySet(). - * - * @param the element type - */ - private abstract class CollectionView implements Collection { - - public CollectionView() { - } - - protected abstract Collection get(Map map); - protected abstract E iteratorNext(Map.Entry entry); - - - public void clear() { - if (fast) { - synchronized (WeakFastHashMap.this) { - map = createMap(); - } - } else { - synchronized (map) { - get(map).clear(); - } - } - } - - public boolean remove(final Object o) { - if (fast) { - synchronized (WeakFastHashMap.this) { - final Map temp = cloneMap(map); - final boolean r = get(temp).remove(o); - map = temp; - return r; - } - } else { - synchronized (map) { - return get(map).remove(o); - } - } - } - - public boolean removeAll(final Collection o) { - if (fast) { - synchronized (WeakFastHashMap.this) { - final Map temp = cloneMap(map); - final boolean r = get(temp).removeAll(o); - map = temp; - return r; - } - } else { - synchronized (map) { - return get(map).removeAll(o); - } - } - } - - public boolean retainAll(final Collection o) { - if (fast) { - synchronized (WeakFastHashMap.this) { - final Map temp = cloneMap(map); - final boolean r = get(temp).retainAll(o); - map = temp; - return r; - } - } else { - synchronized (map) { - return get(map).retainAll(o); - } - } - } - - public int size() { - if (fast) { - return get(map).size(); - } else { - synchronized (map) { - return get(map).size(); - } - } - } - - - public boolean isEmpty() { - if (fast) { - return get(map).isEmpty(); - } else { - synchronized (map) { - return get(map).isEmpty(); - } - } - } - - public boolean contains(final Object o) { - if (fast) { - return get(map).contains(o); - } else { - synchronized (map) { - return get(map).contains(o); - } - } - } - - public boolean containsAll(final Collection o) { - if (fast) { - return get(map).containsAll(o); - } else { - synchronized (map) { - return get(map).containsAll(o); - } - } - } - - public T[] toArray(final T[] o) { - if (fast) { - return get(map).toArray(o); - } else { - synchronized (map) { - return get(map).toArray(o); - } - } - } - - public Object[] toArray() { - if (fast) { - return get(map).toArray(); - } else { - synchronized (map) { - return get(map).toArray(); - } - } - } - - - @Override - public boolean equals(final Object o) { - if (o == this) { - return true; - } - if (fast) { - return get(map).equals(o); - } else { - synchronized (map) { - return get(map).equals(o); - } - } - } - - @Override - public int hashCode() { - if (fast) { - return get(map).hashCode(); - } else { - synchronized (map) { - return get(map).hashCode(); - } - } - } - - public boolean add(final E o) { - throw new UnsupportedOperationException(); - } - - public boolean addAll(final Collection c) { - throw new UnsupportedOperationException(); - } - - public Iterator iterator() { - return new CollectionViewIterator(); - } - - private class CollectionViewIterator implements Iterator { - - private Map expected; - private Map.Entry lastReturned = null; - private final Iterator> iterator; - - public CollectionViewIterator() { - this.expected = map; - this.iterator = expected.entrySet().iterator(); - } - - public boolean hasNext() { - if (expected != map) { - throw new ConcurrentModificationException(); - } - return iterator.hasNext(); - } - - public E next() { - if (expected != map) { - throw new ConcurrentModificationException(); - } - lastReturned = iterator.next(); - return iteratorNext(lastReturned); - } - - public void remove() { - if (lastReturned == null) { - throw new IllegalStateException(); - } - if (fast) { - synchronized (WeakFastHashMap.this) { - if (expected != map) { - throw new ConcurrentModificationException(); - } - WeakFastHashMap.this.remove(lastReturned.getKey()); - lastReturned = null; - expected = map; - } - } else { - iterator.remove(); - lastReturned = null; - } - } - } - } - - /** - * Set implementation over the keys of the FastHashMap - */ - private class KeySet extends CollectionView implements Set { - - @Override - protected Collection get(final Map map) { - return map.keySet(); - } - - @Override - protected K iteratorNext(final Map.Entry entry) { - return entry.getKey(); - } - - } - - /** - * Collection implementation over the values of the FastHashMap - */ - private class Values extends CollectionView { - - @Override - protected Collection get(final Map map) { - return map.values(); - } - - @Override - protected V iteratorNext(final Map.Entry entry) { - return entry.getValue(); - } - } - - /** - * Set implementation over the entries of the FastHashMap - */ - private class EntrySet extends CollectionView> implements Set> { - - @Override - protected Collection> get(final Map map) { - return map.entrySet(); - } - - @Override - protected Map.Entry iteratorNext(final Map.Entry entry) { - return entry; - } - - } - -} diff --git a/src/main/java/org/apache/commons/beanutils/locale/LocaleConvertUtilsBean.java b/src/main/java/org/apache/commons/beanutils/locale/LocaleConvertUtilsBean.java index 2c0e766ac..05fd1bc37 100644 --- a/src/main/java/org/apache/commons/beanutils/locale/LocaleConvertUtilsBean.java +++ b/src/main/java/org/apache/commons/beanutils/locale/LocaleConvertUtilsBean.java @@ -117,9 +117,7 @@ public static LocaleConvertUtilsBean getInstance() { * and then registers default locale converters. */ public LocaleConvertUtilsBean() { - mapConverters.setFast(false); deregister(); - mapConverters.setFast(true); } // --------------------------------------------------------- Properties @@ -370,15 +368,10 @@ public void register(final LocaleConverter converter, final Class clazz, fina * Remove any registered {@link LocaleConverter}. */ public void deregister() { - final FastHashMap defaultConverter = lookup(defaultLocale); - mapConverters.setFast(false); - mapConverters.clear(); mapConverters.put(defaultLocale, defaultConverter); - - mapConverters.setFast(true); } @@ -464,7 +457,6 @@ protected FastHashMap lookup(final Locale locale) { protected FastHashMap create(final Locale locale) { final FastHashMap converter = new DelegateFastHashMap(BeanUtils.createCache()); - converter.setFast(false); converter.put(BigDecimal.class, new BigDecimalLocaleConverter(locale, applyLocalized)); converter.put(BigInteger.class, new BigIntegerLocaleConverter(locale, applyLocalized)); @@ -497,8 +489,6 @@ protected FastHashMap create(final Locale locale) { new SqlTimestampLocaleConverter(locale, "yyyy-MM-dd HH:mm:ss.S") ); - converter.setFast(true); - return converter; } @@ -576,13 +566,5 @@ public int size() { public Collection values() { return map.values(); } - @Override - public boolean getFast() { - return BeanUtils.getCacheFast(map); - } - @Override - public void setFast(final boolean fast) { - BeanUtils.setCacheFast(map, fast); - } } }