001// Generated by delombok at Sat Feb 25 17:58:34 UTC 2023
002package io.cui.tools.reflect;
003
004import java.lang.reflect.Field;
005import java.lang.reflect.Member;
006import java.util.Optional;
007import io.cui.tools.lang.SecuritySupport;
008import io.cui.tools.logging.CuiLogger;
009import io.cui.tools.string.MoreStrings;
010import lombok.NonNull;
011// owolff: The warning is "Reflection should not be used to
012// increase accessibility of classes, methods, or fields "
013// What is actually the use-case of this type, therefore there
014// is nothing we can do
015/**
016 * Wrapper around a {@link Field} that handles implicitly the accessible flag for access.
017 *
018 * @author Oliver Wolff
019 */
020@SuppressWarnings("java:S3011")
021public class FieldWrapper {
022    private static final CuiLogger log = new CuiLogger(FieldWrapper.class);
023    @NonNull
024    private final Field field;
025    private final Class<?> declaringClass;
026
027    /**
028     * @param field must not be null
029     */
030    public FieldWrapper(Field field) {
031        this.field = field;
032        this.declaringClass = ((Member) field).getDeclaringClass();
033    }
034
035    /**
036     * Reads from the field determined by {@link #getField()}. It implicitly sets and resets the
037     * {@link Field#isAccessible()} flag.
038     *
039     * @param object to be read from
040     * @return an {@link Optional} on the given field value if applicable. May return
041     *         {@link Optional#empty()} for cases where:
042     *         <ul>
043     *         <li>Field value is {@code null}</li>
044     *         <li>Given Object is {@code null}</li>
045     *         <li>Given Object is improper type</li>
046     *         <li>an {@link IllegalAccessException} occurred while accessing</li>
047     *         </ul>
048     */
049    public Optional<Object> readValue(Object object) {
050        if (null == object) {
051            log.trace("No Object given, returning Optional#empty()");
052            return Optional.empty();
053        }
054        if (!declaringClass.isAssignableFrom(object.getClass())) {
055            log.trace("Given Object is improper type, returning Optional#empty()");
056            return Optional.empty();
057        }
058        var initialAccessible = field.canAccess(object);
059        log.trace("Reading from field \'{}\' with accessibleFlag=\'{}\' ", field, initialAccessible);
060        synchronized (field) {
061            if (!initialAccessible) {
062                log.trace("Explicitly setting accessible flag");
063                SecuritySupport.setAccessible(field, true);
064            }
065            try {
066                return Optional.ofNullable(field.get(object));
067            } catch (IllegalArgumentException | IllegalAccessException e) {
068                log.warn(e, "Reading from field \'{}\' with accessible=\'{}\' and parameter =\'{}\' could not complete", field, initialAccessible, object);
069                return Optional.empty();
070            } finally {
071                if (!initialAccessible) {
072                    log.trace("Resetting accessible flag");
073                    SecuritySupport.setAccessible(field, false);
074                }
075            }
076        }
077    }
078
079    /**
080     * Reads the value from the field in the given object. It implicitly sets and resets the
081     * {@link Field#isAccessible()} flag.
082     *
083     * @param fieldName to be read
084     * @param object to be read from
085     *
086     * @return the field value. {@link Optional#empty()} if the field cannot be read.
087     */
088    public static final Optional<Object> readValue(final String fieldName, final Object object) {
089        final var fieldProvider = from(object.getClass(), fieldName);
090        log.trace("FieldWrapper: {}", fieldProvider);
091        if (fieldProvider.isPresent()) {
092            var fieldWrapper = fieldProvider.get();
093            return fieldWrapper.readValue(object);
094        }
095        return Optional.empty();
096    }
097
098    /**
099     * Writes to the field determined by {@link #getField()}. It implicitly sets and resets the
100     * {@link Field#isAccessible()} flag.
101     *
102     * @param object to be written to, must not be null
103     * @param value to be written, may be null
104     *
105     * @throws NullPointerException in case object is {@code null}
106     * @throws IllegalArgumentException in case the value is not applicable to the field
107     * @throws IllegalStateException wrapping an {@link IllegalAccessException}
108     */
109    public void writeValue(@NonNull Object object, Object value) {
110        if (object == null) {
111            throw new java.lang.NullPointerException("object is marked non-null but is null");
112        }
113        var initialAccessible = field.canAccess(object);
114        log.trace("Writing to field \'{}\' with accessibleFlag=\'{}\' ", field, initialAccessible);
115        synchronized (field) {
116            if (!initialAccessible) {
117                log.trace("Explicitly setting accessible flag");
118                SecuritySupport.setAccessible(field, true);
119            }
120            try {
121                field.set(object, value);
122            } catch (IllegalAccessException e) {
123                var message = MoreStrings.lenientFormat("Writing to field \'{}\' with accessible=\'{}\' and parameter =\'{}\' could not complete", field, initialAccessible, object);
124                throw new IllegalStateException(message, e);
125            } finally {
126                if (!initialAccessible) {
127                    log.trace("Resetting accessible flag");
128                    SecuritySupport.setAccessible(field, false);
129                }
130            }
131        }
132    }
133
134    /**
135     * Factory Method for creating an {@link FieldWrapper} instance
136     *
137     * @param type must not be null
138     * @param fieldName must not be null
139     * @return a {@link FieldWrapper} if the {@link Field} can be determined,
140     *         {@link Optional#empty()} otherwise
141     */
142    public static final Optional<FieldWrapper> from(final Class<?> type, final String fieldName) {
143        var loaded = MoreReflection.accessField(type, fieldName);
144        if (loaded.isPresent()) {
145            return Optional.of(new FieldWrapper(loaded.get()));
146        }
147        return Optional.empty();
148    }
149
150    @NonNull
151    @java.lang.SuppressWarnings("all")
152    @lombok.Generated
153    public Field getField() {
154        return this.field;
155    }
156}