Package org.jinq.jpa.transform

Source Code of org.jinq.jpa.transform.LambdaAnalysis

package org.jinq.jpa.transform;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.objectweb.asm.Handle;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.analysis.AnalyzerException;

import ch.epfl.labos.iu.orm.queryll2.path.MethodAnalysisResults;
import ch.epfl.labos.iu.orm.queryll2.path.PathAnalysisFactory;
import ch.epfl.labos.iu.orm.queryll2.path.PathAnalysisSimplifier;
import ch.epfl.labos.iu.orm.queryll2.path.TransformationClassAnalyzer;
import ch.epfl.labos.iu.orm.queryll2.symbolic.TypedValue;

import com.user00.thunk.SerializedLambda;

/**
* Holds analysis information about the code for a lambda.
*/
public class LambdaAnalysis
{
   public static class LambdaAsClassAnalysisConfig
   {
      private static int countObjectParameters(Method m)
      {
         int count = 0;
         if (m.getReturnType().getName().equals("java.lang.Object")) count++;
         for (Class<?> c: m.getParameterTypes())
            if (c.getName().equals("java.lang.Object")) count++;
         return count;
      }
     
      public Method findLambdaMethod(Class<?> lambdaClass)
            throws AnalyzerException
      {
         String methodName = "apply";
         Method matchingMethod = null;
         Method[] classMethods = lambdaClass.getDeclaredMethods();
         for (Method m: classMethods)
         {
            if (m.getName().matches(methodName + "\\$mc[^$]*\\$sp"))
            {
               matchingMethod = m;
               break;
            }
            if (!m.getName().equals(methodName)) continue;
            if (matchingMethod != null)
            {
               // Try to choose the most specific method
               int newCount = countObjectParameters(m);
               int oldCount = countObjectParameters(matchingMethod);
               if (newCount == oldCount)
                  throw new AnalyzerException(null, "Multiple methods have the expected name for the lambda");
               if (newCount > oldCount) continue;
            }
            matchingMethod = m;
         }
         return matchingMethod;
      }
     
      public int getNumberOfLambdaArguments(Class<?> c)
      {
         try {
            if (Class.forName("scala.Function1").isAssignableFrom(c))
               return 1;
            else if (Class.forName("scala.Function1").isAssignableFrom(c))
               return 2;
         }
         catch (ClassNotFoundException e)
         {
            throw new IllegalArgumentException("Cannot find Scala classes", e);
         }
         throw new IllegalArgumentException("Cannot determine number of arguments to the lambda function.");
      }
   }
  
   private int numCapturedArgs;
   private int numLambdaArgs;
   /**
    * JPAQueryComposer stores the lambdas that are chained together to create a query in a list.
    * The lambdaIndex refers to the index of this lambda in the list of lambdas used to create
    * the resulting query.
    */
   private int lambdaIndex;
   /**
    * Some lambdas are from sublambdas inside other lambdas. In that
    * case, we don't have the actual captured args, but we know how the
    * captured args are determined from the parent.
    */
   private List<TypedValue> indirectCapturedArgs;
   /**
    * Some lambdas are from sublambdas inside other lambdas, and the parameters
    * to these sublambdas may be stored as fields in the sublambda. We don't
    * have the actual data for the fields, but we have the indirect mapping of
    * these fields to variables in the parent lambda.
    */
   private Map<String, TypedValue> indirectParamFields;
   /**
    * Scala lambdas are actual Java classes and objects. Lambda parameters
    * are stored as fields of the object (as opposed to as extra method
    * arguments). This variable is true if this lambda format is used.
    */
   private boolean usesParametersAsFields;
   MethodAnalysisResults symbolicAnalysis;

   public static LambdaAnalysis fullyAnalyzeClassAsLambda(LambdaInfo lambdaInfo, LambdaAsClassAnalysisConfig lambdaAsClassConfig, int numLambdaArgs, MetamodelUtil metamodel, ClassLoader alternateClassLoader, boolean isObjectEqualsSafe, boolean throwExceptionOnFailure)
   {
      try {
         MethodAnalysisResults analysis = analyzeLambdaClass(lambdaInfo.Lambda.getClass(), metamodel, lambdaAsClassConfig, lambdaInfo.Lambda.getClass().getClassLoader(), isObjectEqualsSafe);
         if (analysis == null)
         {
            if (throwExceptionOnFailure) throw new IllegalArgumentException("Could not analyze lambda code");
            return null;
         }
         return new LambdaAnalysis(lambdaInfo.Lambda, analysis, numLambdaArgs, lambdaInfo.lambdaIndex);
      }
      catch (Exception e)
      {
         if (throwExceptionOnFailure) throw new IllegalArgumentException("Could not analyze lambda code", e);
         return null;
      }
   }

   /**
    * Used to analyze a lambda when we only have the name of the class used as the lambda
    * and not an actual reference to the lambda.
    */
   public static LambdaAnalysis analyzeClassAsLambda(MetamodelUtil metamodel, ClassLoader alternateClassLoader, boolean isObjectEqualsSafe, LambdaAsClassAnalysisConfig lambdaAsClassConfig, String className, Map<String, TypedValue> indirectParamMapping, boolean throwExceptionOnFailure)
   {
      try {
         Class<?> c = Class.forName(className);
         MethodAnalysisResults analysis = analyzeLambdaClass(c, metamodel, lambdaAsClassConfig, alternateClassLoader, isObjectEqualsSafe);
         if (analysis == null)
         {
            if (throwExceptionOnFailure) throw new IllegalArgumentException("Could not analyze lambda code");
            return null;
         }
         return new LambdaAnalysis(analysis, indirectParamMapping, lambdaAsClassConfig.getNumberOfLambdaArguments(c));
      }
      catch (IOException e)
      {
         if (throwExceptionOnFailure) throw new IllegalArgumentException("Encountered problems when trying to load the code for your lambdas. You may need to supply a lambdaClassLoader hint to Jinq to help it find your lambdas.", e);
         return null;
      }
      catch (AnalyzerException e)
      {
         if (throwExceptionOnFailure) throw new IllegalArgumentException("Could not analyze lambda code", e);
         return null;
      } catch (ClassNotFoundException e)
      {
         if (throwExceptionOnFailure) throw new IllegalArgumentException("Could not load the class of the lambda", e);
         return null;
      }
   }

   public static LambdaAnalysis fullyAnalyzeLambda(LambdaInfo lambdaInfo, MetamodelUtil metamodel, ClassLoader alternateClassLoader, boolean isObjectEqualsSafe, boolean throwExceptionOnFailure)
   {
      // TODO: The part below will need to be moved to a separate method.
      //   That way, we can used the serialized lambda info to check if
      //   we've cached the results of this analysis already without needing
      //   to redo all this analysis.
      SerializedLambda s = lambdaInfo.serializedLambda;
      try {
         if (s == null) return null;
         MethodAnalysisResults analysis = analyzeLambda(metamodel, alternateClassLoader, isObjectEqualsSafe, s.implClass, s.implMethodName, s.implMethodSignature);
         if (analysis == null)
         {
            if (throwExceptionOnFailure) throw new IllegalArgumentException("Could not analyze lambda code");
            return null;
         }
         return new LambdaAnalysis(lambdaInfo.Lambda, s, analysis, lambdaInfo.lambdaIndex);
      }
      catch (Exception e)
      {
         if (throwExceptionOnFailure) throw new IllegalArgumentException("Could not analyze lambda code", e);
         return null;
      }
   }

   /**
    * Used to analyze a lambda when we only have the name of the method used in the lambda
    * and not an actual reference to the lambda.
    */
   public static LambdaAnalysis analyzeMethod(MetamodelUtil metamodel, ClassLoader alternateClassLoader, boolean isObjectEqualsSafe, Handle lambdaHandle, List<TypedValue> indirectCapturedArgs, boolean throwExceptionOnFailure)
   {
      // TODO: The part below will need to be moved to a separate method.
      //   That way, we can used the serialized lambda info to check if
      //   we've cached the results of this analysis already without needing
      //   to redo all this analysis.
      try {
         MethodAnalysisResults analysis = analyzeLambda(metamodel, alternateClassLoader, isObjectEqualsSafe, lambdaHandle.getOwner(), lambdaHandle.getName(), lambdaHandle.getDesc());
         if (analysis == null)
         {
            if (throwExceptionOnFailure) throw new IllegalArgumentException("Could not analyze lambda code");
            return null;
         }
         // TODO: Handle lambda arguments properly
         return new LambdaAnalysis(analysis, indirectCapturedArgs, Type.getArgumentTypes(lambdaHandle.getDesc()).length);
      }
      catch (IOException e)
      {
         if (throwExceptionOnFailure) throw new IllegalArgumentException("Encountered problems when trying to load the code for your lambdas. You may need to supply a lambdaClassLoader hint to Jinq to help it find your lambdas.", e);
         return null;
      }
      catch (AnalyzerException e)
      {
         if (throwExceptionOnFailure) throw new IllegalArgumentException("Could not analyze lambda code", e);
         return null;
      }
   }

   private static MethodAnalysisResults analyzeLambda(MetamodelUtil metamodel, ClassLoader alternateClassLoader, boolean isObjectEqualsSafe, String className, String methodName, String methodSignature) throws IOException, AnalyzerException
   {
      // Open up the corresponding class to analyze
      PathAnalysisFactory pathAnalysisFactory = new PathAnalysisFactory(
            metamodel.getMethodChecker(isObjectEqualsSafe));
      TransformationClassAnalyzer classAnalyzer =
            new TransformationClassAnalyzer(className, alternateClassLoader);
      MethodAnalysisResults analysis = classAnalyzer.analyzeLambdaMethod(methodName, methodSignature, pathAnalysisFactory);
      PathAnalysisSimplifier.cleanAndSimplify(analysis, metamodel.getComparisonMethods(isObjectEqualsSafe));
      return analysis;
   }

   private static MethodAnalysisResults analyzeLambdaClass(Class<?> lambdaClass, MetamodelUtil metamodel, LambdaAsClassAnalysisConfig lambdaAsClass, ClassLoader alternateClassLoader, boolean isObjectEqualsSafe) throws IOException, AnalyzerException
   {
      // Open up the corresponding class to analyze
      TransformationClassAnalyzer classAnalyzer =
            new TransformationClassAnalyzer(lambdaClass.getName(), alternateClassLoader);
      Method matchingMethod = lambdaAsClass.findLambdaMethod(lambdaClass);
      if (matchingMethod == null)
         throw new AnalyzerException(null, "Could not find a lambda method with the expected name in the class");
      PathAnalysisFactory pathAnalysisFactory = new PathAnalysisFactory(
            metamodel.getMethodChecker(isObjectEqualsSafe));
      MethodAnalysisResults analysis = classAnalyzer.analyzeLambdaMethod(matchingMethod.getName(), Type.getMethodDescriptor(matchingMethod), pathAnalysisFactory);
      PathAnalysisSimplifier.cleanAndSimplify(analysis, metamodel.getComparisonMethods(isObjectEqualsSafe));
      return analysis;
   }
  
   LambdaAnalysis(Object lambda, SerializedLambda serializedLambda, MethodAnalysisResults symbolicAnalysis, int lambdaIndex)
   {
      this.numCapturedArgs = serializedLambda.capturedArgs.length;
      this.numLambdaArgs = Type.getArgumentTypes(serializedLambda.implMethodSignature).length;
      this.lambdaIndex = lambdaIndex;
      this.symbolicAnalysis = symbolicAnalysis;
      this.indirectCapturedArgs = null;
      this.usesParametersAsFields = false;
   }

   LambdaAnalysis(MethodAnalysisResults symbolicAnalysis, List<TypedValue> indirectCapturedArgs, int numLambdaArgs)
   {
      this.numCapturedArgs = indirectCapturedArgs.size();
      this.numLambdaArgs = numLambdaArgs;
      this.lambdaIndex = -1;
      this.symbolicAnalysis = symbolicAnalysis;
      this.indirectCapturedArgs = indirectCapturedArgs;
      this.usesParametersAsFields = false;
   }

   LambdaAnalysis(Object lambda, MethodAnalysisResults symbolicAnalysis, int numLambdaArgs, int lambdaIndex)
   {
      this.numCapturedArgs = 0;
      this.numLambdaArgs = numLambdaArgs;
      this.lambdaIndex = lambdaIndex;
      this.symbolicAnalysis = symbolicAnalysis;
      this.indirectCapturedArgs = null;
      this.usesParametersAsFields = true;
   }

   LambdaAnalysis(MethodAnalysisResults symbolicAnalysis, Map<String, TypedValue> indirectParamFields, int numLambdaArgs)
   {
      this.numCapturedArgs = 0;
      this.numLambdaArgs = numLambdaArgs;
      this.lambdaIndex = -1;
      this.symbolicAnalysis = symbolicAnalysis;
      this.indirectCapturedArgs = null;
      this.indirectParamFields = indirectParamFields;
      this.usesParametersAsFields = true;
   }

   public boolean usesIndirectArgs()
   {
      return indirectCapturedArgs != null;
   }

   public int getNumCapturedArgs()
   {
      return numCapturedArgs;
   }

   public int getNumLambdaArgs()
   {
      return numLambdaArgs;
   }

   public TypedValue getIndirectCapturedArg(int argIndex)
   {
      return indirectCapturedArgs.get(argIndex);
   }
  
   public int getLambdaIndex()
   {
      return lambdaIndex;
   }

   public TypedValue getIndirectFieldValue(String name)
   {
      return indirectParamFields.get(name);
   }

   public boolean usesIndirectFields()
   {
      return indirectParamFields != null;
   }
  
   public boolean usesParametersAsFields()
   {
      return usesParametersAsFields;
   }
}
TOP

Related Classes of org.jinq.jpa.transform.LambdaAnalysis

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.