/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2017 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.Array;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import org.pentaho.di.core.RowMetaAndData;
import org.pentaho.di.core.exception.KettleException;
/**
* Engine for get/set metadata injection properties from bean.
*/
public class BeanInjector {
private final BeanInjectionInfo info;
public BeanInjector( BeanInjectionInfo info ) {
this.info = info;
}
public Object getProperty( Object root, String propName ) throws Exception {
List<Integer> extractedIndexes = new ArrayList<>();
BeanInjectionInfo.Property prop = info.getProperties().get( propName );
if ( prop == null ) {
throw new RuntimeException( "Property not found" );
}
Object obj = root;
for ( int i = 1, arrIndex = 0; i < prop.path.size(); i++ ) {
BeanLevelInfo s = prop.path.get( i );
obj = s.field.get( obj );
if ( obj == null ) {
return null; // some value in path is null - return empty
}
switch ( s.dim ) {
case ARRAY:
int indexArray = extractedIndexes.get( arrIndex++ );
if ( Array.getLength( obj ) <= indexArray ) {
return null;
}
obj = Array.get( obj, indexArray );
if ( obj == null ) {
return null; // element is empty
}
break;
case LIST:
int indexList = extractedIndexes.get( arrIndex++ );
List<?> list = (List<?>) obj;
if ( list.size() <= indexList ) {
return null;
}
obj = list.get( indexList );
if ( obj == null ) {
return null; // element is empty
}
break;
case NONE:
break;
}
}
return obj;
}
public boolean hasProperty( Object root, String propName ) {
BeanInjectionInfo.Property prop = info.getProperties().get( propName );
return prop != null;
}
public void setProperty( Object root, String propName, List<RowMetaAndData> data, String dataN )
throws KettleException {
BeanInjectionInfo.Property prop = info.getProperties().get( propName );
if ( prop == null ) {
throw new KettleException( "Property '" + propName + "' not found for injection to " + root.getClass() );
}
String dataName, dataValue;
if ( data != null ) {
dataName = dataN;
dataValue = null;
} else {
dataName = null;
dataValue = dataN;
}
if ( prop.pathArraysCount == 0 ) {
// no arrays in path
try {
setProperty( root, prop, 0, data != null ? data.get( 0 ) : null, dataName, dataValue );
} catch ( Exception ex ) {
throw new KettleException( "Error inject property '" + propName + "' into " + root.getClass(), ex );
}
} else if ( prop.pathArraysCount == 1 ) {
// one array in path
try {
if ( data != null ) {
for ( int i = 0; i < data.size(); i++ ) {
setProperty( root, prop, i, data.get( i ), dataName, dataValue );
}
} else {
for ( int i = 0;; i++ ) {
boolean found = setProperty( root, prop, i, null, null, dataValue );
if ( !found ) {
break;
}
}
}
} catch ( Exception ex ) {
throw new KettleException( "Error inject property '" + propName + "' into " + root.getClass(), ex );
}
} else {
if ( prop.pathArraysCount > 1 ) {
throw new KettleException( "Property '" + propName + "' has more than one array in path for injection to "
+ root.getClass() );
}
}
}
/**
* Sets data from RowMetaAndData, or constant value from dataValue depends on 'data != null'.
*/
private boolean setProperty( Object root, BeanInjectionInfo.Property prop, int index, RowMetaAndData data,
String dataName, String dataValue ) throws Exception {
Object obj = root;
for ( int i = 1; i < prop.path.size(); i++ ) {
BeanLevelInfo s = prop.path.get( i );
if ( i < prop.path.size() - 1 ) {
// get path
Object next;
switch ( s.dim ) {
case ARRAY:
// array
Object existArray = data != null ? extendArray( s, obj, index + 1 ) : checkArray( s, obj, index );
if ( existArray == null ) {
// out of array for constant
return false;
}
next = Array.get( existArray, index ); // get specific element
if ( next == null ) {
next = createObject( s.leafClass, root );
Array.set( existArray, index, next );
}
obj = next;
break;
case LIST:
// list
List<Object> existList = data != null ? extendList( s, obj, index + 1 ) : checkList( s, obj, index );
if ( existList == null ) {
// out of array for constant
return false;
}
next = existList.get( index ); // get specific element
if ( next == null ) {
next = createObject( s.leafClass, root );
existList.set( index, next );
}
obj = next;
break;
case NONE:
// plain field
if ( s.field != null ) {
next = s.field.get( obj );
if ( next == null ) {
next = createObject( s.leafClass, root );
s.field.set( obj, next );
}
obj = next;
} else if ( s.getter != null ) {
next = s.getter.invoke( obj );
if ( next == null ) {
if ( s.setter == null ) {
throw new KettleException( "No setter defined for " + root.getClass() );
}
next = s.leafClass.newInstance();
s.setter.invoke( obj, next );
}
obj = next;
} else {
throw new KettleException( "No field or getter defined for " + root.getClass() );
}
break;
}
} else {
// set to latest field
if ( !s.convertEmpty ) {
if ( data != null ) {
if ( data.isEmptyValue( dataName ) ) {
return true;
}
} else {
if ( dataValue == null ) {
return true;
}
}
}
if ( s.setter != null ) {
// usual setter
Object value;
if ( data != null ) {
value = data.getAsJavaType( dataName, s.leafClass, s.converter );
} else {
value = RowMetaAndData.getStringAsJavaType( dataValue, s.leafClass, s.converter );
}
s.setter.invoke( obj, value );
} else if ( s.field != null ) {
Object value;
if ( data != null ) {
value = data.getAsJavaType( dataName, s.leafClass, s.converter );
} else {
value = RowMetaAndData.getStringAsJavaType( dataValue, s.leafClass, s.converter );
}
switch ( s.dim ) {
case ARRAY:
Object existArray = data != null ? extendArray( s, obj, index + 1 ) : checkArray( s, obj, index );
if ( existArray == null ) {
// out of array for constant
return false;
}
Array.set( existArray, index, value );
break;
case LIST:
List<Object> existList = data != null ? extendList( s, obj, index + 1 ) : checkList( s, obj, index );
if ( existList == null ) {
// out of array for constant
return false;
}
existList.set( index, value );
break;
case NONE:
s.field.set( obj, value );
break;
}
} else {
throw new KettleException( "No field or setter defined for " + root.getClass() );
}
}
}
return true;
}
private Object createObject( Class<?> clazz, Object root ) throws KettleException {
try {
// Object can be inner of metadata class. In this case constructor will require parameter
for ( Constructor<?> c : clazz.getConstructors() ) {
if ( c.getParameterTypes().length == 0 ) {
return clazz.newInstance();
} else if ( c.getParameterTypes().length == 1 && c.getParameterTypes()[0].isAssignableFrom( info.clazz ) ) {
return c.newInstance( root );
}
}
} catch ( Throwable ex ) {
throw new KettleException( "Can't create object " + clazz, ex );
}
throw new KettleException( "Constructor not found for " + clazz );
}
private Object extendArray( BeanLevelInfo s, Object obj, int newSize ) throws Exception {
Object existArray = s.field.get( obj );
if ( existArray == null ) {
existArray = Array.newInstance( s.leafClass, newSize );
s.field.set( obj, existArray );
}
int existSize = Array.getLength( existArray );
if ( existSize < newSize ) {
Object newSized = Array.newInstance( s.leafClass, newSize );
System.arraycopy( existArray, 0, newSized, 0, existSize );
existArray = newSized;
s.field.set( obj, existArray );
}
return existArray;
}
private Object checkArray( BeanLevelInfo s, Object obj, int index ) throws Exception {
Object existArray = s.field.get( obj );
if ( existArray == null ) {
return null;
}
int existSize = Array.getLength( existArray );
return index < existSize ? existArray : null;
}
private List<Object> extendList( BeanLevelInfo s, Object obj, int newSize ) throws Exception {
@SuppressWarnings( "unchecked" )
List<Object> existList = (List<Object>) s.field.get( obj );
if ( existList == null ) {
existList = new ArrayList<>();
s.field.set( obj, existList );
}
while ( existList.size() < newSize ) {
existList.add( null );
}
return existList;
}
private List<Object> checkList( BeanLevelInfo s, Object obj, int index ) throws Exception {
@SuppressWarnings( "unchecked" )
List<Object> existList = (List<Object>) s.field.get( obj );
if ( existList == null ) {
return null;
}
return index < existList.size() ? existList : null;
}
}