Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions src/main/java/org/apache/commons/lang3/builder/AbstractReflection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* 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.lang3.builder;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.util.function.Supplier;

import org.apache.commons.lang3.SystemProperties;

/**
* Abstracts reflection access for reflection classes in this package.
*
* @since 3.21.0
*/
public abstract class AbstractReflection {

/**
* Builds a subclass.
*
* @param <B> This Builder type.
*/
public abstract static class AbstractBuilder<B extends AbstractBuilder<B>> implements Supplier<AbstractReflection> {

/**
* Whether to set the the {@code accessible} flag.
*/
private boolean accessibleFlag = accessibleFlag();

AbstractBuilder() {
// Empty.
}

/**
* Returns {@code this} instance typed as a subclass.
*
* @return {@code this} instance typed as a subclass.
*/
@SuppressWarnings("unchecked")
protected B asThis() {
return (B) this;
}

/**
* Sets the forceAccessible flag, defaults to {@code forceAccessible()} which defaults to true.
* <p>
* In general, controls whether the instances built by this builder will force the accessible flag for reflection.
* </p>
* <p>
* See subclassses for specific behavior.
* </p>
*
* @param forceAccessible Whether to force accessibility by calling {@link AccessibleObject#setAccessible(boolean)}.
* @return {@code this} instance.
*/
public B setForceAccessible(final boolean forceAccessible) {
this.accessibleFlag = forceAccessible;
return asThis();
}
}

/**
* Tests whether the system property {@code "AbstractReflection.forceAccessible"} is set to true.
*
* <p>
* If the property is not set, return true.
* </p>
*
* @return whether the system property {@code "AbstractReflection.forceAccessible"} is set to true with true as the default.
*/
static boolean accessibleFlag() {
return SystemProperties.getBoolean(AbstractReflection.class, "forceAccessible", () -> true);
}

/**
* If {@code forceAccessible} flag is true, each field in the given array is made accessible via {@link AccessibleObject#setAccessible(boolean)} only if a
* field is not already accessible.
*
* @param accessibleFlag Whether to call {@link AccessibleObject#setAccessible(boolean)} if a field is not already accessible.
* @param fields The fields to set.
* @throws SecurityException Thrown if {@code forceAccessible} flag is true and the request is denied.
* @see SecurityManager#checkPermission
* @see RuntimePermission
*/
static void setAccessible(final boolean accessibleFlag, final Field[] fields) {
if (accessibleFlag) {
for (final Field field : fields) {
// Test to avoid the permission check if there is a security manager.
if (!field.isAccessible()) {
field.setAccessible(true);
}
}
}
}

/**
* Whether to set the the {@code accessible} flag.
*/
private final boolean accessibleFlag;


/**
* Constructs a new instance.
*
* @param <T> The type to build.
* @param builder The builder.
*/
<T extends AbstractBuilder<T>> AbstractReflection(final AbstractBuilder<T> builder) {
this.accessibleFlag = builder.accessibleFlag;
}

/**
* Tests whether fields should be made accessible.
*
* @return whether fields should be made accessible.
*/
protected boolean isAccessible() {
return accessibleFlag;
}

void setAccessible(final Field[] fields) {
setAccessible(accessibleFlag, fields);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package org.apache.commons.lang3.builder;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
Expand Down Expand Up @@ -94,7 +93,35 @@
* @see HashCodeBuilder
* @since 1.0
*/
public class CompareToBuilder implements Builder<Integer> {
public class CompareToBuilder extends AbstractReflection implements Builder<Integer> {

/**
* Builds instances of CompareToBuilder.
*/
public static class Builder extends AbstractBuilder<Builder> {

/**
* Constructs a new Builder instance.
*/
private Builder() {
// empty
}

@Override
public CompareToBuilder get() {
return new CompareToBuilder(this);
}

}

/**
* Constructs a new Builder.
*
* @return a new Builder.
*/
public static Builder builder() {
return new Builder();
}

/**
* Appends to {@code builder} the comparison of {@code lhs}
Expand All @@ -106,23 +133,27 @@ public class CompareToBuilder implements Builder<Integer> {
* @param builder {@link CompareToBuilder} to append to
* @param useTransients whether to compare transient fields
* @param excludeFields fields to exclude
* @param forceAccessible Whether to set fields' accessible flags
*/
private static void reflectionAppend(
final Object lhs,
final Object rhs,
final Class<?> clazz,
final CompareToBuilder builder,
final boolean useTransients,
final String[] excludeFields) {
final String[] excludeFields,
final boolean forceAccessible) {

final Field[] fields = clazz.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
setAccessible(forceAccessible, fields);
for (int i = 0; i < fields.length && builder.comparison == 0; i++) {
final Field field = fields[i];
if (!ArrayUtils.contains(excludeFields, field.getName())
&& !field.getName().contains("$")
final String name = field.getName();
if (!ArrayUtils.contains(excludeFields, name)
&& !name.contains("$")
&& (useTransients || !Modifier.isTransient(field.getModifiers()))
&& !Modifier.isStatic(field.getModifiers())) {
&& !Modifier.isStatic(field.getModifiers())
&& field.isAccessible()) {
// IllegalAccessException can't happen. Would get a Security exception instead.
// Throw a runtime exception in case the impossible happens.
builder.append(Reflection.getUnchecked(field, lhs), Reflection.getUnchecked(field, rhs));
Expand Down Expand Up @@ -230,22 +261,20 @@ public static int reflectionCompare(
final boolean compareTransients,
final Class<?> reflectUpToClass,
final String... excludeFields) {

if (lhs == rhs) {
return 0;
}
Objects.requireNonNull(lhs, "lhs");
Objects.requireNonNull(rhs, "rhs");

Class<?> lhsClazz = lhs.getClass();
if (!lhsClazz.isInstance(rhs)) {
throw new ClassCastException();
}
final CompareToBuilder compareToBuilder = new CompareToBuilder();
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields);
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields, AbstractReflection.accessibleFlag());
while (lhsClazz.getSuperclass() != null && lhsClazz != reflectUpToClass) {
lhsClazz = lhsClazz.getSuperclass();
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields);
reflectionAppend(lhs, rhs, lhsClazz, compareToBuilder, compareTransients, excludeFields, AbstractReflection.accessibleFlag());
}
return compareToBuilder.toComparison();
}
Expand Down Expand Up @@ -329,9 +358,14 @@ public static int reflectionCompare(final Object lhs, final Object rhs, final St
* {@link #toComparison} to get the result.</p>
*/
public CompareToBuilder() {
super(builder());
comparison = 0;
}

private CompareToBuilder(final Builder builder) {
super(builder);
}

/**
* Appends to the {@code builder} the comparison of
* two {@code booleans}s.
Expand Down
38 changes: 35 additions & 3 deletions src/main/java/org/apache/commons/lang3/builder/EqualsBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package org.apache.commons.lang3.builder;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
Expand Down Expand Up @@ -86,7 +85,35 @@
*
* @since 1.0
*/
public class EqualsBuilder implements Builder<Boolean> {
public class EqualsBuilder extends AbstractReflection implements org.apache.commons.lang3.builder.Builder<Boolean> {

/**
* Builds instances of CompareToBuilder.
*/
public static class Builder extends AbstractBuilder<Builder> {

/**
* Constructs a new Builder instance.
*/
private Builder() {
// empty
}

@Override
public EqualsBuilder get() {
return new EqualsBuilder(this);
}

}

/**
* Constructs a new Builder.
*
* @return a new Builder.
*/
public static Builder builder() {
return new Builder();
}

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

private EqualsBuilder(Builder builder) {
super(builder);
}

/**
* Test if two {@code booleans}s are equal.
*
Expand Down Expand Up @@ -1003,7 +1035,7 @@ private void reflectionAppend(
try {
register(lhs, rhs);
final Field[] fields = clazz.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
setAccessible(fields);
for (int i = 0; i < fields.length && isEquals; i++) {
final Field field = fields[i];
if (!ArrayUtils.contains(excludeFields, field.getName())
Expand Down
Loading