/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * 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. * ******************************************************************************/ package org.pentaho.di.core.injection.bean; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.pentaho.di.core.injection.Injection; import org.pentaho.di.core.injection.InjectionDeep; import org.pentaho.di.core.injection.InjectionTypeConverter; /** * Storage for one step on the bean deep level. */ class BeanLevelInfo { enum DIMENSION { NONE, ARRAY, LIST }; /** Parent step or null for root. */ public BeanLevelInfo parent; /** Class for step from field or methods. */ public Class<?> leafClass; /** Field of step, or null if bean has getter/setter. */ public Field field; /** Getter and setter. */ public Method getter, setter; /** Dimension of level. */ public DIMENSION dim = DIMENSION.NONE; /** Values converter. */ public InjectionTypeConverter converter; /** False if source empty value shoudn't affect on target field. */ public boolean convertEmpty; /** Name prefix on the path. */ public String prefix; public void init( BeanInjectionInfo info ) { introspect( info, leafClass, new TreeMap<>() ); } /** * Introspect class and all interfaces and ancestors recursively. */ private void introspect( BeanInjectionInfo info, Type type, Map<String, Type> ownerGenericsInfo ) { Map<String, Type> genericsInfo = new TreeMap<>( ownerGenericsInfo ); while ( type != null ) { Class<?> clazz; ParameterizedType pt; if ( type instanceof ParameterizedType ) { pt = (ParameterizedType) type; clazz = (Class<?>) pt.getRawType(); } else { pt = null; clazz = (Class<?>) type; } // introspect generics TypeVariable<?>[] tps = clazz.getTypeParameters(); if ( tps.length > 0 ) { if ( pt == null ) { throw new RuntimeException( "Can't introspect class with parameters on the high level" ); } Type[] args = pt.getActualTypeArguments(); if ( tps.length != args.length ) { throw new RuntimeException( "Wrong generics declaration" ); } Map<String, Type> prevGenerics = genericsInfo; genericsInfo = new TreeMap<>(); for ( int i = 0; i < tps.length; i++ ) { if ( args[i] instanceof TypeVariable ) { TypeVariable<?> argsi = (TypeVariable<?>) args[i]; Type prev = prevGenerics.get( argsi.getName() ); if ( prev == null ) { throw new RuntimeException( "Generic '" + args[i] + "' was not declared yet" ); } genericsInfo.put( tps[i].getName(), prev ); } else { genericsInfo.put( tps[i].getName(), args[i] ); } } System.out.println(); } introspect( info, clazz.getDeclaredFields(), clazz.getDeclaredMethods(), genericsInfo ); for ( Type intf : clazz.getGenericInterfaces() ) { introspect( info, intf, genericsInfo ); } type = clazz.getGenericSuperclass(); } } /** * Introspect fields and methods of some class. */ protected void introspect( BeanInjectionInfo info, Field[] fields, Method[] methods, Map<String, Type> genericsInfo ) { for ( Field f : fields ) { Injection annotationInjection = f.getAnnotation( Injection.class ); InjectionDeep annotationInjectionDeep = f.getAnnotation( InjectionDeep.class ); if ( annotationInjection == null && annotationInjectionDeep == null ) { // no injection annotations continue; } if ( annotationInjection != null && annotationInjectionDeep != null ) { // both annotations exist - wrong throw new RuntimeException( "Field can't be annotated twice for injection " + f ); } if ( f.isSynthetic() || f.isEnumConstant() || Modifier.isStatic( f.getModifiers() ) ) { // fields can't contain real data with such modifier throw new RuntimeException( "Wrong modifier for anotated field " + f ); } BeanLevelInfo leaf = new BeanLevelInfo(); leaf.parent = this; leaf.field = f; Type t; if ( f.getType().isArray() ) { Type ff = f.getGenericType(); leaf.dim = DIMENSION.ARRAY; if ( ff instanceof GenericArrayType ) { GenericArrayType ffg = (GenericArrayType) ff; t = resolveGenericType( ffg.getGenericComponentType(), genericsInfo ); } else { t = f.getType().getComponentType(); } } else if ( List.class.equals( f.getType() ) ) { leaf.dim = DIMENSION.LIST; Type fieldType = f.getGenericType(); Type listType = ( (ParameterizedType) fieldType ).getActualTypeArguments()[0]; try { t = resolveGenericType( listType, genericsInfo ); } catch ( Throwable ex ) { throw new RuntimeException( "Can't retrieve type from List for " + f, ex ); } } else { leaf.dim = DIMENSION.NONE; t = resolveGenericType( f.getGenericType(), genericsInfo ); } if ( t instanceof ParameterizedType ) { ParameterizedType pt = (ParameterizedType) t; leaf.leafClass = (Class<?>) pt.getRawType(); } else { leaf.leafClass = (Class<?>) t; } if ( annotationInjection != null ) { try { leaf.converter = annotationInjection.converter().newInstance(); } catch ( Exception ex ) { throw new RuntimeException( "Error instantiate converter for " + f, ex ); } leaf.convertEmpty = annotationInjection.convertEmpty(); info.addInjectionProperty( annotationInjection, leaf ); } else if ( annotationInjectionDeep != null ) { // introspect deeper leaf.prefix = annotationInjectionDeep.prefix(); TreeMap<String, Type> gi = new TreeMap<>( genericsInfo ); leaf.introspect( info, t, gi ); } } for ( Method m : methods ) { Injection annotationInjection = m.getAnnotation( Injection.class ); InjectionDeep annotationInjectionDeep = m.getAnnotation( InjectionDeep.class ); if ( annotationInjection == null && annotationInjectionDeep == null ) { // no injection annotations continue; } if ( annotationInjection != null && annotationInjectionDeep != null ) { // both annotations exist - wrong throw new RuntimeException( "Method can't be annotated twice for injection " + m ); } if ( m.isSynthetic() || Modifier.isStatic( m.getModifiers() ) ) { // method is static throw new RuntimeException( "Wrong modifier for anotated method " + m ); } BeanLevelInfo leaf = new BeanLevelInfo(); leaf.parent = this; if ( annotationInjectionDeep != null ) { Type getterClass = isGetter( m ); if ( getterClass == null ) { throw new RuntimeException( "Method should be getter: " + m ); } if ( m.getReturnType() != null && List.class.equals( m.getReturnType() ) ) { // returns list leaf.dim = DIMENSION.LIST; ParameterizedType getterType = (ParameterizedType) getterClass; getterClass = getterType.getActualTypeArguments()[0]; } Class<?> getter = (Class<?>) resolveGenericType( getterClass, genericsInfo ); if ( getter.isArray() ) { throw new RuntimeException( "Method should be getter: " + m ); } leaf.getter = m; leaf.leafClass = getter; leaf.prefix = annotationInjectionDeep.prefix(); leaf.init( info ); } else { Class<?> setterClass = isSetter( m ); if ( setterClass == null || setterClass.isArray() ) { throw new RuntimeException( "Method should be setter: " + m ); } leaf.setter = m; leaf.leafClass = setterClass; try { leaf.converter = annotationInjection.converter().newInstance(); } catch ( Exception ex ) { throw new RuntimeException( "Error instantiate converter for " + m, ex ); } leaf.convertEmpty = annotationInjection.convertEmpty(); info.addInjectionProperty( annotationInjection, leaf ); } } } /** * Resolves class by generic map if required(when type is TypeVariable). */ private Type resolveGenericType( Type type, Map<String, Type> genericsInfo ) { if ( type instanceof TypeVariable ) { String name = ( (TypeVariable<?>) type ).getName(); type = genericsInfo.get( name ); if ( type == null ) { throw new RuntimeException( "Unknown generics for '" + name + "'" ); } } return type; } private Type isGetter( Method m ) { if ( m.getReturnType() == void.class ) { return null; } if ( m.getParameterTypes().length == 0 ) { // getter without parameters return m.getGenericReturnType(); } return null; } private Class<?> isSetter( Method m ) { if ( m.getReturnType() != void.class ) { return null; } if ( m.getParameterTypes().length == 1 ) { // setter with one parameter return m.getParameterTypes()[0]; } return null; } protected List<BeanLevelInfo> createCallStack() { List<BeanLevelInfo> stack = new ArrayList<>(); BeanLevelInfo p = this; while ( p != null ) { if ( p.field != null ) { p.field.setAccessible( true ); } stack.add( p ); p = p.parent; } Collections.reverse( stack ); return stack; } @Override public String toString() { String r = ""; if ( field != null ) { r += "field " + field.getName(); } else { r += "<root field>"; } r += "(class " + leafClass.getSimpleName() + ")"; return r; } }