All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.utils.ReflectionMap Maven / Gradle / Ivy

// ***************************************************************************************************************************
// * 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.juneau.utils;

import static java.lang.Character.*;
import static org.apache.juneau.collections.JsonMap.*;
import static org.apache.juneau.common.internal.StringUtils.*;
import static org.apache.juneau.internal.CollectionUtils.*;

import java.lang.reflect.*;
import java.util.*;
import java.util.function.*;

import org.apache.juneau.*;

/**
 * Allows arbitrary objects to be mapped to classes and methods base on class/method name keys.
 *
 * 

* The valid pattern matches are: *

    *
  • Classes: *
      *
    • Fully qualified: *
        *
      • "com.foo.MyClass" *
      *
    • Fully qualified inner class: *
        *
      • "com.foo.MyClass$Inner1$Inner2" *
      *
    • Simple: *
        *
      • "MyClass" *
      *
    • Simple inner: *
        *
      • "MyClass$Inner1$Inner2" *
      • "Inner1$Inner2" *
      • "Inner2" *
      *
    *
  • Methods: *
      *
    • Fully qualified with args: *
        *
      • "com.foo.MyClass.myMethod(String,int)" *
      • "com.foo.MyClass.myMethod(java.lang.String,int)" *
      • "com.foo.MyClass.myMethod()" *
      *
    • Fully qualified: *
        *
      • "com.foo.MyClass.myMethod" *
      *
    • Simple with args: *
        *
      • "MyClass.myMethod(String,int)" *
      • "MyClass.myMethod(java.lang.String,int)" *
      • "MyClass.myMethod()" *
      *
    • Simple: *
        *
      • "MyClass.myMethod" *
      *
    • Simple inner class: *
        *
      • "MyClass$Inner1$Inner2.myMethod" *
      • "Inner1$Inner2.myMethod" *
      • "Inner2.myMethod" *
      *
    *
  • Fields: *
      *
    • Fully qualified: *
        *
      • "com.foo.MyClass.myField" *
      *
    • Simple: *
        *
      • "MyClass.myField" *
      *
    • Simple inner class: *
        *
      • "MyClass$Inner1$Inner2.myField" *
      • "Inner1$Inner2.myField" *
      • "Inner2.myField" *
      *
    *
  • Constructors: *
      *
    • Fully qualified with args: *
        *
      • "com.foo.MyClass(String,int)" *
      • "com.foo.MyClass(java.lang.String,int)" *
      • "com.foo.MyClass()" *
      *
    • Simple with args: *
        *
      • "MyClass(String,int)" *
      • "MyClass(java.lang.String,int)" *
      • "MyClass()" *
      *
    • Simple inner class: *
        *
      • "MyClass$Inner1$Inner2()" *
      • "Inner1$Inner2()" *
      • "Inner2()" *
      *
    *
  • A comma-delimited list of anything on this list. *
* *
See Also:
    *
* * @param The type of object in this map. */ public class ReflectionMap { //----------------------------------------------------------------------------------------------------------------- // Static //----------------------------------------------------------------------------------------------------------------- /** * Static builder creator. * * @param The type of object in this map. * @param c The type of object in this map. * @return A new instance of this object. */ public static Builder create(Class c) { return new Builder<>(); } //----------------------------------------------------------------------------------------------------------------- // Builder //----------------------------------------------------------------------------------------------------------------- /** * Builder class. * @param The type of object in this map. */ public static class Builder { final List> classEntries; final List> methodEntries; final List> fieldEntries; final List> constructorEntries; /** * Constructor. */ protected Builder() { classEntries = list(); methodEntries = list(); fieldEntries = list(); constructorEntries = list(); } /** * Copy constructor. * * @param copyFrom The builder being copied. */ protected Builder(Builder copyFrom) { classEntries = copyOf(copyFrom.classEntries); methodEntries = copyOf(copyFrom.methodEntries); fieldEntries = copyOf(copyFrom.fieldEntries); constructorEntries = copyOf(copyFrom.constructorEntries); } /** * Adds a mapping to this builder. * * @param key * The mapping key. *
Can be any of the following: *
    *
  • Full class name (e.g. "com.foo.MyClass"). *
  • Simple class name (e.g. "MyClass"). *
  • All classes (e.g. "*"). *
  • Full method name (e.g. "com.foo.MyClass.myMethod"). *
  • Simple method name (e.g. "MyClass.myMethod"). *
  • A comma-delimited list of anything on this list. *
* @param value The value for this mapping. * @return This object. */ public Builder append(String key, V value) { if (isEmpty(key)) throw new BasicRuntimeException("Invalid reflection signature: [{0}]", key); try { splitNames(key, k -> { if (k.endsWith(")")) { int i = k.substring(0, k.indexOf('(')).lastIndexOf('.'); if (i == -1 || isUpperCase(k.charAt(i+1))) { constructorEntries.add(new ConstructorEntry<>(k, value)); } else { methodEntries.add(new MethodEntry<>(k, value)); } } else { int i = k.lastIndexOf('.'); if (i == -1) { classEntries.add(new ClassEntry<>(k, value)); } else if (isUpperCase(k.charAt(i+1))) { classEntries.add(new ClassEntry<>(k, value)); fieldEntries.add(new FieldEntry<>(k, value)); } else { methodEntries.add(new MethodEntry<>(k, value)); fieldEntries.add(new FieldEntry<>(k, value)); } } }); } catch (IndexOutOfBoundsException e) { throw new BasicRuntimeException("Invalid reflection signature: [{0}]", key); } return this; } /** * Create new instance of {@link ReflectionMap} based on the contents of this builder. * * @return A new {@link ReflectionMap} object. */ public ReflectionMap build() { return new ReflectionMap<>(this); } /** * Creates a copy of this builder. * * @return A copy of this builder. */ public Builder copy() { return new Builder<>(this); } } //----------------------------------------------------------------------------------------------------------------- // Instance //----------------------------------------------------------------------------------------------------------------- final ClassEntry[] classEntries; final MethodEntry[] methodEntries; final FieldEntry[] fieldEntries; final ConstructorEntry[] constructorEntries; /** * Constructor. * * @param b Initializer object. */ protected ReflectionMap(Builder b) { this.classEntries = b.classEntries.toArray(new ClassEntry[b.classEntries.size()]); this.methodEntries = b.methodEntries.toArray(new MethodEntry[b.methodEntries.size()]); this.fieldEntries = b.fieldEntries.toArray(new FieldEntry[b.fieldEntries.size()]); this.constructorEntries = b.constructorEntries.toArray(new ConstructorEntry[b.constructorEntries.size()]); } static void splitNames(String key, Consumer consumer) { if (key.indexOf(',') == -1) { consumer.accept(key); } else { int m = 0; boolean escaped = false; for (int i = 0; i < key.length(); i++) { char c = key.charAt(i); if (c == '(') escaped = true; else if (c == ')') escaped = false; else if (c == ',' && ! escaped) { consumer.accept(key.substring(m, i).trim()); m = i+1; } } consumer.accept(key.substring(m).trim()); } } /** * Finds first value in this map that matches the specified class. * * @param c The class to test for. * @param ofType Only return objects of the specified type. * @return The matching object. Never null. */ public Optional find(Class c, Class ofType) { for (ClassEntry e : classEntries) if (e.matches(c)) if (ofType == null || ofType.isInstance(e.value)) return optional(e.value); return empty(); } /** * Finds first value in this map that matches the specified class. * * @param c The class to test for. * @return The matching object. Never null. */ public Optional find(Class c) { return find(c, null); } /** * Finds all values in this map that matches the specified class. * * @param c The class to test for. * @param ofType Only return objects of the specified type. * @return A modifiable list of matching values. Never null. */ public List findAll(Class c, Class ofType) { List list = null; for (ClassEntry e : classEntries) if (e.matches(c) && e.value != null) if (ofType == null || ofType.isInstance(e.value)) list = lazyAdd(list, e.value); return lazyList(list); } /** * Finds all values in this map that matches the specified class. * * @param c The class to test for. * @return A modifiable list of matching values. Never null. */ public List findAll(Class c) { List list = null; for (ClassEntry e : classEntries) if (e.matches(c) && e.value != null) list = lazyAdd(list, e.value); return lazyList(list); } /** * Finds all values in this map that matches the specified class. * * @param c The class to test for. * @param ofType Only return objects of the specified type. * @param array The array to append values to. * @return The same list passed in or a new modifiable list if null. */ public V[] appendAll(Class c, Class ofType, V[] array) { List list = null; for (ClassEntry e : classEntries) if (e.matches(c) && e.value != null) if (ofType == null || ofType.isInstance(e.value)) list = lazyAdd(array, list, e.value); return lazyArray(array, list); } private static List lazyAdd(List list, V v) { if (list == null) list = list(); list.add(v); return list; } /** * Finds first value in this map that matches the specified method. * * @param m The method to test for. * @param ofType Only return objects of the specified type. * @return The matching object. Never null. */ public Optional find(Method m, Class ofType) { for (MethodEntry e : methodEntries) if (e.matches(m)) if (ofType == null || ofType.isInstance(e.value)) return optional(e.value); return empty(); } /** * Finds first value in this map that matches the specified method. * * @param m The method to test for. * @return The matching object. Never null. */ public Optional find(Method m) { return find(m, null); } /** * Finds all values in this map that matches the specified method. * * @param m The method to test for. * @param ofType Only return objects of the specified type. * @return A modifiable list of matching values. Never null. */ public List findAll(Method m, Class ofType) { List list = null; for (MethodEntry e : methodEntries) if (e.matches(m) && e.value != null) if (ofType == null || ofType.isInstance(e.value)) list = lazyAdd(list, e.value); return lazyList(list); } /** * Finds all values in this map that matches the specified method. * * @param m The method to test for. * @return A modifiable list of matching values. Never null. */ public List findAll(Method m) { List list = null; for (MethodEntry e : methodEntries) if (e.matches(m) && e.value != null) list = lazyAdd(list, e.value); return lazyList(list); } /** * Finds all values in this map that matches the specified method. * * @param m The method to test for. * @param ofType Only return objects of the specified type. * @param array The array to append values to. * @return The same list passed in or a new modifiable list if null. */ public V[] appendAll(Method m, Class ofType, V[] array) { List list = null; for (MethodEntry e : methodEntries) if (e.matches(m) && e.value != null) if (ofType == null || ofType.isInstance(e.value)) list = lazyAdd(array, list, e.value); return lazyArray(array, list); } /** * Finds first value in this map that matches the specified field. * * @param f The field to test for. * @param ofType Only return objects of the specified type. * @return The matching object. Never null. */ public Optional find(Field f, Class ofType) { for (FieldEntry e : fieldEntries) if (e.matches(f)) if (ofType == null || ofType.isInstance(e.value)) return optional(e.value); return empty(); } /** * Finds first value in this map that matches the specified field. * * @param f The field to test for. * @return The matching object. Never null. */ public Optional find(Field f) { return find(f, null); } /** * Finds all values in this map that matches the specified field. * * @param f The field to test for. * @param ofType Only return objects of the specified type. * @return A modifiable list of matching values. Never null. */ public List findAll(Field f, Class ofType) { List list = null; for (FieldEntry e : fieldEntries) if (e.matches(f) && e.value != null) if (ofType == null || ofType.isInstance(e.value)) list = lazyAdd(list, e.value); return lazyList(list); } /** * Finds all values in this map that matches the specified field. * * @param f The field to test for. * @return A modifiable list of matching values. Never null. */ public List findAll(Field f) { List list = null; for (FieldEntry e : fieldEntries) if (e.matches(f) && e.value != null) list = lazyAdd(list, e.value); return lazyList(list); } /** * Finds all values in this map that matches the specified field. * * @param f The field to test for. * @param ofType Only return objects of the specified type. * @param array The array to append values to. * @return The same list passed in or a new modifiable list if null. */ public V[] appendAll(Field f, Class ofType, V[] array) { List list = null; for (FieldEntry e : fieldEntries) if (e.matches(f) && e.value != null) if (ofType == null || ofType.isInstance(e.value)) list = lazyAdd(array, list, e.value); return lazyArray(array, list); } /** * Finds first value in this map that matches the specified constructor. * * @param c The constructor to test for. * @param ofType Only return objects of the specified type. * @return The matching object. Never null. */ public Optional find(Constructor c, Class ofType) { for (ConstructorEntry e : constructorEntries) if (e.matches(c)) if (ofType == null || ofType.isInstance(e.value)) return optional(e.value); return empty(); } /** * Finds first value in this map that matches the specified constructor. * * @param c The constructor to test for. * @return The matching object. Never null. */ public Optional find(Constructor c) { return find(c, null); } /** * Finds all values in this map that matches the specified constructor. * * @param c The constructor to test for. * @param ofType Only return objects of the specified type. * @return A modifiable list of matching values. Never null. */ public List findAll(Constructor c, Class ofType) { List list = null; for (ConstructorEntry e : constructorEntries) if (e.matches(c) && e.value != null) if (ofType == null || ofType.isInstance(e.value)) list = lazyAdd(list, e.value); return lazyList(list); } /** * Finds all values in this map that matches the specified constructor. * * @param c The constructor to test for. * @return A modifiable list of matching values. Never null. */ public List findAll(Constructor c) { List list = null; for (ConstructorEntry e : constructorEntries) if (e.matches(c) && e.value != null) list = lazyAdd(list, e.value); return lazyList(list); } /** * Finds all values in this map that matches the specified constructor. * * @param c The constructor to test for. * @param ofType Only return objects of the specified type. * @param array The array to append values to. * @return The same list passed in or a new modifiable list if null. */ public V[] appendAll(Constructor c, Class ofType, V[] array) { List list = null; for (ConstructorEntry e : constructorEntries) if (e.matches(c) && e.value != null) if (ofType == null || ofType.isInstance(e.value)) list = lazyAdd(array, list, e.value); return lazyArray(array, list); } static class ClassEntry { final String simpleName, fullName; final V value; ClassEntry(String name, V value) { this.simpleName = simpleClassName(name); this.fullName = name; this.value = value; } public boolean matches(Class c) { if (c == null) return false; return classMatches(simpleName, fullName, c); } @Override public String toString() { return filteredMap() .append("simpleName", simpleName) .append("fullName", fullName) .append("value", value) .asString(); } } static class MethodEntry { String simpleClassName, fullClassName, methodName, args[]; V value; MethodEntry(String name, V value) { int i = name.indexOf('('); this.args = i == -1 ? null : splitMethodArgs(name.substring(i+1, name.length()-1)); if (args != null) { for (int j = 0; j < args.length; j++) { // Strip off generic parameters. int k = args[j].indexOf('<'); if (k > 0) args[j] = args[j].substring(0, k); // Convert from xxx[][] to [[Lxxx; notation. if (args[j].endsWith("[]")) { int l = 0; while (args[j].endsWith("[]")) { l++; args[j] = args[j].substring(0, args[j].length()-2); } StringBuilder sb = new StringBuilder(args[j].length() + l + 2); for (int m = 0; m < l; m++) sb.append('['); sb.append('L').append(args[j]).append(';'); args[j] = sb.toString(); } } } name = i == -1 ? name : name.substring(0, i); i = name.lastIndexOf('.'); String s1 = name.substring(0, i).trim(), s2 = name.substring(i+1).trim(); this.simpleClassName = simpleClassName(s1); this.fullClassName = s1; this.methodName = s2; this.value = value; } public boolean matches(Method m) { if (m == null) return false; Class c = m.getDeclaringClass(); return classMatches(simpleClassName, fullClassName, c) && (eq(m.getName(), methodName)) && (argsMatch(args, m.getParameterTypes())); } @Override public String toString() { return filteredMap() .append("simpleClassName", simpleClassName) .append("fullClassName", fullClassName) .append("methodName", methodName) .append("args", args) .append("value", value) .asString(); } } static class ConstructorEntry { String simpleClassName, fullClassName, args[]; V value; ConstructorEntry(String name, V value) { int i = name.indexOf('('); this.args = split(name.substring(i+1, name.length()-1)); name = name.substring(0, i).trim(); this.simpleClassName = simpleClassName(name); this.fullClassName = name; this.value = value; } public boolean matches(Constructor m) { if (m == null) return false; Class c = m.getDeclaringClass(); return classMatches(simpleClassName, fullClassName, c) && (argsMatch(args, m.getParameterTypes())); } @Override public String toString() { return filteredMap() .append("simpleClassName", simpleClassName) .append("fullClassName", fullClassName) .append("args", args) .append("value", value) .asString(); } } static class FieldEntry { String simpleClassName, fullClassName, fieldName; V value; FieldEntry(String name, V value) { int i = name.lastIndexOf('.'); String s1 = name.substring(0, i), s2 = name.substring(i+1); this.simpleClassName = simpleClassName(s1); this.fullClassName = s1; this.fieldName = s2; this.value = value; } public boolean matches(Field f) { if (f == null) return false; Class c = f.getDeclaringClass(); return classMatches(simpleClassName, fullClassName, c) && (eq(f.getName(), fieldName)); } @Override public String toString() { return filteredMap() .append("simpleClassName", simpleClassName) .append("fullClassName", fullClassName) .append("fieldName", fieldName) .append("value", value) .asString(); } } static boolean argsMatch(String[] names, Class[] args) { if (names == null) return true; if (names.length != args.length) return false; for (int i = 0; i < args.length; i++) { String n = names[i]; Class a = args[i]; if (! (eq(n, a.getSimpleName()) || eq(n, a.getName()))) return false; } return true; } static String simpleClassName(String name) { int i = name.indexOf('.'); if (i == -1) return name; return null; } static boolean classMatches(String simpleName, String fullName, Class c) { // For class org.apache.juneau.a.rttests.RountTripBeansWithBuilders$Ac$Builder // c.getSimpleName() == "Builder" // c.getFullName() == "org.apache.juneau.a.rttests.RountTripBeansWithBuilders$Ac$Builder" // c.getPackage() == "org.apache.juneau.a.rttests" String cSimple = c.getSimpleName(), cFull = c.getName(); if (eq(simpleName, cSimple) || eq(fullName, cFull) || "*".equals(simpleName)) return true; if (cFull.indexOf('$') != -1) { Package p = c.getPackage(); if (p != null) cFull = cFull.substring(p.getName().length() + 1); if (eq(simpleName, cFull)) return true; int i = cFull.indexOf('$'); while (i != -1) { cFull = cFull.substring(i+1); if (eq(simpleName, cFull)) return true; i = cFull.indexOf('$'); } } return false; } @Override /* Object */ public String toString() { return filteredMap() .append("classEntries", classEntries) .append("methodEntries", methodEntries) .append("fieldEntries", fieldEntries) .append("constructorEntries", constructorEntries) .asString(); } //----------------------------------------------------------------------------------------------------------------- // Utility methods //----------------------------------------------------------------------------------------------------------------- private static List lazyList(List list) { return list == null ? Collections.emptyList() : list; } private static List lazyAdd(V[] array, List list, V v) { if (list == null) list = list(array); list.add(v); return list; } @SuppressWarnings("unchecked") private static V[] lazyArray(V[] array, List list) { if (list == null) return array; array = (V[])Array.newInstance(array.getClass().getComponentType(), list.size()); list.toArray(array); return array; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy