compat/maven-model-builder/src/main/java/org/apache/maven/model/interpolation/reflection/ReflectionValueExtractor.java [46:286]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public class ReflectionValueExtractor {
    private static final Object[] OBJECT_ARGS = new Object[0];

    /**
     * Use a WeakHashMap here, so the keys (Class objects) can be garbage collected.
     * This approach prevents permgen space overflows due to retention of discarded
     * classloaders.
     */
    private static final Map<Class<?>, WeakReference<ClassMap>> CLASS_MAPS = new WeakHashMap<>();

    static final int EOF = -1;

    static final char PROPERTY_START = '.';

    static final char INDEXED_START = '[';

    static final char INDEXED_END = ']';

    static final char MAPPED_START = '(';

    static final char MAPPED_END = ')';

    static class Tokenizer {
        final String expression;

        int idx;

        Tokenizer(String expression) {
            this.expression = expression;
        }

        public int peekChar() {
            return idx < expression.length() ? expression.charAt(idx) : EOF;
        }

        public int skipChar() {
            return idx < expression.length() ? expression.charAt(idx++) : EOF;
        }

        public String nextToken(char delimiter) {
            int start = idx;

            while (idx < expression.length() && delimiter != expression.charAt(idx)) {
                idx++;
            }

            // delimiter MUST be present
            if (idx <= start || idx >= expression.length()) {
                return null;
            }

            return expression.substring(start, idx++);
        }

        public String nextPropertyName() {
            final int start = idx;

            while (idx < expression.length() && Character.isJavaIdentifierPart(expression.charAt(idx))) {
                idx++;
            }

            // property name does not require delimiter
            if (idx <= start || idx > expression.length()) {
                return null;
            }

            return expression.substring(start, idx);
        }

        public int getPosition() {
            return idx < expression.length() ? idx : EOF;
        }

        // to make tokenizer look pretty in debugger
        @Override
        public String toString() {
            return idx < expression.length() ? expression.substring(idx) : "<EOF>";
        }
    }

    private ReflectionValueExtractor() {}

    /**
     * <p>The implementation supports indexed, nested and mapped properties.</p>
     * <ul>
     * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
     * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
     * pattern, i.e. "user.addresses[1].street"</li>
     * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern,
     *  i.e. "user.addresses(myAddress).street"</li>
     * </ul>
     *
     * @param expression not null expression
     * @param root       not null object
     * @return the object defined by the expression
     * @throws IntrospectionException if any
     */
    public static Object evaluate(@Nonnull String expression, @Nullable Object root) throws IntrospectionException {
        return evaluate(expression, root, true);
    }

    /**
     * <p>
     * The implementation supports indexed, nested and mapped properties.
     * </p>
     * <ul>
     * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
     * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
     * pattern, i.e. "user.addresses[1].street"</li>
     * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e.
     * "user.addresses(myAddress).street"</li>
     * </ul>
     *
     * @param expression not null expression
     * @param root not null object
     * @param trimRootToken trim root token yes/no.
     * @return the object defined by the expression
     * @throws IntrospectionException if any
     */
    public static Object evaluate(@Nonnull String expression, @Nullable Object root, boolean trimRootToken)
            throws IntrospectionException {
        Object value = root;

        // ----------------------------------------------------------------------
        // Walk the dots and retrieve the ultimate value desired from the
        // MavenProject instance.
        // ----------------------------------------------------------------------

        if (expression == null || expression.isEmpty() || !Character.isJavaIdentifierStart(expression.charAt(0))) {
            return null;
        }

        boolean hasDots = expression.indexOf(PROPERTY_START) >= 0;

        final Tokenizer tokenizer;
        if (trimRootToken && hasDots) {
            tokenizer = new Tokenizer(expression);
            tokenizer.nextPropertyName();
            if (tokenizer.getPosition() == EOF) {
                return null;
            }
        } else {
            tokenizer = new Tokenizer("." + expression);
        }

        int propertyPosition = tokenizer.getPosition();
        while (value != null && tokenizer.peekChar() != EOF) {
            switch (tokenizer.skipChar()) {
                case INDEXED_START:
                    value = getIndexedValue(
                            expression,
                            propertyPosition,
                            tokenizer.getPosition(),
                            value,
                            tokenizer.nextToken(INDEXED_END));
                    break;
                case MAPPED_START:
                    value = getMappedValue(
                            expression,
                            propertyPosition,
                            tokenizer.getPosition(),
                            value,
                            tokenizer.nextToken(MAPPED_END));
                    break;
                case PROPERTY_START:
                    propertyPosition = tokenizer.getPosition();
                    value = getPropertyValue(value, tokenizer.nextPropertyName());
                    break;
                default:
                    // could not parse expression
                    return null;
            }
        }

        if (value instanceof Optional<?> optional) {
            value = optional.orElse(null);
        }
        return value;
    }

    private static Object getMappedValue(
            final String expression, final int from, final int to, final Object value, final String key)
            throws IntrospectionException {
        if (value == null || key == null) {
            return null;
        }

        if (value instanceof Map map) {
            return map.get(key);
        }

        final String message = String.format(
                "The token '%s' at position '%d' refers to a java.util.Map, but the value "
                        + "seems is an instance of '%s'",
                expression.subSequence(from, to), from, value.getClass());

        throw new IntrospectionException(message);
    }

    private static Object getIndexedValue(
            final String expression, final int from, final int to, final Object value, final String indexStr)
            throws IntrospectionException {
        try {
            int index = Integer.parseInt(indexStr);

            if (value.getClass().isArray()) {
                return Array.get(value, index);
            }

            if (value instanceof List list) {
                return list.get(index);
            }
        } catch (NumberFormatException | IndexOutOfBoundsException e) {
            return null;
        }

        final String message = String.format(
                "The token '%s' at position '%d' refers to a java.util.List or an array, but the value "
                        + "seems is an instance of '%s'",
                expression.subSequence(from, to), from, value.getClass());

        throw new IntrospectionException(message);
    }

    private static Object getPropertyValue(Object value, String property) throws IntrospectionException {
        if (value == null || property == null || property.isEmpty()) {
            return null;
        }

        ClassMap classMap = getClassMap(value.getClass());
        String methodBase = Character.toTitleCase(property.charAt(0)) + property.substring(1);
        try {
            for (String prefix : Arrays.asList("get", "is", "to", "as")) {
                Method method = classMap.findMethod(prefix + methodBase);
                if (method != null) {
                    return method.invoke(value, OBJECT_ARGS);
                }
            }
            return null;
        } catch (InvocationTargetException e) {
            throw new IntrospectionException(e.getTargetException());
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



impl/maven-impl/src/main/java/org/apache/maven/impl/model/reflection/ReflectionValueExtractor.java [42:282]:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public class ReflectionValueExtractor {
    private static final Object[] OBJECT_ARGS = new Object[0];

    /**
     * Use a WeakHashMap here, so the keys (Class objects) can be garbage collected.
     * This approach prevents permgen space overflows due to retention of discarded
     * classloaders.
     */
    private static final Map<Class<?>, WeakReference<ClassMap>> CLASS_MAPS = new WeakHashMap<>();

    static final int EOF = -1;

    static final char PROPERTY_START = '.';

    static final char INDEXED_START = '[';

    static final char INDEXED_END = ']';

    static final char MAPPED_START = '(';

    static final char MAPPED_END = ')';

    static class Tokenizer {
        final String expression;

        int idx;

        Tokenizer(String expression) {
            this.expression = expression;
        }

        public int peekChar() {
            return idx < expression.length() ? expression.charAt(idx) : EOF;
        }

        public int skipChar() {
            return idx < expression.length() ? expression.charAt(idx++) : EOF;
        }

        public String nextToken(char delimiter) {
            int start = idx;

            while (idx < expression.length() && delimiter != expression.charAt(idx)) {
                idx++;
            }

            // delimiter MUST be present
            if (idx <= start || idx >= expression.length()) {
                return null;
            }

            return expression.substring(start, idx++);
        }

        public String nextPropertyName() {
            final int start = idx;

            while (idx < expression.length() && Character.isJavaIdentifierPart(expression.charAt(idx))) {
                idx++;
            }

            // property name does not require delimiter
            if (idx <= start || idx > expression.length()) {
                return null;
            }

            return expression.substring(start, idx);
        }

        public int getPosition() {
            return idx < expression.length() ? idx : EOF;
        }

        // to make tokenizer look pretty in debugger
        @Override
        public String toString() {
            return idx < expression.length() ? expression.substring(idx) : "<EOF>";
        }
    }

    private ReflectionValueExtractor() {}

    /**
     * <p>The implementation supports indexed, nested and mapped properties.</p>
     * <ul>
     * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
     * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
     * pattern, i.e. "user.addresses[1].street"</li>
     * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern,
     *  i.e. "user.addresses(myAddress).street"</li>
     * </ul>
     *
     * @param expression not null expression
     * @param root       not null object
     * @return the object defined by the expression
     * @throws IntrospectionException if any
     */
    public static Object evaluate(@Nonnull String expression, @Nullable Object root) throws IntrospectionException {
        return evaluate(expression, root, true);
    }

    /**
     * <p>
     * The implementation supports indexed, nested and mapped properties.
     * </p>
     * <ul>
     * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
     * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
     * pattern, i.e. "user.addresses[1].street"</li>
     * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e.
     * "user.addresses(myAddress).street"</li>
     * </ul>
     *
     * @param expression not null expression
     * @param root not null object
     * @param trimRootToken trim root token yes/no.
     * @return the object defined by the expression
     * @throws IntrospectionException if any
     */
    public static Object evaluate(@Nonnull String expression, @Nullable Object root, boolean trimRootToken)
            throws IntrospectionException {
        Object value = root;

        // ----------------------------------------------------------------------
        // Walk the dots and retrieve the ultimate value desired from the
        // MavenProject instance.
        // ----------------------------------------------------------------------

        if (expression == null || expression.isEmpty() || !Character.isJavaIdentifierStart(expression.charAt(0))) {
            return null;
        }

        boolean hasDots = expression.indexOf(PROPERTY_START) >= 0;

        final Tokenizer tokenizer;
        if (trimRootToken && hasDots) {
            tokenizer = new Tokenizer(expression);
            tokenizer.nextPropertyName();
            if (tokenizer.getPosition() == EOF) {
                return null;
            }
        } else {
            tokenizer = new Tokenizer("." + expression);
        }

        int propertyPosition = tokenizer.getPosition();
        while (value != null && tokenizer.peekChar() != EOF) {
            switch (tokenizer.skipChar()) {
                case INDEXED_START:
                    value = getIndexedValue(
                            expression,
                            propertyPosition,
                            tokenizer.getPosition(),
                            value,
                            tokenizer.nextToken(INDEXED_END));
                    break;
                case MAPPED_START:
                    value = getMappedValue(
                            expression,
                            propertyPosition,
                            tokenizer.getPosition(),
                            value,
                            tokenizer.nextToken(MAPPED_END));
                    break;
                case PROPERTY_START:
                    propertyPosition = tokenizer.getPosition();
                    value = getPropertyValue(value, tokenizer.nextPropertyName());
                    break;
                default:
                    // could not parse expression
                    return null;
            }
        }

        if (value instanceof Optional<?> optional) {
            value = optional.orElse(null);
        }
        return value;
    }

    private static Object getMappedValue(
            final String expression, final int from, final int to, final Object value, final String key)
            throws IntrospectionException {
        if (value == null || key == null) {
            return null;
        }

        if (value instanceof Map map) {
            return map.get(key);
        }

        final String message = String.format(
                "The token '%s' at position '%d' refers to a java.util.Map, but the value "
                        + "seems is an instance of '%s'",
                expression.subSequence(from, to), from, value.getClass());

        throw new IntrospectionException(message);
    }

    private static Object getIndexedValue(
            final String expression, final int from, final int to, final Object value, final String indexStr)
            throws IntrospectionException {
        try {
            int index = Integer.parseInt(indexStr);

            if (value.getClass().isArray()) {
                return Array.get(value, index);
            }

            if (value instanceof List list) {
                return list.get(index);
            }
        } catch (NumberFormatException | IndexOutOfBoundsException e) {
            return null;
        }

        final String message = String.format(
                "The token '%s' at position '%d' refers to a java.util.List or an array, but the value "
                        + "seems is an instance of '%s'",
                expression.subSequence(from, to), from, value.getClass());

        throw new IntrospectionException(message);
    }

    private static Object getPropertyValue(Object value, String property) throws IntrospectionException {
        if (value == null || property == null || property.isEmpty()) {
            return null;
        }

        ClassMap classMap = getClassMap(value.getClass());
        String methodBase = Character.toTitleCase(property.charAt(0)) + property.substring(1);
        try {
            for (String prefix : Arrays.asList("get", "is", "to", "as")) {
                Method method = classMap.findMethod(prefix + methodBase);
                if (method != null) {
                    return method.invoke(value, OBJECT_ARGS);
                }
            }
            return null;
        } catch (InvocationTargetException e) {
            throw new IntrospectionException(e.getTargetException());
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -



