/*! ******************************************************************************
*
* 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;
}
}