Skip to content

Commit 751fc70

Browse files
committed
A general solution for the org.apache.commons.lang3.builder package to
make object accessibility optional
1 parent 17d776d commit 751fc70

36 files changed

Lines changed: 933 additions & 194 deletions
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.commons.lang3.builder;
19+
20+
import java.lang.reflect.AccessibleObject;
21+
import java.lang.reflect.Field;
22+
import java.util.function.Supplier;
23+
24+
import org.apache.commons.lang3.SystemProperties;
25+
26+
/**
27+
* Abstracts reflection access for reflection classes in this package.
28+
*
29+
* @since 3.21.0
30+
*/
31+
public abstract class AbstractReflection {
32+
33+
/**
34+
* Builds a subclass.
35+
*
36+
* @param <B> This Builder type.
37+
*/
38+
public abstract static class AbstractBuilder<B extends AbstractBuilder<B>> implements Supplier<AbstractReflection> {
39+
40+
/**
41+
* Whether to set the the {@code accessible} flag.
42+
*/
43+
private boolean accessibleFlag = accessibleFlag();
44+
45+
AbstractBuilder() {
46+
// Empty.
47+
}
48+
49+
/**
50+
* Returns {@code this} instance typed as a subclass.
51+
*
52+
* @return {@code this} instance typed as a subclass.
53+
*/
54+
@SuppressWarnings("unchecked")
55+
protected B asThis() {
56+
return (B) this;
57+
}
58+
59+
/**
60+
* Sets the forceAccessible flag, defaults to {@code forceAccessible()} which defaults to true.
61+
* <p>
62+
* In general, controls whether the instances built by this builder will force the accessible flag for reflection.
63+
* </p>
64+
* <p>
65+
* See subclassses for specific behavior.
66+
* </p>
67+
*
68+
* @param forceAccessible Whether to force accessibility by calling {@link AccessibleObject#setAccessible(boolean)}.
69+
* @return {@code this} instance.
70+
*/
71+
public B setForceAccessible(final boolean forceAccessible) {
72+
this.accessibleFlag = forceAccessible;
73+
return asThis();
74+
}
75+
}
76+
77+
/**
78+
* Tests whether the system property {@code "AbstractReflection.forceAccessible"} is set to true.
79+
*
80+
* <p>
81+
* If the property is not set, return true.
82+
* </p>
83+
*
84+
* @return whether the system property {@code "AbstractReflection.forceAccessible"} is set to true with true as the default.
85+
*/
86+
static boolean accessibleFlag() {
87+
return SystemProperties.getBoolean(AbstractReflection.class, "forceAccessible", () -> true);
88+
}
89+
90+
/**
91+
* If {@code forceAccessible} flag is true, each field in the given array is made accessible via {@link AccessibleObject#setAccessible(boolean)} only if a
92+
* field is not already accessible.
93+
*
94+
* @param accessibleFlag Whether to call {@link AccessibleObject#setAccessible(boolean)} if a field is not already accessible.
95+
* @param fields The fields to set.
96+
* @throws SecurityException Thrown if {@code forceAccessible} flag is true and the request is denied.
97+
* @see SecurityManager#checkPermission
98+
* @see RuntimePermission
99+
*/
100+
static void setAccessible(final boolean accessibleFlag, final Field[] fields) {
101+
if (accessibleFlag) {
102+
for (final Field field : fields) {
103+
// Test to avoid the permission check if there is a security manager.
104+
if (!field.isAccessible()) {
105+
field.setAccessible(true);
106+
}
107+
}
108+
}
109+
}
110+
111+
/**
112+
* Whether to set the the {@code accessible} flag.
113+
*/
114+
private final boolean accessibleFlag;
115+
116+
117+
/**
118+
* Constructs a new instance.
119+
*
120+
* @param <T> The type to build.
121+
* @param builder The builder.
122+
*/
123+
<T extends AbstractBuilder<T>> AbstractReflection(final AbstractBuilder<T> builder) {
124+
this.accessibleFlag = builder.accessibleFlag;
125+
}
126+
127+
/**
128+
* Tests whether fields should be made accessible.
129+
*
130+
* @return whether fields should be made accessible.
131+
*/
132+
protected boolean isAccessible() {
133+
return accessibleFlag;
134+
}
135+
136+
void setAccessible(final Field[] fields) {
137+
setAccessible(accessibleFlag, fields);
138+
}
139+
}

src/main/java/org/apache/commons/lang3/builder/CompareToBuilder.java

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
*/
1717
package org.apache.commons.lang3.builder;
1818

19-
import java.lang.reflect.AccessibleObject;
2019
import java.lang.reflect.Field;
2120
import java.lang.reflect.Modifier;
2221
import java.util.Collection;
@@ -94,7 +93,35 @@
9493
* @see HashCodeBuilder
9594
* @since 1.0
9695
*/
97-
public class CompareToBuilder implements Builder<Integer> {
96+
public class CompareToBuilder extends AbstractReflection implements Builder<Integer> {
97+
98+
/**
99+
* Builds instances of CompareToBuilder.
100+
*/
101+
public static class Builder extends AbstractBuilder<Builder> {
102+
103+
/**
104+
* Constructs a new Builder instance.
105+
*/
106+
private Builder() {
107+
// empty
108+
}
109+
110+
@Override
111+
public CompareToBuilder get() {
112+
return new CompareToBuilder(this);
113+
}
114+
115+
}
116+
117+
/**
118+
* Constructs a new Builder.
119+
*
120+
* @return a new Builder.
121+
*/
122+
public static Builder builder() {
123+
return new Builder();
124+
}
98125

99126
/**
100127
* Appends to {@code builder} the comparison of {@code lhs}
@@ -106,23 +133,27 @@ public class CompareToBuilder implements Builder<Integer> {
106133
* @param builder {@link CompareToBuilder} to append to
107134
* @param useTransients whether to compare transient fields
108135
* @param excludeFields fields to exclude
136+
* @param forceAccessible Whether to set fields' accessible flags
109137
*/
110138
private static void reflectionAppend(
111139
final Object lhs,
112140
final Object rhs,
113141
final Class<?> clazz,
114142
final CompareToBuilder builder,
115143
final boolean useTransients,
116-
final String[] excludeFields) {
144+
final String[] excludeFields,
145+
final boolean forceAccessible) {
117146

118147
final Field[] fields = clazz.getDeclaredFields();
119-
AccessibleObject.setAccessible(fields, true);
148+
setAccessible(forceAccessible, fields);
120149
for (int i = 0; i < fields.length && builder.comparison == 0; i++) {
121150
final Field field = fields[i];
122-
if (!ArrayUtils.contains(excludeFields, field.getName())
123-
&& !field.getName().contains("$")
151+
final String name = field.getName();
152+
if (!ArrayUtils.contains(excludeFields, name)
153+
&& !name.contains("$")
124154
&& (useTransients || !Modifier.isTransient(field.getModifiers()))
125-
&& !Modifier.isStatic(field.getModifiers())) {
155+
&& !Modifier.isStatic(field.getModifiers())
156+
&& field.isAccessible()) {
126157
// IllegalAccessException can't happen. Would get a Security exception instead.
127158
// Throw a runtime exception in case the impossible happens.
128159
builder.append(Reflection.getUnchecked(field, lhs), Reflection.getUnchecked(field, rhs));
@@ -230,22 +261,20 @@ public static int reflectionCompare(
230261
final boolean compareTransients,
231262
final Class<?> reflectUpToClass,
232263
final String... excludeFields) {
233-
234264
if (lhs == rhs) {
235265
return 0;
236266
}
237267
Objects.requireNonNull(lhs, "lhs");
238268
Objects.requireNonNull(rhs, "rhs");
239-
240269
Class<?> lhsClazz = lhs.getClass();
241270
if (!lhsClazz.isInstance(rhs)) {
242271
throw new ClassCastException();
243272
}
244273
final CompareToBuilder compareToBuilder = new CompareToBuilder();
245-
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields);
274+
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields, AbstractReflection.accessibleFlag());
246275
while (lhsClazz.getSuperclass() != null && lhsClazz != reflectUpToClass) {
247276
lhsClazz = lhsClazz.getSuperclass();
248-
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields);
277+
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields, AbstractReflection.accessibleFlag());
249278
}
250279
return compareToBuilder.toComparison();
251280
}
@@ -329,9 +358,14 @@ public static int reflectionCompare(final Object lhs, final Object rhs, final St
329358
* {@link #toComparison} to get the result.</p>
330359
*/
331360
public CompareToBuilder() {
361+
super(builder());
332362
comparison = 0;
333363
}
334364

365+
private CompareToBuilder(final Builder builder) {
366+
super(builder);
367+
}
368+
335369
/**
336370
* Appends to the {@code builder} the comparison of
337371
* two {@code booleans}s.

src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
*/
1717
package org.apache.commons.lang3.builder;
1818

19-
import java.lang.reflect.AccessibleObject;
2019
import java.lang.reflect.Field;
2120
import java.lang.reflect.Modifier;
2221
import java.util.ArrayList;
@@ -86,7 +85,35 @@
8685
*
8786
* @since 1.0
8887
*/
89-
public class EqualsBuilder implements Builder<Boolean> {
88+
public class EqualsBuilder extends AbstractReflection implements org.apache.commons.lang3.builder.Builder<Boolean> {
89+
90+
/**
91+
* Builds instances of CompareToBuilder.
92+
*/
93+
public static class Builder extends AbstractBuilder<Builder> {
94+
95+
/**
96+
* Constructs a new Builder instance.
97+
*/
98+
private Builder() {
99+
// empty
100+
}
101+
102+
@Override
103+
public EqualsBuilder get() {
104+
return new EqualsBuilder(this);
105+
}
106+
107+
}
108+
109+
/**
110+
* Constructs a new Builder.
111+
*
112+
* @return a new Builder.
113+
*/
114+
public static Builder builder() {
115+
return new Builder();
116+
}
90117

91118
/**
92119
* A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops.
@@ -371,11 +398,16 @@ private static void unregister(final Object lhs, final Object rhs) {
371398
* @see Object#equals(Object)
372399
*/
373400
public EqualsBuilder() {
401+
super(builder());
374402
// set up default classes to bypass reflection for
375403
bypassReflectionClasses = new ArrayList<>(1);
376404
bypassReflectionClasses.add(String.class); //hashCode field being lazy but not transient
377405
}
378406

407+
private EqualsBuilder(Builder builder) {
408+
super(builder);
409+
}
410+
379411
/**
380412
* Test if two {@code booleans}s are equal.
381413
*
@@ -1003,7 +1035,7 @@ private void reflectionAppend(
10031035
try {
10041036
register(lhs, rhs);
10051037
final Field[] fields = clazz.getDeclaredFields();
1006-
AccessibleObject.setAccessible(fields, true);
1038+
setAccessible(fields);
10071039
for (int i = 0; i < fields.length && isEquals; i++) {
10081040
final Field field = fields[i];
10091041
if (!ArrayUtils.contains(excludeFields, field.getName())

0 commit comments

Comments
 (0)