PropertyTypeNavigator.java

  1. package com.gh.mygreen.xlsmapper.util;

  2. import java.lang.reflect.Field;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.ParameterizedType;
  5. import java.lang.reflect.Type;
  6. import java.util.Collection;
  7. import java.util.LinkedList;
  8. import java.util.Map;
  9. import java.util.concurrent.ConcurrentHashMap;

  10. import com.gh.mygreen.xlsmapper.util.PropertyPath.Token;

  11. /**
  12.  * クラス定義からプロパティのクラスタイプを取得する。
  13.  * <p>プロパティは式言語の形式に似た形式をとることが可能で、フィールドにもアクセスできます。</p>
  14.  *
  15.  *
  16.  * @since 2.0
  17.  * @author T.TSUCHIE
  18.  *
  19.  */
  20. public class PropertyTypeNavigator {
  21.    
  22.     /**
  23.      * 非公開のプロパティにアクセス可能かどうか。
  24.      */
  25.     private boolean allowPrivate;
  26.    
  27.     /**
  28.      * クラスタイプの解析ができない場合に処理を終了するかどうか。
  29.      */
  30.     private boolean ignoreNotResolveType;
  31.    
  32.     /**
  33.      * プロパティの解析結果をキャッシュするかどうか。
  34.      */
  35.     private boolean cacheWithPath;
  36.    
  37.     /**
  38.      * プロパティの式を解析して、トークンに分解するクラス。
  39.      */
  40.     private final PropertyPathTokenizer tokenizer = new PropertyPathTokenizer();
  41.    
  42.     /**
  43.      * プロパティの解析結果をキャッシュデータ
  44.      */
  45.     private final Map<String, PropertyPath> cacheData = new ConcurrentHashMap<>();
  46.    
  47.      /**
  48.      * プロパティの値を取得する。
  49.      * <p>オプションはデフォルト値で処理する。</p>
  50.      * @param rootClass 取得元となるクラス
  51.      * @param property プロパティの式。
  52.      * @return プロパティのクラスタイプ。
  53.      * @throws IllegalArgumentException peropety is null or empty.
  54.      * @throws PropertyAccessException 存在しないプロパティを指定した場合など。
  55.      * @throws IllegalStateException リストやマップにアクセスする際にGenericsタイプが設定されておらずクラスタイプが取得できない場合。
  56.      *         ただし、オプションignoreNotResolveType = falseのとき。
  57.      */
  58.     public static Object get(final Class<?> rootClass, final String property) {
  59.         return new PropertyTypeNavigator().getPropertyType(rootClass, property);
  60.     }
  61.    
  62.     /**
  63.      * デフォルトコンストラクタ
  64.      */
  65.     public PropertyTypeNavigator() {
  66.        
  67.     }
  68.    
  69.     /**
  70.      * プロパティの値を取得する。
  71.      *
  72.      * @param rootClass 取得元となるクラス
  73.      * @param property プロパティの式。
  74.      * @return プロパティのクラスタイプ。
  75.      * @throws IllegalArgumentException peropety is null or empty.
  76.      * @throws PropertyAccessException 存在しないプロパティを指定した場合など。
  77.      * @throws IllegalStateException リストやマップにアクセスする際にGenericsタイプが設定されておらずクラスタイプが取得できない場合。
  78.      *         ただし、オプションignoreNotResolveType = falseのとき。
  79.      */
  80.     public Class<?> getPropertyType(final Class<?> rootClass, final String property) {
  81.        
  82.         ArgUtils.notEmpty(property, "property");
  83.        
  84.         final PropertyPath path = parseProperty(property);
  85.         final LinkedList<Object> stack = new LinkedList<>();
  86.         Class<?> targetClass = rootClass;
  87.         for(Token token : path.getPathAsToken()) {
  88.            
  89.             targetClass = accessProperty(targetClass, token, stack);
  90.             if(targetClass == null) {
  91.                 return null;
  92.             }
  93.         }
  94.        
  95.         return targetClass;
  96.        
  97.     }
  98.    
  99.     /**
  100.      * プロパティの式をパースして、{@link PropertyPath} オブジェクトに変換する。
  101.      * @param property プロパティアクセス用の式
  102.      * @return 式を解析した結果 {@link PropertyPath}
  103.      */
  104.     private PropertyPath parseProperty(final String property) {
  105.        
  106.         if(isCacheWithPath()) {
  107.             return cacheData.computeIfAbsent(property, k -> tokenizer.parse(k));
  108.         } else {
  109.             return tokenizer.parse(property);
  110.         }
  111.        
  112.     }
  113.    
  114.     private Class<?> accessProperty(final Class<?> targetClass, final Token token, final LinkedList<Object> stack) {
  115.        
  116.         if(token instanceof Token.Separator) {
  117.             return accessPropertyBySeparator(targetClass, (Token.Separator)token, stack);
  118.            
  119.         } else if(token instanceof Token.Name) {
  120.             return accessPropertyByName(targetClass, (Token.Name)token, stack);
  121.            
  122.         } else if(token instanceof Token.Key) {
  123.             return accessPropertyByKey(targetClass, (Token.Key)token, stack);
  124.         }
  125.        
  126.         throw new IllegalStateException("not support token type : " + token.getValue());
  127.        
  128.     }
  129.    
  130.     private Class<?> accessPropertyBySeparator(final Class<?> targetClass, final Token.Separator token, final LinkedList<Object> stack) {
  131.        
  132.         return targetClass;
  133.        
  134.     }
  135.    
  136.     private Class<?> accessPropertyByName(final Class<?> targetClass, final Token.Name token, final LinkedList<Object> stack) {
  137.        
  138.         // メソッドアクセス
  139.         final String getterMethodName = "get" + Utils.capitalize(token.getValue());
  140.         try {
  141.             Method getterMethod = allowPrivate ?
  142.                     targetClass.getDeclaredMethod(getterMethodName) : targetClass.getMethod(getterMethodName);
  143.             getterMethod.setAccessible(true);
  144.            
  145.             // Collectionなどの場合、メソッド情報をスタックに積んでおく
  146.             stack.push(getterMethod);
  147.            
  148.             return getterMethod.getReturnType();
  149.            
  150.         } catch (NoSuchMethodException | SecurityException e) {
  151.             // not found method
  152.            
  153.         }
  154.        
  155.         // boolean用メソッドアクセス
  156.         final String booleanMethodName = "is" + Utils.capitalize(token.getValue());
  157.         try {
  158.             Method getterMethod = allowPrivate ?
  159.                     targetClass.getDeclaredMethod(booleanMethodName) : targetClass.getMethod(booleanMethodName);;
  160.             getterMethod.setAccessible(true);
  161.            
  162.             return getterMethod.getReturnType();
  163.            
  164.         } catch (NoSuchMethodException | SecurityException e) {
  165.             // not found method
  166.            
  167.         }
  168.        
  169.         // フィールドアクセス
  170.         final String fieldName = token.getValue();
  171.         try {
  172.             Field field = allowPrivate ?
  173.                     targetClass.getDeclaredField(fieldName) : targetClass.getField(fieldName);
  174.             field.setAccessible(true);
  175.            
  176.             // Collectionなどの場合、メソッド情報をスタックに積んでおく
  177.             stack.push(field);
  178.            
  179.             return field.getType();
  180.            
  181.         } catch (NoSuchFieldException | SecurityException e) {
  182.             // not found field
  183.            
  184.         }
  185.        
  186.         throw new PropertyAccessException("not found property : " + token.getValue());
  187.        
  188.     }
  189.    
  190.     private Class<?> accessPropertyByKey(final Class<?> targetClass, final Token.Key token, final LinkedList<Object> stack) {
  191.        
  192.         final Object parent = stack.isEmpty() ? null : stack.pollFirst();
  193.         if(parent == null) {
  194.             return null;
  195.         }
  196.         if(Collection.class.isAssignableFrom(targetClass)) {
  197.            
  198.             Type argType = null;
  199.             if(parent instanceof Method) {
  200.                 // getterメソッドのからGenericsのタイプを取得する
  201.                 Type type = ((Method)parent).getGenericReturnType();
  202.                 if(!ParameterizedType.class.isAssignableFrom(type.getClass())) {
  203.                     if(ignoreNotResolveType) {
  204.                         return null;
  205.                     } else {
  206.                         throw new IllegalStateException("not resolve generics type with property : " + token.getValue());
  207.                     }
  208.                 }
  209.                 argType = ((ParameterizedType)type).getActualTypeArguments()[0];
  210.                
  211.             } else if(parent instanceof Field) {
  212.                 Type type = ((Field)parent).getGenericType();
  213.                 if(!ParameterizedType.class.isAssignableFrom(type.getClass())) {
  214.                     if(ignoreNotResolveType) {
  215.                         return null;
  216.                     } else {
  217.                         throw new IllegalStateException("not resolve generics type with property : " + token.getValue());
  218.                     }
  219.                 }
  220.                
  221.                 argType = ((ParameterizedType)type).getActualTypeArguments()[0];
  222.                
  223.             } else if(parent instanceof ParameterizedType) {
  224.                 ParameterizedType type = (ParameterizedType) parent;
  225.                 argType = type.getActualTypeArguments()[0];
  226.                
  227.             } else {
  228.                 if(ignoreNotResolveType) {
  229.                     return null;
  230.                 } else {
  231.                     throw new IllegalStateException("not resolve generics type with property : " + token.getValue());
  232.                 }
  233.             }
  234.            
  235.             if(argType == null) {
  236.                 return null;
  237.             }
  238.            
  239.             if(argType instanceof Class) {
  240.                 return (Class<?>)argType;
  241.                
  242.             } else if(argType instanceof ParameterizedType) {
  243.                 ParameterizedType paramType = ((ParameterizedType)argType);
  244.                 stack.push(paramType);
  245.                 return (Class<?>)paramType.getRawType();
  246.             }
  247.            
  248.         } else if (targetClass.isArray()) {
  249.            
  250.             return targetClass.getComponentType();
  251.            
  252.         } else if(Map.class.isAssignableFrom(targetClass)) {
  253.            
  254.             Type argType = null;
  255.             if(parent instanceof Method) {
  256.                 // getterメソッドのからGenericsのタイプを取得する
  257.                 Type type = ((Method)parent).getGenericReturnType();
  258.                 if(!ParameterizedType.class.isAssignableFrom(type.getClass())) {
  259.                     if(ignoreNotResolveType) {
  260.                         return null;
  261.                     } else {
  262.                         throw new IllegalStateException("not resolve generics type with property : " + token.getValue());
  263.                     }
  264.                 }
  265.                 argType = ((ParameterizedType)type).getActualTypeArguments()[1];
  266.                
  267.             } else if(parent instanceof Field) {
  268.                 Type type = ((Field)parent).getGenericType();
  269.                 if(!ParameterizedType.class.isAssignableFrom(type.getClass())) {
  270.                     if(ignoreNotResolveType) {
  271.                         return null;
  272.                     } else {
  273.                         throw new IllegalStateException("not resolve generics type with property : " + token.getValue());
  274.                     }
  275.                 }
  276.                 argType = ((ParameterizedType)type).getActualTypeArguments()[1];
  277.                
  278.             } else if(parent instanceof ParameterizedType) {
  279.                 ParameterizedType type = (ParameterizedType) parent;
  280.                 argType = type.getActualTypeArguments()[1];
  281.                
  282.             } else {
  283.                 if(ignoreNotResolveType) {
  284.                     return null;
  285.                 } else {
  286.                     throw new IllegalStateException("not resolve generics type with property : " + token.getValue());
  287.                 }
  288.             }
  289.            
  290.             if(argType == null) {
  291.                 return null;
  292.             }
  293.            
  294.             if(argType instanceof Class) {
  295.                 return (Class<?>)argType;
  296.                
  297.             } else if(argType instanceof ParameterizedType) {
  298.                 ParameterizedType paramType = ((ParameterizedType)argType);
  299.                 stack.push(paramType);
  300.                 return (Class<?>)paramType.getRawType();
  301.             }
  302.            
  303.         }
  304.        
  305.         throw new PropertyAccessException("not support key access : " + targetClass.getName());
  306.        
  307.     }
  308.    
  309.     /**
  310.      * 今までのキャッシュデータをクリアする。
  311.      */
  312.     public void clearCache() {
  313.         this.cacheData.clear();
  314.     }
  315.    
  316.     /**
  317.      * 実行時オプション - 非公開のプロパティにアクセス可能かどうか。
  318.      * @return true: アクセスを許可する。false: デフォルト値。
  319.      */
  320.     public boolean isAllowPrivate() {
  321.         return allowPrivate;
  322.     }
  323.    
  324.     /**
  325.      * 実行時オプション - 非公開のプロパティにアクセス可能かどうか。
  326.      * @param allowPrivate true: アクセスを許可する。
  327.      */
  328.     public void setAllowPrivate(boolean allowPrivate) {
  329.         this.allowPrivate = allowPrivate;
  330.     }
  331.    
  332.     /**
  333.      * クラスタイプの解析ができない場合に処理を終了するかどうか。
  334.      * <p>ListなどでGenericsタイプが指定されていでクラスタイプが取得できないときに、nullを返すかどうか指定します。</p>
  335.      * @return true:その時点で処理を終了する。
  336.      */
  337.     public boolean isIgnoreNotResolveType() {
  338.         return ignoreNotResolveType;
  339.     }
  340.    
  341.     /**
  342.      * クラスタイプの解析ができない場合に処理を終了するかどうか。
  343.      * <p>ListなどでGenericsタイプが指定されていでクラスタイプが取得できないときに、nullを返すかどうか指定します。</p>
  344.      * @param ignoreNotResolveType true:その時点で処理を終了する。
  345.      */
  346.     public void setIgnoreNotResolveType(boolean ignoreNotResolveType) {
  347.         this.ignoreNotResolveType = ignoreNotResolveType;
  348.     }
  349.    
  350.    
  351.     /**
  352.      * プロパティの解析結果をキャッシュするかどうか。
  353.      * @return true: キャッシュする。false: デフォルト値。
  354.      */
  355.     public boolean isCacheWithPath() {
  356.         return cacheWithPath;
  357.     }
  358.    
  359.     /**
  360.      * プロパティの解析結果をキャッシュするかどうか。
  361.      * @param cacheWithPath true: キャッシュする。
  362.      */
  363.     public void setCacheWithPath(boolean cacheWithPath) {
  364.         this.cacheWithPath = cacheWithPath;
  365.     }
  366.    
  367. }