AbstractReflection.java

/*
 * 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.Set;
import java.util.function.Supplier;

import org.apache.commons.lang3.SystemProperties;
import org.apache.commons.lang3.tuple.Pair;

/**
 * Abstracts reflection access for reflection-based classes in this package.
 * <p>
 * See {@link AbstractBuilder#setForceAccessible(boolean)} for details.
 * </p>
 *
 * @since 3.21.0
 * @see AbstractBuilder#setForceAccessible(boolean)
 * @see AccessibleObject#setAccessible(boolean)
 */
public abstract class AbstractReflection {

    /**
     * Builds an instance of a subclass of {@link AbstractReflection}.
     *
     * @param <B> An AbstractBuilder subclass.
     */
    public abstract static class AbstractBuilder<B extends AbstractBuilder<B>> implements Supplier<AbstractReflection> {

        /**
         * Whether the {@link AbstractReflection} subclass will call {@link AccessibleObject#setAccessible(boolean) AccessibleObject#setAccessible(true)} on
         * inaccessible fields.
         */
        private boolean forceAccessible = getForceAccessible();

        /**
         * Constructs a new instance for a subclass.
         */
        AbstractBuilder() {
            // Empty.
        }

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

        /**
         * Whether the {@link AbstractReflection} subclass will call {@link AccessibleObject#setAccessible(boolean) AccessibleObject#setAccessible(true)} on
         * inaccessible fields.
         * <p>
         * In general, controls whether the instances built by this builder will force the accessible flag for reflection.
         * </p>
         * <p>
         * Defaults to {@code getForceAccessible()}, which defaults to true for compatibility.
         * </p>
         * <p>
         * This default is read from the system property {@code "AbstractReflection.forceAccessible"}, which defaults to true for compatibility.
         * </p>
         * <p>
         * The parsing rules are as {@link Boolean#parseBoolean(String)}.
         * </p>
         * <p>
         * See subclassses for specific behavior.
         * </p>
         *
         * @param forceAccessible Whether to force accessibility by calling {@link AccessibleObject#setAccessible(boolean)
         *                        AccessibleObject#setAccessible(true)}.
         * @return {@code this} instance.
         * @see AccessibleObject#setAccessible(boolean)
         */
        public B setForceAccessible(final boolean forceAccessible) {
            this.forceAccessible = forceAccessible;
            return asThis();
        }
    }

    /**
     * Tests whether the system property {@code "AbstractReflection.forceAccessible"} is set to true.
     * <p>
     * The parsing rules are as {@link Boolean#parseBoolean(String)}.
     * </p>
     * <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.
     * @see Boolean#parseBoolean(String)
     */
    static boolean getForceAccessible() {
        return SystemProperties.getBoolean(AbstractReflection.class, "forceAccessible", () -> true);
    }

    static boolean isRegistered(final Object lhs, final Object rhs, final Set<Pair<IDKey, IDKey>> registry) {
        final Pair<IDKey, IDKey> pair = toRegisterPair(lhs, rhs);
        final Pair<IDKey, IDKey> swappedPair = Pair.of(pair.getRight(), pair.getLeft());
        return registry != null && (registry.contains(pair) || registry.contains(swappedPair));
    }

    static void register(final Object lhs, final Object rhs, final Set<Pair<IDKey, IDKey>> registry) {
        registry.add(toRegisterPair(lhs, rhs));
    }

    /**
     * If {@code forceAccessible} flag is true, then the field is made accessible by calling {@link AccessibleObject#setAccessible(boolean)
     * AccessibleObject#setAccessible(true)} but <em>only</em> if a field is not already accessible.
     *
     * @param forceAccessible Whether to call {@link AccessibleObject#setAccessible(boolean)} if a field is not already accessible.
     * @param field          The field to set.
     * @return true if the field is accessible, false otherwise.
     * @throws SecurityException Thrown if {@code forceAccessible} flag is true and the request is denied.
     * @see AccessibleObject#setAccessible(boolean)
     * @see SecurityManager#checkPermission
     */
    static boolean setAccessible(final boolean forceAccessible, final Field field) {
        return !field.isAccessible() && forceAccessible && setAccessibleTrue(field);
    }

    /**
     * Sets the field as accessible by calling {@link AccessibleObject#setAccessible(boolean) AccessibleObject#setAccessible(true)} but <em>only</em> if a field
     * is not already accessible.
     *
     * @param field The field to set, may be null.
     * @return true if the field is accessible, false otherwise.
     * @throws SecurityException Thrown if {@code forceAccessible} flag is true and the request is denied.
     * @see AccessibleObject#setAccessible(boolean)
     * @see SecurityManager#checkPermission
     */
    private static boolean setAccessibleTrue(final Field field) {
        if (field != null) {
            // Test isAccessible() to avoid the permission check.
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            return field.isAccessible();
        }
        return false;
    }

    /**
     * Converters value pair into a register pair.
     *
     * @param lhs {@code this} object.
     * @param rhs the other object.
     * @return the pair.
     */
    static Pair<IDKey, IDKey> toRegisterPair(final Object lhs, final Object rhs) {
        return Pair.of(new IDKey(lhs), new IDKey(rhs));
    }

    static void unregister(final Object lhs, final Object rhs, final Set<Pair<IDKey, IDKey>> registry, final ThreadLocal<Set<Pair<IDKey, IDKey>>> registryTL) {
        registry.remove(toRegisterPair(lhs, rhs));
        if (registry.isEmpty()) {
            registryTL.remove();
        }
    }

    /**
     * Whether to call {@link AccessibleObject#setAccessible(boolean) AccessibleObject#setAccessible(true)} on inaccessible fields.
     */
    private final boolean forceAccessible;

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

    /**
     * Tests whether fields should be made accessible with {@link AccessibleObject#setAccessible(boolean)}.
     *
     * @return whether fields should be made accessible with {@link AccessibleObject#setAccessible(boolean)}.
     */
    protected boolean isForceAccessible() {
        return forceAccessible;
    }

    /**
     * If {@code forceAccessible} flag is true, each field in the given array is made accessible by calling {@link AccessibleObject#setAccessible(boolean)
     * AccessibleObject#setAccessible(true)} but <em>only</em> if a field is not already accessible.
     *
     * @param field The fields to set.
     * @throws SecurityException Thrown if {@code forceAccessible} flag is true and the request is denied.
     * @return true if the field is accessible, false otherwise.
     * @see AccessibleObject#setAccessible(boolean)
     * @see SecurityManager#checkPermission
     */
    boolean setAccessible(final Field field) {
        return setAccessible(isForceAccessible(), field);
    }
}