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 @@
+0The 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.commonscommons-parent
- 41
+ 474.0.0commons-beanutilscommons-beanutils
- 1.9.3
+ 1.9.4-SNAPSHOTApache Commons BeanUtils2000
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.commonscommons-parent
- 41
+ 484.0.0commons-beanutilscommons-beanutils
- 1.9.3
+ 1.9.4-SNAPSHOTApache Commons BeanUtils2000
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
The KEYS
@@ -111,32 +111,32 @@ limitations under the License.
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.commonscommons-parent
- 48
+ 474.0.0commons-beanutils
@@ -50,6 +50,8 @@
3.0.08.21
+ 3.8
+
3.1.100.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 @@
The KEYS
@@ -116,12 +116,12 @@ limitations under the License.
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.6beanutils1.9.4
+
+ 1.10
+ sha256BEANUTILS12310460
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 extends K, ? extends V> 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 extends K, ? extends V> m) {
+ for (Map.Entry extends K, ? extends V> 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 extends K, ? extends V> 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 extends K, ? extends V> 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 extends K, ? extends V> 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 extends K, ? extends V> map) {
- return new WeakHashMap(map);
- }
-
- protected Map cloneMap(final Map extends K, ? extends V> 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 extends E> 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);
- }
}
}