/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.gosu.ir.transform.expression; import gw.internal.gosu.ir.nodes.IRMethod; import gw.internal.gosu.ir.nodes.IRMethodFactory; import gw.internal.gosu.ir.transform.ExpressionTransformer; import gw.internal.gosu.ir.transform.TopLevelTransformationContext; import gw.internal.gosu.ir.transform.statement.ForEachStatementTransformer; import gw.internal.gosu.parser.Symbol; import gw.internal.gosu.parser.TypeLord; import gw.lang.ir.IRExpression; import gw.lang.ir.IRStatement; import gw.lang.ir.IRSymbol; import gw.lang.ir.expression.IRNoOpExpression; import gw.lang.ir.statement.IRForEachStatement; import gw.lang.ir.statement.IRStatementList; import gw.lang.parser.expressions.IMemberAccessExpression; import gw.lang.reflect.IRelativeTypeInfo; import gw.lang.reflect.IType; import gw.lang.reflect.gs.IGosuClass; import gw.lang.reflect.gs.IGosuObject; import gw.lang.reflect.java.IJavaType; import gw.lang.reflect.java.JavaTypes; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; /** */ public abstract class AbstractMemberExpansionTransformer<T extends IMemberAccessExpression> extends AbstractExpressionTransformer<T> { protected AbstractMemberExpansionTransformer( TopLevelTransformationContext cc, T expr ) { super( cc, expr ); } /** * Subclassers need only implement this method for the iteration expression i.e., the singular form of the expansion expr. */ protected abstract IRExpression createIterationExpr(IType rootComponentType, String identifierName, IType identifierType, IType compType); protected abstract IType getPropertyOrMethodType(IType rootComponentType, IType compType); protected IRExpression compile_impl() { IType arrayType = _expr().getType(); final boolean bVoid = arrayType == JavaTypes.pVOID(); if( !bVoid && !arrayType.isArray() ) { throw new IllegalStateException( "Expecting an array type. Found: " + arrayType.getName() ); } final IType compType = bVoid ? arrayType : getConcreteType( arrayType.getComponentType() ); IType rootType = _expr().getRootType(); final IType rootComponentType = TypeLord.getExpandableComponentType( rootType ); IType propertyType = getPropertyOrMethodType(rootComponentType, compType); _cc().pushScope( false ); try { if (bVoid) { // This is like calling root.each(\r -> r.foo()) return compileExpansionWithNoReturnValue( rootType, rootComponentType, arrayType, compType ); } else if(isBytecodeType( propertyType ) && isArrayOrCollection( rootType ) && !isArrayOrCollection( rootComponentType ) && !isArrayOrCollection( propertyType )) { // This is like calling root.map(\r -> r.Bar) return compileExpansionDirectlyToArray( rootType, rootComponentType, arrayType, compType ); } else { // This is like calling root.flatMap(\r -> r.Bar) return compileExpansionUsingArrayList( rootType, rootComponentType, arrayType, compType, propertyType ); } } finally { _cc().popScope(); } } private static boolean isArrayOrCollection( IType type ) { return type.isArray() || JavaTypes.COLLECTION().isAssignableFrom( type ); } protected IRExpression compileExpansionWithNoReturnValue( IType rootType, IType rootComponentType, IType resultType, IType resultCompType ) { // Evaluate the root and assign it to a temp variable IRSymbol tempRoot = _cc().makeAndIndexTempSymbol( getDescriptor( rootType ) ); IRStatement tempRootAssignment = buildAssignment( tempRoot, ExpressionTransformer.compile( _expr().getRootExpression(), _cc() ) ); IRForEachStatement statement = createNoValueLoop( rootType, rootComponentType, resultCompType, tempRoot ); return buildComposite( tempRootAssignment, statement, new IRNoOpExpression() ); } private IRForEachStatement createNoValueLoop(IType rootType, IType rootComponentType, IType resultCompType, IRSymbol tempRoot ) { Symbol loopIdentifier = new Symbol(_cc().makeTempSymbolName(), rootComponentType, null); IRForEachStatement forLoop = ForEachStatementTransformer.makeLoop( _cc(), identifier( tempRoot ), rootType, loopIdentifier, null); IRSymbol irLoopIdentifier = _cc().getSymbol( loopIdentifier.getName() ); forLoop.setBody( new IRStatementList( false, buildMethodCall( createIterationExpr( rootComponentType, irLoopIdentifier.getName(), rootComponentType, resultCompType ) ) ) ); return forLoop; } /** * If this method is being called, it means we're expanding a one-dimensional array or collection, with a right hand side * that evaluates to a property that's not an array or collection. In that case, we build up an array and simply store * values directly into it. We also null-short-circuit in the event that the root is null. The member expansion portion * ends up as a composite that looks like: * * temp_array = new Foo[temp_root.length] * for (a in temp_root index i) { * temp_array[i] = a.Bar * } * temp_array * * And the overall expression looks like: * * temp_root = root * ( temp_root == null ? (Bar[]) null : (Bar[]) member_expansion ) */ protected IRExpression compileExpansionDirectlyToArray( IType rootType, IType rootComponentType, IType resultType, IType resultCompType ) { // Evaluate the root and assign it to a temp variable IRSymbol tempRoot = _cc().makeAndIndexTempSymbol( getDescriptor( rootType ) ); IRStatement tempRootAssignment = buildAssignment( tempRoot, ExpressionTransformer.compile( _expr().getRootExpression(), _cc() ) ); // Create the result array and assign it to a temp variable IRSymbol resultArray = _cc().makeAndIndexTempSymbol( getDescriptor( resultType ) ); IRStatement arrayCreation = buildAssignment( resultArray, makeArray( resultCompType, createArrayLengthExpression( rootType, tempRoot ) ) ); // Create the loop that populates the array IRForEachStatement forLoop = createArrayStoreLoop(rootType, rootComponentType, resultCompType, tempRoot, resultArray); // Build the expansion out of the array creation, for loop, and identifier IRExpression expansion = buildComposite( arrayCreation, forLoop, identifier( resultArray ) ); // Short-circuit if we're not dealing with primitive types if (!rootComponentType.isPrimitive() && !resultCompType.isPrimitive()) { return buildComposite( tempRootAssignment, buildNullCheckTernary( identifier( tempRoot ), checkCast( _expr().getType(), pushNull() ), checkCast( _expr().getType(), expansion ) ) ); } else { return buildComposite( tempRootAssignment, checkCast( _expr().getType(), expansion ) ); } } private IRExpression makeArray( IType componentType, IRExpression lengthExpression ) { if( componentType.isPrimitive() || componentType instanceof IGosuClass || componentType instanceof IJavaType ) { // Gosu/Java arrays are directly represented in bytecode, so we can directly construct a Java array from the component type return newArray( getDescriptor( componentType ), lengthExpression ); } // Custom array types may not have direct representation in bytecode e.g., whacky entities IRExpression array = callMethod( IType.class, "makeArrayInstance", new Class[]{int.class}, pushType( componentType ), exprList( lengthExpression ) ); return checkCast( componentType.getArrayType(), array ); } private IRExpression createArrayLengthExpression(IType rootType, IRSymbol tempRoot) { // We either need to do foo.length or foo.size() depending on if we've got an array or a collection if ( rootType.isArray() ) { return buildArrayLength( identifier( tempRoot ) ); } else if (JavaTypes.COLLECTION().isAssignableFrom( rootType ) ) { IRMethod irMethod = IRMethodFactory.createIRMethod( rootType, "size", JavaTypes.pINT(), new IType[0], IRelativeTypeInfo.Accessibility.PUBLIC, false ); return callMethod( irMethod, identifier( tempRoot ), exprList()); } else { throw new IllegalArgumentException( "Cannot get the size of type " + rootType ); } } private IRForEachStatement createArrayStoreLoop(IType rootType, IType rootComponentType, IType resultCompType, IRSymbol tempRoot, IRSymbol resultArray) { // The body of the loop looks like: // temp_array[i] = l.Bar Symbol loopIdentifier = new Symbol(_cc().makeTempSymbolName(), rootComponentType, null); Symbol loopIndex = new Symbol(_cc().makeTempSymbolName(), JavaTypes.pINT(), null); IRForEachStatement forLoop = ForEachStatementTransformer.makeLoop( _cc(), identifier( tempRoot ), rootType, loopIdentifier, loopIndex); IRSymbol irLoopIdentifier = _cc().getSymbol( loopIdentifier.getName() ); IRSymbol irLoopIndex = _cc().getSymbol( loopIndex.getName() ); forLoop.setBody( buildArrayStore( identifier( resultArray), identifier( irLoopIndex ), createIterationExpr( rootComponentType, irLoopIdentifier.getName(), rootComponentType, resultCompType ), resultArray.getType().getComponentType() ) ); return forLoop; } /** * This method will compile the expansion using an ArrayList to collect temporary results. This is appropriate if the right-hand-side * is a Collection or array, if the root is an Iterable or Iterator or other object whose size can't easily be determined up-front, * or if the root is a nested Collection or array that will require additional unwrapping. * * The overall result of the expansion thus looks like the following composite: * * temp_arraylist = new ArrayList() * for (a in temp_root) { * temp_arraylist.addAll( AbstractMemberExpansionTransform.arrayToCollection( a.Bars ) ) * } * AbstractMemberExpansionTransformer.listToArray(temp_array) * */ protected IRExpression compileExpansionUsingArrayList( IType rootType, IType rootComponentType, IType resultType, IType resultCompType, IType propertyType ) { // Evaluate the root and assign it to a temp variable IRSymbol tempRoot = _cc().makeAndIndexTempSymbol( getDescriptor( rootType ) ); IRStatement tempRootAssignment = buildAssignment( tempRoot, ExpressionTransformer.compile( _expr().getRootExpression(), _cc() ) ); // Create an ArrayList to store the results IRSymbol resultArrayList = _cc().makeAndIndexTempSymbol( getDescriptor( JavaTypes.ARRAY_LIST() ) ); IRStatement arrayListCreation = buildAssignment(resultArrayList, buildNewExpression( ArrayList.class, new Class[0], exprList() ) ); // Now we loop over each element in the root array or collection. For each element, we evaluate the // RHS, wrap it by calling arrayToCollection, then add it to the result list via addAll IRForEachStatement forLoop = createArrayListAddLoop(rootType, rootComponentType, resultCompType, tempRoot, resultArrayList, propertyType ); // Now take the ArrayList and convert it to the desired return type IRExpression listToArrayConversion = convertListToArray(resultType, resultCompType, resultArrayList); return buildComposite( tempRootAssignment, arrayListCreation, forLoop, listToArrayConversion ); } private IRForEachStatement createArrayListAddLoop(IType rootType, IType rootComponentType, IType resultCompType, IRSymbol tempRoot, IRSymbol resultArrayList, IType propertyType) { Symbol loopIdentifier = new Symbol(_cc().makeTempSymbolName(), rootComponentType, null); IRForEachStatement forLoop = ForEachStatementTransformer.makeLoop( _cc(), identifier( tempRoot ), rootType, loopIdentifier, null); IRSymbol irLoopIdentifier = _cc().getSymbol( loopIdentifier.getName() ); // If the right hand side is an array or collection, then wrap it up as a Collection and call addAll on the set we're building up. // Otherwise, box it and call the add method. IRStatement loopBody; if ( propertyType.isArray() ) { IRExpression resultAsCollection = callStaticMethod( AbstractMemberExpansionTransformer.class, "arrayToCollection", new Class[]{Object.class}, exprList( createIterationExpr( rootComponentType, irLoopIdentifier.getName(), rootComponentType, resultCompType) ) ); loopBody = buildMethodCall( callMethod( ArrayList.class, "addAll", new Class[]{Collection.class}, identifier( resultArrayList ), exprList( resultAsCollection ) ) ); } else { loopBody = buildMethodCall( callMethod( ArrayList.class, "add", new Class[]{Object.class}, identifier( resultArrayList ), exprList( boxValueToType( propertyType, createIterationExpr( rootComponentType, irLoopIdentifier.getName(), rootComponentType, resultCompType) ) ) ) ); } forLoop.setBody( new IRStatementList( false, loopBody ) ); return forLoop; } private IRExpression convertListToArray(IType resultType, IType resultCompType, IRSymbol resultArrayList) { if( resultCompType.isPrimitive() ) { return convertToPrimitiveArray( resultCompType, identifier( resultArrayList ) ); } else { IType arrayComponentType = getMoreSpecificType(resultCompType, resultType.getComponentType()); IRExpression listToArrayCall = callStaticMethod( AbstractMemberExpansionTransformer.class, "listToArray", new Class[]{List.class, IType.class}, exprList( identifier( resultArrayList ), pushType( arrayComponentType ) )); return checkCast( arrayComponentType.getArrayType(), listToArrayCall ); } } private IType getMoreSpecificType(IType type1, IType type2) { if (type1.isAssignableFrom(type2)) { return type2; } else if (type2.isAssignableFrom(type1)){ return type1; } else { return type1; } } private IRExpression convertToPrimitiveArray( IType compType, IRExpression listToConvert ) { if( compType == JavaTypes.pBOOLEAN() ) { return callStaticMethod( AbstractMemberExpansionTransformer.class, "listToPrimitiveArray_boolean", new Class[]{List.class}, exprList( listToConvert ) ); } else if( compType == JavaTypes.pBYTE() ) { return callStaticMethod( AbstractMemberExpansionTransformer.class, "listToPrimitiveArray_byte", new Class[]{List.class}, exprList( listToConvert ) ); } else if( compType == JavaTypes.pCHAR() ) { return callStaticMethod( AbstractMemberExpansionTransformer.class, "listToPrimitiveArray_char", new Class[]{List.class}, exprList( listToConvert ) ); } else if( compType == JavaTypes.pDOUBLE() ) { return callStaticMethod( AbstractMemberExpansionTransformer.class, "listToPrimitiveArray_double", new Class[]{List.class}, exprList( listToConvert ) ); } else if( compType == JavaTypes.pFLOAT() ) { return callStaticMethod( AbstractMemberExpansionTransformer.class, "listToPrimitiveArray_float", new Class[]{List.class}, exprList( listToConvert ) ); } else if( compType == JavaTypes.pINT() ) { return callStaticMethod( AbstractMemberExpansionTransformer.class, "listToPrimitiveArray_int", new Class[]{List.class}, exprList( listToConvert ) ); } else if( compType == JavaTypes.pLONG() ) { return callStaticMethod( AbstractMemberExpansionTransformer.class, "listToPrimitiveArray_long", new Class[]{List.class}, exprList( listToConvert ) ); } else if( compType == JavaTypes.pSHORT() ) { return callStaticMethod( AbstractMemberExpansionTransformer.class, "listToPrimitiveArray_short", new Class[]{List.class}, exprList( listToConvert ) ); } else { throw new UnsupportedOperationException( "Don't know how to handle primitive type: " + compType.getName() ); } } public static boolean[] listToPrimitiveArray_boolean( List l ) { int iCount = l.size(); boolean[] array = new boolean[iCount]; for( int i = 0; i < iCount; i++ ) { array[i] = ((Boolean)l.get( i )).booleanValue(); } return array; } public static byte[] listToPrimitiveArray_byte( List l ) { int iCount = l.size(); byte[] array = new byte[iCount]; for( int i = 0; i < iCount; i++ ) { array[i] = ((Byte)l.get( i )).byteValue(); } return array; } public static char[] listToPrimitiveArray_char( List l ) { int iCount = l.size(); char[] array = new char[iCount]; for( int i = 0; i < iCount; i++ ) { array[i] = ((Character)l.get( i )).charValue(); } return array; } public static int[] listToPrimitiveArray_int( List l ) { int iCount = l.size(); int[] array = new int[iCount]; for( int i = 0; i < iCount; i++ ) { array[i] = ((Integer)l.get( i )).intValue(); } return array; } public static short[] listToPrimitiveArray_short( List l ) { int iCount = l.size(); short[] array = new short[iCount]; for( int i = 0; i < iCount; i++ ) { array[i] = ((Short)l.get( i )).shortValue(); } return array; } public static long[] listToPrimitiveArray_long( List l ) { int iCount = l.size(); long[] array = new long[iCount]; for( int i = 0; i < iCount; i++ ) { array[i] = ((Long)l.get( i )).longValue(); } return array; } public static float[] listToPrimitiveArray_float( List l ) { int iCount = l.size(); float[] array = new float[iCount]; for( int i = 0; i < iCount; i++ ) { array[i] = ((Float)l.get( i )).floatValue(); } return array; } public static double[] listToPrimitiveArray_double( List l ) { int iCount = l.size(); double[] array = new double[iCount]; for( int i = 0; i < iCount; i++ ) { array[i] = ((Double)l.get( i )).doubleValue(); } return array; } public static Object listToArray( List l, IType compType ) { int iCount = l.size(); Object array = compType.makeArrayInstance( iCount ); for( int i = 0; i < iCount; i++ ) { compType.setArrayComponent( array, i, l.get( i ) ); } return array; } public static Collection arrayToCollection( Object value ) { if (value == null) { return Collections.emptyList(); } else if (value instanceof Collection) { return (Collection) value; } else if (value instanceof Object[]) { return Arrays.asList((Object[]) value); } else if (value instanceof IGosuObject) { List col = new ArrayList(); IType arrayType = ((IGosuObject)value).getIntrinsicType(); int iLength = arrayType.getArrayLength( value ); for( int j = 0; j < iLength; j++ ) { //noinspection unchecked col.add( arrayType.getArrayComponent( value, j ) ); } return col; } else if( value.getClass().isArray() && value.getClass().getComponentType().isPrimitive() ) { List col = new ArrayList(); for( int i = 0; i < Array.getLength( value ); i++ ) { //noinspection unchecked col.add( Array.get( value, i ) ); } return col; } else { throw new IllegalArgumentException("Cannot turn value of type " + value.getClass() + " into an array"); } } }