![JAR search and dependency download from the Maven repository](/logo.png)
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 extends V> 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 extends V> 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 extends V> 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 extends V> 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 extends V> 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 extends V> 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 extends V> 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 extends V> 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 extends V> 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 extends V> 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 extends V> 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 extends V> 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