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

sviolet.thistle.util.reflect.ReflectGetter Maven / Gradle / Ivy

/*
 * Copyright (C) 2015-2018 S.Violet
 *
 * Licensed 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.
 *
 * Project GitHub: https://github.com/shepherdviolet/thistle
 * Email: [email protected]
 */

package sviolet.thistle.util.reflect;

import sviolet.thistle.util.conversion.BeanMethodNameUtils;
import sviolet.thistle.util.judge.CheckUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 简易的反射取值工具
 *
 * @author S.Violet
 */
public class ReflectGetter {

    /**
     * 

根据键路径从对象中取值, 忽略异常

* *

若值不存在则返回null, 若获取异常则返回null

* *

ReflectGetter.getWithoutException(obj, "body.list[1].name", true);

* *

* 如下基本类型无法再拆分:
* int, long, float, boolean, byte, char, double, short, byte[], char[]
* Integer, Long, Float, Boolean, Byte, Character, Double, Short, Byte[], Character[]
* String
*

* * @param obj 对象 * @param keyPath 键路径 * @param getByMethodEnabled true:尝试用getter方法从Bean中取值 false:只从成员变量中取值 * @return 若值不存在则返回null, 若获取异常则返回null */ public static T getWithoutException(Object obj, String keyPath, boolean getByMethodEnabled) { try { return get(obj, keyPath, getByMethodEnabled); } catch (IllegalKeyPathException | TypeNotMatchException | ReflectException | OutOfBoundException | FieldNotFoundException e) { return null; } } /** *

根据键路径从对象中取值

* *

ReflectGetter.get(obj, "body.list[1].name", true);

* *

* 如下基本类型无法再拆分:
* int, long, float, boolean, byte, char, double, short, byte[], char[]
* Integer, Long, Float, Boolean, Byte, Character, Double, Short, Byte[], Character[]
* String
*

* * @param obj 对象 * @param keyPath 键路径 * @param getByMethodEnabled true:尝试用getter方法从Bean中取值 false:只从成员变量中取值 * @return 若值不存在则返回null * @throws IllegalKeyPathException 无效的键路径 * @throws TypeNotMatchException 取值时类型不匹配 * @throws OutOfBoundException 数组越界异常 * @throws FieldNotFoundException Bean对象中成员变量或Getter方法不存在 * @throws ReflectException Bean对象通过反射取值时异常 */ public static T get(Object obj, String keyPath, boolean getByMethodEnabled) throws IllegalKeyPathException, TypeNotMatchException, OutOfBoundException, ReflectException, FieldNotFoundException { return get(obj, parseKeyPath(keyPath), getByMethodEnabled); } /** *

根据键路径从对象中取值, 忽略异常

* *

ReflectGetter.getWithoutException(obj, keyPath, true);

* *

若值不存在则返回null, 若获取异常则返回null

* *

* 如下基本类型无法再拆分:
* int, long, float, boolean, byte, char, double, short, byte[], char[]
* Integer, Long, Float, Boolean, Byte, Character, Double, Short, Byte[], Character[]
* String
*

* * @param obj 对象 * @param keyPath 键路径 * @param getByMethodEnabled true:尝试用getter方法从Bean中取值 false:只从成员变量中取值 * @return 若值不存在则返回null, 若获取异常则返回null */ public static T getWithoutException(Object obj, KeyPath keyPath, boolean getByMethodEnabled) { try { return get(obj, keyPath, getByMethodEnabled); } catch (IllegalKeyPathException | TypeNotMatchException | ReflectException | OutOfBoundException | FieldNotFoundException e) { return null; } } /** *

[推荐]根据键路径从对象中取值. 建议先用ReflectGetter.parseKeyPath()方法解析键路径(键路径实例可重复使用, 线程安全), * 然后使用该方法取值.

* *

ReflectGetter.get(obj, keyPath, true);

* *

* 如下基本类型无法再拆分:
* int, long, float, boolean, byte, char, double, short, byte[], char[]
* Integer, Long, Float, Boolean, Byte, Character, Double, Short, Byte[], Character[]
* String
*

* * @param obj 对象 * @param keyPath 键路径 * @param getByMethodEnabled true:尝试用getter方法从Bean中取值 false:只从成员变量中取值 * @return 若值不存在则返回null * @throws IllegalKeyPathException 无效的键路径 * @throws TypeNotMatchException 取值时类型不匹配 * @throws OutOfBoundException 数组越界异常 * @throws FieldNotFoundException Bean对象中成员变量或Getter方法不存在 * @throws ReflectException Bean对象通过反射取值时异常 */ @SuppressWarnings("unchecked") public static T get(Object obj, KeyPath keyPath, boolean getByMethodEnabled) throws IllegalKeyPathException, TypeNotMatchException, OutOfBoundException, ReflectException, FieldNotFoundException { if (obj == null){ return null; } if (keyPath == null) { throw new IllegalKeyPathException("keyPath is null"); } Object currentObj = obj; KeyPath currentKeyPath = keyPath; int index = 0; //0:key 1:index1 2:index2 int stat = 0; while (currentKeyPath != null) { int type = getObjectType(currentObj); if (stat == 0) { if (CheckUtils.isEmpty(currentKeyPath.key)){ if (currentKeyPath.index1 >= 0){ stat = 1; continue; } } switch (type){ case 0: throw new TypeNotMatchException("Can't get field by key from basic type, type:" + currentObj.getClass().getName() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); case 1: Map mapObj = (Map) currentObj; currentObj = mapObj.get(currentKeyPath.key); break; case 2: case 3: case 4: throw new TypeNotMatchException("Can't get field by key from List / array[] / array[][], type:" + currentObj.getClass().getName() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); case 9: Class clazz = currentObj.getClass(); if (getByMethodEnabled) { Method method = null; try { //ignore private method method = clazz.getMethod(BeanMethodNameUtils.fieldToGetter(currentKeyPath.key)); if(!void.class.isAssignableFrom(method.getReturnType())){ if (!method.isAccessible()){ method.setAccessible(true); } currentObj = method.invoke(currentObj); break; } } catch (NoSuchMethodException ignore) { //find field } catch (Throwable t) { throw new ReflectException("Error while getting field by method, method:" + (method != null ? method.getName() : "unknown") + ", type:" + currentObj.getClass().getName() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath, t); } } try { Field field = clazz.getDeclaredField(currentKeyPath.key); if (!field.isAccessible()){ field.setAccessible(true); } currentObj = field.get(currentObj); } catch (NoSuchFieldException e) { throw new FieldNotFoundException("Field not found in bean, field:" + currentKeyPath.key + ", type:" + currentObj.getClass().getName() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); } catch (Throwable t) { throw new ReflectException("Error while getting field by name, field:" + currentKeyPath.key + ", type:" + currentObj.getClass().getName() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath, t); } break; default: throw new TypeNotMatchException("Unexpected object type, type:" + currentObj.getClass().getName() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); } if (currentObj == null){ return null; } if (currentKeyPath.index1 >= 0){ stat = 1; } else { currentKeyPath = currentKeyPath.next; index++; stat = 0; } } else if (stat == 1) { Object temp; switch (type){ case 0: throw new TypeNotMatchException("Can't get field by index from basic type, type:" + currentObj.getClass().getName() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); case 1: throw new TypeNotMatchException("Can't get field by index from Map, type:" + currentObj.getClass().getName() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); case 2: List listObj = (List) currentObj; try { temp = listObj.get(currentKeyPath.index1); } catch (Throwable t){ throw new OutOfBoundException("Out of bound while getting field from List, your index1:" + currentKeyPath.index1 + ", list size:" + listObj.size() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); } break; case 3: try { temp = Array.get(currentObj, currentKeyPath.index1); } catch (Throwable t){ throw new OutOfBoundException("Out of bound while getting field from array[], your index1:" + currentKeyPath.index1 + ", array length:" + Array.getLength(currentObj) + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); } break; case 4: try { temp = Array.get(currentObj, currentKeyPath.index1); } catch (Throwable t){ throw new OutOfBoundException("Out of bound while getting field from array[][], your index1:" + currentKeyPath.index1 + ", array length:" + Array.getLength(currentObj) + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); } if (currentKeyPath.index2 < 0){ throw new TypeNotMatchException("Object is array[][], but array[] is defined in your keyPath, type:" + currentObj.getClass().getName() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); } if (temp == null){ return null; } currentObj = temp; stat = 2; continue; case 9: throw new TypeNotMatchException("Can't get field by index from bean type, type:" + currentObj.getClass().getName() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); default: throw new TypeNotMatchException("Unexpected object type, type:" + currentObj.getClass().getName() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); } if (currentKeyPath.index2 >= 0){ throw new TypeNotMatchException("Object is array[], but array[][] is defined in your keyPath, type:" + currentObj.getClass().getName() + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); } if (temp == null){ return null; } currentObj = temp; currentKeyPath = currentKeyPath.next; index++; stat = 0; } else { try { currentObj = Array.get(currentObj, currentKeyPath.index2); } catch (Throwable t){ throw new OutOfBoundException("Out of bound while getting field from array[][], your index2:" + currentKeyPath.index1 + ", array length2:" + Array.getLength(currentObj) + ". objType:" + obj.getClass().getName() + ", index:" + index + ", keyPath:" + keyPath); } currentKeyPath = currentKeyPath.next; index++; stat = 0; } } return (T) currentObj; } /** * -1: nul * 0: basic * 1: map * 2: list * 3. array[] * 4. array[][] * 9. bean * * basic: * integer, long, float, boolean, byte, char, double, short, byte[], char[], String */ private static int getObjectType(Object obj){ //null if (obj == null){ return -1; } //map if (obj instanceof Map) { return 1; } //list if (obj instanceof List) { return 2; } //basic if (obj.getClass().isPrimitive() || obj instanceof Integer || obj instanceof Long || obj instanceof Float || obj instanceof Boolean || obj instanceof Byte || obj instanceof Character || obj instanceof Double || obj instanceof Short || obj instanceof String) { return 0; } String className = obj.getClass().getName(); //basic if ("[B".equals(className) || "[C".equals(className) || "[Ljava.lang.Byte;".equals(className) || "[Ljava.lang.Character;".equals(className)) { return 0; } //array [][] if (className.startsWith("[[")){ return 4; } //array [] if (className.startsWith("[")){ return 3; } //bean return 9; } /** *

[推荐]解析键路径. 建议先用ReflectGetter.parseKeyPath()方法解析键路径(键路径实例可重复使用, 线程安全), * 然后使用该方法取值.

* *

* 示例1:一般
* body.infoList[3].name
* 示例2:三维数组
* infoList[3][1].[1]
* 示例2:Obj本身是List或Array
* [1].name
*

* *

* 转移符:
* // -> /
* /. -> .
* /[ -> [
* /] -> ]
*

* * @param keyPath 键路径(字符串) * @return 键路径 * @throws ReflectGetter.IllegalKeyPathException 无效的键路径 */ public static KeyPath parseKeyPath(String keyPath) throws ReflectGetter.IllegalKeyPathException { //split List elements = splitPath(keyPath); //result KeyPath firstElement = null; //previous KeyPath previousElement = null; //current KeyPath currentElement; for (String element : elements) { //record origin for log String elementOrigin = element; //new key path currentElement = new KeyPath(); if (!CheckUtils.isEmpty(element)){ /* element not null */ /* 0: init stat 1: first [ 2: first ] 3: second [ 4: second ] */ int step = 0; int start = 0; boolean escape = false; for (int i = 0 ; i < element.length() ; i++){ char current = element.charAt(i); if (escape) { /* previous is escape / */ switch (current) { case '/': case '[': case ']': if (i < 2){ element = element.substring(i); } else { element = element.substring(0, i - 1) + element.substring(i); } i--; break; default: throw new ReflectGetter.IllegalKeyPathException("Illegal keyPath, unexpected \"/" + current + "\", element:" + elementOrigin + ", keyPath:" + keyPath); } escape = false; } else { /* normal char */ //chars after "key[index1][index2]" if (step > 4){ throw new ReflectGetter.IllegalKeyPathException("Illegal keyPath, unexpected \"" + current + "\", element:" + elementOrigin + ", keyPath:" + keyPath); } switch (current) { case '/': escape = true; break; case '[': if (step == 0) { currentElement.key = element.substring(start, i); start = i + 1; step = 1; } else if (step == 2) { start = i + 1; step = 3; } else { throw new ReflectGetter.IllegalKeyPathException("Illegal keyPath, unexpected \"" + current + "\", element:" + elementOrigin + ", keyPath:" + keyPath); } break; case ']': if (step == 1) { try { currentElement.index1 = Integer.parseInt(element.substring(start, i)); } catch (Throwable t) { throw new ReflectGetter.IllegalKeyPathException("Illegal keyPath, \"" + current + "\" can't cast to integer, element:" + elementOrigin + ", keyPath:" + keyPath, t); } step = 2; } else if (step == 3) { try { currentElement.index2 = Integer.parseInt(element.substring(start, i)); } catch (Throwable t) { throw new ReflectGetter.IllegalKeyPathException("Illegal keyPath, \"" + current + "\" can't cast to integer, element:" + elementOrigin + ", keyPath:" + keyPath, t); } step = 4; } else { throw new ReflectGetter.IllegalKeyPathException("Illegal keyPath, unexpected \"" + current + "\", element:" + elementOrigin + ", keyPath:" + keyPath); } break; default: //chars in ][ if (step == 2) { throw new ReflectGetter.IllegalKeyPathException("Illegal keyPath, unexpected \"" + current + "\", element:" + elementOrigin + ", keyPath:" + keyPath); } break; } } } // no [] element if (step == 0){ currentElement.key = element; } } else { /* element is null */ currentElement.key = ""; } if (firstElement == null){ firstElement = currentElement; } if (previousElement != null){ previousElement.next = currentElement; } previousElement = currentElement; } if (firstElement == null){ throw new ReflectGetter.IllegalKeyPathException("Empty keyPath, keyPath:" + keyPath); } return firstElement; } /** * split keyPath by . * @param keyPath keyPath * @return List * @throws ReflectGetter.IllegalKeyPathException exception */ private static List splitPath(String keyPath) throws ReflectGetter.IllegalKeyPathException{ //record origin for log String keyPathOrigin = keyPath; //result List list = new ArrayList<>(); if (keyPath == null){ return list; } boolean escape = false; int start = 0; for (int i = 0 ; i < keyPath.length() ; i++){ char current = keyPath.charAt(i); if (escape) { /* previous char is escape / */ switch (current) { case '.': if (i < 2){ keyPath = keyPath.substring(i); } else { keyPath = keyPath.substring(0, i - 1) + keyPath.substring(i); } i--; break; case '/': case '[': case ']': // valid char but do nothing break; default: throw new ReflectGetter.IllegalKeyPathException("Illegal keyPath, unexpected \"/" + current + "\", index:" + i + ", keyPath:" + keyPathOrigin); } escape = false; } else { /* normal char */ switch (current) { case '/': escape = true; break; case '.': list.add(keyPath.substring(start, i)); start = i + 1; break; default: break; } } } //remain if (start <= keyPath.length()){ list.add(keyPath.substring(start, keyPath.length())); } return list; } /** * 键路径实例 */ public final static class KeyPath { private String key; private int index1 = -1; private int index2 = -1; private KeyPath next; private KeyPath() { } @Override public String toString() { return "<" + key + ">" + (index1 > -1 ? "[" + index1 + "]" : "") + (index2 > -1 ? "[" + index2 + "]" : "") + (next != null ? " - " + next : ""); } } //Exceptions/////////////////////////////////////////////////////////////////////////////////////////// /** * 类型不匹配异常 */ public static class TypeNotMatchException extends Exception{ private static final long serialVersionUID = -1320649019170185133L; public TypeNotMatchException(String message) { super(message); } } /** * 数组越界异常 */ public static class OutOfBoundException extends Exception{ private static final long serialVersionUID = 7582276186625326816L; public OutOfBoundException(String message) { super(message); } } /** * Bean对象中成员变量或Getter方法不存在 */ public static class FieldNotFoundException extends Exception { private static final long serialVersionUID = -1216738558046238249L; public FieldNotFoundException(String message) { super(message); } } /** * Bean对象通过反射取值时异常 */ public static class ReflectException extends Exception { private static final long serialVersionUID = 2534544438666088680L; public ReflectException(String message) { super(message); } public ReflectException(String message, Throwable cause) { super(message, cause); } } /** * 非法键路径异常 */ public static class IllegalKeyPathException extends Exception { private static final long serialVersionUID = -360819106527518480L; public IllegalKeyPathException(String message) { super(message); } public IllegalKeyPathException(String message, Throwable cause) { super(message, cause); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy