diff --git a/pom.xml b/pom.xml index fc4d73c..467cb11 100644 --- a/pom.xml +++ b/pom.xml @@ -10,8 +10,8 @@ UTF-8 - 1.8 - 1.8.2 + 11 + 1.9.5 @@ -25,13 +25,6 @@ maven-model 2.2.1 - - com.sun.tools - tools - ${java.version} - system - ${java.home}/../lib/tools.jar - org.aspectj aspectjrt @@ -56,9 +49,10 @@ com.amazonaws aws-java-sdk-lambda - 1.11.60 + 1.11.774 + @@ -66,10 +60,10 @@ org.apache.maven.plugins maven-compiler-plugin - 3.6.0 + 3.8.1 - 1.8 - 1.8 + 11 + 11 -proc:none diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/annotations/LambdaProcessor.java b/src/main/java/ch/zhaw/splab/podilizerproc/annotations/LambdaProcessor.java index e6e9ab2..b6863ae 100644 --- a/src/main/java/ch/zhaw/splab/podilizerproc/annotations/LambdaProcessor.java +++ b/src/main/java/ch/zhaw/splab/podilizerproc/annotations/LambdaProcessor.java @@ -2,6 +2,8 @@ import ch.zhaw.splab.podilizerproc.awslambda.Functions; import ch.zhaw.splab.podilizerproc.awslambda.LambdaFunction; +import ch.zhaw.splab.podilizerproc.depdencies.CompilationUnitInfo; +import ch.zhaw.splab.podilizerproc.depdencies.DependencyResolver; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.MethodTree; @@ -10,25 +12,33 @@ import com.sun.source.util.TreePathScanner; import com.sun.source.util.Trees; -import javax.annotation.processing.*; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; -import javax.tools.Diagnostic; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.util.Types; import javax.tools.JavaFileObject; import java.io.IOException; import java.io.Writer; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; /** * Processor of {@link Lambda} annotation */ @SupportedAnnotationTypes({"ch.zhaw.splab.podilizerproc.annotations.Lambda"}) public class LambdaProcessor extends AbstractProcessor { + private Trees trees; + private Types typeUtils; /** - * Initialization of {@link ProcessEnvironment} object and {@link Trees} object + * Initialization. * * @param processingEnv */ @@ -36,18 +46,31 @@ public class LambdaProcessor extends AbstractProcessor { public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); trees = Trees.instance(processingEnv); + typeUtils = processingEnv.getTypeUtils(); } public boolean process(Set annotations, RoundEnvironment roundEnv) { - ; + // TODO: For a normal maven compile call this class / method is called twice. + // This method should somehow detect if it was already run and should avoid executing twice. + if (roundEnv.errorRaised()) { + System.out.println("[TERMITE] Code not compilable, skipping processing for now..."); + return false; + } + if (roundEnv.processingOver()) { + System.out.println("[TERMITE] Nothing left todo, skipping processing..."); + return false; + } + + List functions = new ArrayList<>(); Set annotatedMethods = roundEnv.getElementsAnnotatedWith(Lambda.class); - if (annotatedMethods.size() == 0) { + if (annotatedMethods.isEmpty()) { return true; } + DependencyResolver dependencyResolver = new DependencyResolver(trees, roundEnv); for (Element element : annotatedMethods) { @@ -55,6 +78,7 @@ public boolean process(Set annotations, RoundEnvironment TypeScanner typeScanner = new TypeScanner(); CUVisitor cuVisitor = new CUVisitor(); + Set requiredCompilationUnits = dependencyResolver.resolveDependencies(element); TreePath tp = trees.getPath(element); methodScanner.scan(tp, trees); @@ -63,15 +87,24 @@ public boolean process(Set annotations, RoundEnvironment TreePath tp1 = trees.getPath(getMostExternalType(element)); cuVisitor.visit(tp1, trees); + ExecutableType emeth = (ExecutableType) element.asType(); Lambda lambda = element.getAnnotation(Lambda.class); LambdaFunction lambdaFunction = - new LambdaFunction(methodScanner.getMethod(), typeScanner.getClazz(), cuVisitor.getCu(), lambda); + new LambdaFunction(methodScanner.getMethod(), + typeScanner.getClazz(), + cuVisitor.getCu(), + lambda, + requiredCompilationUnits, + emeth.getParameterTypes(), + emeth.getReturnType()); functions.add(lambdaFunction); + try { String packageName = lambdaFunction.generateInputPackage(); + System.out.println("[TERMITE] Generating local Data Classes. For Package: " + packageName); String generatedClassPath = packageName.substring(8, packageName.length() - 1); - JavaFileObject inputType = processingEnv.getFiler().createSourceFile(generatedClassPath +".InputType", null); + JavaFileObject inputType = processingEnv.getFiler().createSourceFile(generatedClassPath + ".InputType", null); JavaFileObject outputType = processingEnv.getFiler().createSourceFile(generatedClassPath + ".OutputType", null); Writer writer = inputType.openWriter(); Writer writer1 = outputType.openWriter(); @@ -81,8 +114,11 @@ public boolean process(Set annotations, RoundEnvironment writer1.append(lambdaFunction.createOutputType()); writer.flush(); writer1.flush(); + writer.close(); + writer1.close(); + System.out.println("[TERMITE] Successfully generated local sources."); } catch (IOException e) { - e.printStackTrace(); + System.out.println("[TERMITE] Failed to generate local sources. They probably already exist."); } } diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/aspect/Invoke.java b/src/main/java/ch/zhaw/splab/podilizerproc/aspect/Invoke.java index a511ecf..0824857 100644 --- a/src/main/java/ch/zhaw/splab/podilizerproc/aspect/Invoke.java +++ b/src/main/java/ch/zhaw/splab/podilizerproc/aspect/Invoke.java @@ -1,12 +1,37 @@ package ch.zhaw.splab.podilizerproc.aspect; -import ch.zhaw.splab.podilizerproc.awslambda.InvokeThread; +import ch.zhaw.splab.podilizerproc.annotations.Lambda; +import ch.zhaw.splab.podilizerproc.awslambda.AwsCredentialsReader; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.lambda.AWSLambda; +import com.amazonaws.services.lambda.AWSLambdaClientBuilder; +import com.amazonaws.services.lambda.model.InvokeRequest; +import com.amazonaws.services.lambda.model.InvokeResult; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.*; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; @Aspect public class Invoke { @@ -14,12 +39,168 @@ public class Invoke { @Around("@annotation(lambda)") public Object anyExec(ProceedingJoinPoint joinPoint, ch.zhaw.splab.podilizerproc.annotations.Lambda lambda) throws Throwable { + System.out.println("[TERMITE] Annotation invoked"); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); - InvokeThread invokeThread = new InvokeThread(method, lambda, joinPoint); - invokeThread.start(); - return null; + return invokeOnLambda(method, lambda, joinPoint); } + private Object invokeOnLambda(Method method, Lambda lambda, ProceedingJoinPoint joinPoint) throws Throwable { + Class inClazz = null; + Class outClazz = null; + try { + inClazz = Class.forName(getInputPackage(method)); + outClazz = Class.forName(getOutputPackage(method)); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + AwsCredentialsReader credentialsReader = new AwsCredentialsReader(); + credentialsReader.read(); + String awsAccessKeyId = credentialsReader.getAwsAccessKeyId(); + String awsSecretKeyAccessKey = credentialsReader.getAwsSecretAccessKey(); + String regionName = lambda.region(); + String functionName = getFunctionName(method); + + AWSCredentials credentials = new BasicAWSCredentials(awsAccessKeyId, awsSecretKeyAccessKey); + Regions region = Regions.fromName(regionName); + + + AWSLambdaClientBuilder clientBuilder = AWSLambdaClientBuilder + .standard() + .withCredentials(new AWSStaticCredentialsProvider(credentials)) + .withRegion(region); + + if (!lambda.endPoint().equals("")) { + clientBuilder = clientBuilder.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(lambda.endPoint(), regionName)); + } + AWSLambda awsLambda = clientBuilder.build(); + + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + String json = ""; + try { + List ctorArgs = new ArrayList<>(); + if (!Modifier.isStatic(joinPoint.getSignature().getModifiers())) { + ctorArgs.add(joinPoint.getTarget()); + } + ctorArgs.addAll(Arrays.asList(joinPoint.getArgs())); + + + Constructor correctCtor = null; + for (Constructor constructor : inClazz.getConstructors()) { + if (constructor.getParameterCount() == ctorArgs.size()) { + correctCtor = constructor; + break; + } + } + + Object inputObj = correctCtor.newInstance(ctorArgs.toArray()); + json = objectMapper.writeValueAsString(inputObj); + // TODO: Remove these verbose printouts (Or make log level configurable) + System.out.println("[TERMITE] Sending json input."); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | JsonProcessingException e) { + e.printStackTrace(); + } + Object outObj = null; + Object methodResult = null; + + try { + InvokeRequest invokeRequest = new InvokeRequest(); + invokeRequest.setFunctionName(functionName); + invokeRequest.setPayload(json); + System.out.println("[TERMITE] Starting "); + InvokeResult invokeResult = awsLambda.invoke(invokeRequest); + String resultJson = byteBufferToString(invokeResult.getPayload(), StandardCharsets.UTF_8); + System.out.println("[TERMITE] Received json output." + + " Size: " + invokeResult.getSdkHttpMetadata().getAllHttpHeaders().get("Content-Length")); + System.out.flush(); + try { + outObj = objectMapper.readValue(resultJson, outClazz); + objectMapper.readerForUpdating(outObj).readValue(resultJson); + } catch (Throwable t) { + System.out.println("[TERMITE] Failed to deserialize result."); + t.printStackTrace(); + } + // TODO: Remove these verbose printouts (Or make log level configurable) + + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + if (outObj != null && signature != null && !void.class.equals(signature.getReturnType())) { + // GetResult is only generated for non void methods + Method getResultMethod = outObj.getClass().getDeclaredMethod("getResult"); + methodResult = getResultMethod.invoke(outObj); + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("Function " + method.getName() + " is unreachable. Processing locally..."); + + // Note: While using the joinPoint to proceed normal execution, the code will sometimes be executed twice. + // This is a known AspectJ Bug documented here: https://github.com/spring-projects/spring-framework/issues/24046 + methodResult = joinPoint.proceed(); + } + awsLambda.shutdown(); + + try { + if (outObj != null) { + String functionReport = "Thread of Function " + method.getName() + " invocation was finished. " + + "Function performed at - " + outObj.getClass().getDeclaredMethod("getDefaultReturn", null).invoke(outObj) + + " - for " + outObj.getClass().getDeclaredMethod("getTime", null).invoke(outObj) + " ms"; + System.out.println(functionReport); + } else { + System.out.println("Processed " + method.getName() + " locally with result " + methodResult); + } + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + + return methodResult; + } + + + /** + * Generates package name for input type based on method signature + * + * @param method is method signature object + * @return {@link String} package name of InputType + */ + private String getInputPackage(Method method) { + String fullClassName = method.getDeclaringClass().getName(); + return "aws." + fullClassName + "." + method.getName() + method.getParameterCount() + ".InputType"; + } + + /** + * Generates package name for output type based on method signature + * + * @param method is method signature object + * @return {@link String} package name of OutputType + */ + private String getOutputPackage(Method method) { + String fullClassName = method.getDeclaringClass().getName(); + return "aws." + fullClassName + "." + method.getName() + method.getParameterCount() + ".OutputType"; + } + + /** + * Generates function name for annotated method over the load process + * + * @param method is annotated method to generate lambda function name for + * @return {@link String} name of format 'package_class_method_#argsNumber' + */ + private String getFunctionName(Method method) { + String result = method.getDeclaringClass().getName().replace('.', '_'); + result += "_" + method.getName(); + result += method.getParameterCount(); + return result; + } + + private static String byteBufferToString(ByteBuffer buffer, Charset charset) { + byte[] bytes; + if (buffer.hasArray()) { + bytes = buffer.array(); + } else { + bytes = new byte[buffer.remaining()]; + buffer.get(bytes); + } + return new String(bytes, charset); + } } diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/Filer.java b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/Filer.java index 969d12d..0546e24 100644 --- a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/Filer.java +++ b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/Filer.java @@ -42,7 +42,7 @@ public Path getPath() { if (cu.getPackageName() == null) { packageName = "aws"; } else { - packageName = "aws/" + cu.getPackageName().toString().replace(',', '/'); + packageName = "aws/" + cu.getPackageName().toString().replace('.', '/'); } clazzName = clazz.getSimpleName().toString(); @@ -71,7 +71,7 @@ public Path getPomPath() { if (cu.getPackageName() == null) { packageName = "aws"; } else { - packageName = "aws/" + cu.getPackageName().toString().replace(',', '/'); + packageName = "aws/" + cu.getPackageName().toString().replace('.', '/'); } clazzName = clazz.getSimpleName().toString(); diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/Functions.java b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/Functions.java index ae40c48..dd3d278 100644 --- a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/Functions.java +++ b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/Functions.java @@ -1,14 +1,14 @@ package ch.zhaw.splab.podilizerproc.awslambda; +import ch.zhaw.splab.podilizerproc.depdencies.CompilationUnitInfo; import org.codehaus.plexus.util.FileUtils; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; +import javax.tools.JavaFileObject; +import java.io.*; import java.net.URL; import java.net.URLClassLoader; import java.util.List; +import java.util.Set; public class Functions { List functions; @@ -46,6 +46,8 @@ public void write(){ printWriter2.print(function.createOutputType()); printWriter2.close(); + writeRequiredCompilationUnits(path, function); + ClassLoader cl = getClass().getClassLoader(); URLClassLoader urlcl = (URLClassLoader)cl; URL[] classPath = urlcl.getURLs(); @@ -57,9 +59,10 @@ public void write(){ jarBuilder.mvnBuild(); - JarUploader jarUploader = new JarUploader(function.getLambdaFunctionName(), - function.getAwsFiler().getPomPath().toString() + "/target/lambda-java-1.0-SNAPSHOT.jar", - "LambdaFunction::handleRequest", + String lamdaJarLocation = function.getAwsFiler().getPomPath().toString(); + lamdaJarLocation = lamdaJarLocation.replace('\\', '/') + "/target/lambda-java-1.0-SNAPSHOT.jar"; + + JarUploader jarUploader = new JarUploader(function.getLambdaFunctionName(), lamdaJarLocation, "LambdaFunction::handleRequest", function.getLambdaAnnotation().region(), function.getLambdaAnnotation().timeOut(), function.getLambdaAnnotation().memorySize(), function.getLambdaAnnotation().endPoint()); jarUploader.uploadFunction(); @@ -69,6 +72,45 @@ public void write(){ } } + + private void writeRequiredCompilationUnits(String path, LambdaFunction function) { + Set requiredCompilationUnits = function.getRequiredCompilationUnits(); + for (CompilationUnitInfo compilationUnit : requiredCompilationUnits) { + String packageName = compilationUnit.getPackageName(); + String absoluteFilePath = path + File.separatorChar + packageName.replace('.', File.separatorChar); + absoluteFilePath = absoluteFilePath + File.separatorChar + compilationUnit.getName() + ".java"; + JavaFileObject sourceFile = compilationUnit.getSourceFile(); + + File targetFile = new File(absoluteFilePath); + boolean wasCreated = false; + try { + targetFile.getParentFile().mkdirs(); + wasCreated = targetFile.createNewFile(); + } catch (IOException e) { + System.out.println("[TERMITE] Unable to create new source file at " + absoluteFilePath); + e.printStackTrace(); + continue; + } + if (!wasCreated) { + System.out.println("[TERMITE] WARNING: File already exists " + absoluteFilePath); + } + + try(Reader reader = sourceFile.openReader(true); + BufferedReader bufferedReader = new BufferedReader(reader); + PrintWriter writer = new PrintWriter(targetFile)) { + bufferedReader + .lines() + .map(line -> line.replaceAll("@\\s*Lambda(\\s*\\([^)]*\\))?", "")) + .forEach(writer::println); + } catch (IOException e) { + System.out.println("[TERMITE] Failed to copy required compilation unit to " + absoluteFilePath); + e.printStackTrace(); + } + + } + + } + // TODO: 3/28/17 recreate getting of external libraries(include maven dependencies) private void writeExternalCP(String pathOut){ ClassLoader cl = getClass().getClassLoader(); diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/InputTypeEntity.java b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/InputTypeEntity.java index 75c328d..dc39cc0 100644 --- a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/InputTypeEntity.java +++ b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/InputTypeEntity.java @@ -1,8 +1,11 @@ package ch.zhaw.splab.podilizerproc.awslambda; +import com.sun.source.tree.MethodTree; import com.sun.source.tree.VariableTree; -import javafx.util.Pair; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeMirror; +import java.util.AbstractMap; import java.util.List; /** @@ -10,11 +13,31 @@ */ public class InputTypeEntity extends PoJoEntity { - public InputTypeEntity(String className, List params) { + /** This "weird" name is used to avoid any unintentional clashes with real variables */ + public static final String EXPLICIT_THIS_FIELD = "termiteExplicitThis"; + + public InputTypeEntity(String className, String qualifiedParentType, MethodTree method, List inputTypes) { super(className); + if (!method.getModifiers().getFlags().contains(Modifier.STATIC)) { + // Add an explicit this reference for non static methods + importStatments.add(qualifiedParentType); + String[] split = qualifiedParentType.split("\\."); + String parentSimpleName = split[split.length - 1]; + fields.add(new AbstractMap.SimpleEntry<>(parentSimpleName, EXPLICIT_THIS_FIELD)); + } for (VariableTree var : - params) { - fields.add(new Pair<>(var.getType().toString(), var.getName().toString())); + method.getParameters()) { + fields.add(new AbstractMap.SimpleEntry<>(var.getType().toString(), var.getName().toString())); + } + for (TypeMirror inputType : inputTypes) { + String typeString = inputType.toString(); + if (typeString.contains(".")) { + if (typeString.contains("<")) { + importStatments.addAll(resolveGenericsFromImport(typeString)); + } else { + importStatments.add(typeString); + } + } } } } diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/InvokeThread.java b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/InvokeThread.java deleted file mode 100644 index 7f86535..0000000 --- a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/InvokeThread.java +++ /dev/null @@ -1,152 +0,0 @@ -package ch.zhaw.splab.podilizerproc.awslambda; - -import ch.zhaw.splab.podilizerproc.annotations.Lambda; -import com.amazonaws.auth.AWSCredentials; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.regions.Region; -import com.amazonaws.regions.Regions; -import com.amazonaws.services.lambda.AWSLambdaClient; -import com.amazonaws.services.lambda.model.InvokeRequest; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import org.aspectj.lang.ProceedingJoinPoint; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; - -public class InvokeThread extends Thread { - private Method method; - private Lambda lambda; - private ProceedingJoinPoint joinPoint; - - public InvokeThread(Method method, Lambda lambda, ProceedingJoinPoint joinPoint) { - super(); - this.method = method; - this.lambda = lambda; - this.joinPoint = joinPoint; - } - - @Override - public void run() { - Class inClazz = null; - Class outClazz = null; - try { - inClazz = Class.forName(getInputPackage(method)); - outClazz = Class.forName(getOutputPackage(method)); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - AwsCredentialsReader credentialsReader = new AwsCredentialsReader(); - credentialsReader.read(); - String awsAccessKeyId = credentialsReader.getAwsAccessKeyId(); - String awsSecretKeyAccessKey = credentialsReader.getAwsSecretAccessKey(); - String regionName = lambda.region(); - String functionName = getFunctionName(method); - - AWSCredentials credentials = new BasicAWSCredentials(awsAccessKeyId, awsSecretKeyAccessKey); - Region region = Region.getRegion(Regions.fromName(regionName)); - AWSLambdaClient lambdaClient = new AWSLambdaClient(credentials); - lambdaClient.setRegion(region); - - if (!lambda.endPoint().equals("")){ - lambdaClient.setEndpoint(lambda.endPoint()); - } - - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); - String json = ""; - try { - Object inputObj = inClazz.getConstructors()[0].newInstance(joinPoint.getArgs()); - json = objectMapper.writeValueAsString(inputObj); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } - Object outObj = null; - try { - InvokeRequest invokeRequest = new InvokeRequest(); - invokeRequest.setFunctionName(functionName); - invokeRequest.setPayload(json); - outObj = objectMapper.readValue(byteBufferToString(lambdaClient.invoke(invokeRequest).getPayload(), - Charset.forName("UTF-8")), outClazz); - } catch (Exception e) { - e.printStackTrace(); - System.out.println("Function " + method.getName() + " is unreachable. Processing locally..."); - try { - Object tmp = joinPoint.proceed(); - } catch (Throwable throwable) { - throwable.printStackTrace(); - } - - } - try { - String functionReport = "Thread of Function " + method.getName() + " invocation was finished. " + - "Function performed at - " + outObj.getClass().getDeclaredMethod("getDefaultReturn", null).invoke(outObj) + - " - for " + outObj.getClass().getDeclaredMethod("getTime", null).invoke(outObj) + " ms"; - if (!method.getReturnType().toString().equals("void")){ - functionReport += "; Return value is: " + outObj.getClass().getDeclaredMethod("getResult", null).invoke(outObj); - } - System.out.println(functionReport); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - - /** - * Generates package name for input type based on method signature - * - * @param method is method signature object - * @return {@link String} package name of InputType - */ - private String getInputPackage(Method method) { - String fullClassName = method.getDeclaringClass().getName(); - return "aws." + fullClassName + "." + method.getName() + method.getParameterCount() + ".InputType"; - } - - /** - * Generates package name for output type based on method signature - * - * @param method is method signature object - * @return {@link String} package name of OutputType - */ - private String getOutputPackage(Method method) { - String fullClassName = method.getDeclaringClass().getName(); - return "aws." + fullClassName + "." + method.getName() + method.getParameterCount() + ".OutputType"; - } - - /** - * Generates function name for annotated method over the load process - * - * @param method is annotated method to generate lambda function name for - * @return {@link String} name of format 'package_class_method_#argsNumber' - */ - private String getFunctionName(Method method) { - String result = method.getDeclaringClass().getName().replace('.', '_'); - result += "_" + method.getName(); - result += method.getParameterCount(); - return result; - } - - public static String byteBufferToString(ByteBuffer buffer, Charset charset) { - byte[] bytes; - if (buffer.hasArray()) { - bytes = buffer.array(); - } else { - bytes = new byte[buffer.remaining()]; - buffer.get(bytes); - } - return new String(bytes, charset); - } -} diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/JarBuilder.java b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/JarBuilder.java index 46cce14..3ea3741 100644 --- a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/JarBuilder.java +++ b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/JarBuilder.java @@ -46,7 +46,11 @@ public void setPath(String path) { Invoker invoker = new DefaultInvoker(); try { if (invoker.getMavenHome() == null) { - invoker.setMavenHome(new File("/usr/share/maven/")); + String mavenHome = System.getenv("MAVEN_HOME"); + if (mavenHome == null || mavenHome.isEmpty()) { + mavenHome = "/usr/share/maven/"; + } + invoker.setMavenHome(new File(mavenHome)); } //log the build output to file PrintStream printStream = new PrintStream(buildLog); diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/JarUploader.java b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/JarUploader.java index f8753d2..911ed6d 100644 --- a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/JarUploader.java +++ b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/JarUploader.java @@ -9,7 +9,7 @@ class JarUploader { private String region; - private String runtime = "java8"; + private String runtime = "java11"; private String role; private String functionName; private String zipFile; @@ -48,7 +48,7 @@ public void run() { try { if (command.startsWith("aws sts")) { //System.out.println("\n\naws sts output: " + input.readLine() + "\n\n"); - role = "arn:aws:iam::" + input.readLine() + ":role/lambda_basic_execution"; + role = "arn:aws:iam::" + input.readLine() + ":role/service-role/lambda_basic_execution"; return; } // while ((line = input.readLine()) != null) @@ -108,6 +108,7 @@ private String getCommand() { if (!endPoint.equals("")){ result += " --endpoint-url " + endPoint; } + System.out.println("[TERMITE] Building Command:\n" + result); return result; } diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/LambdaFunction.java b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/LambdaFunction.java index 6b6545b..8f94d3d 100644 --- a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/LambdaFunction.java +++ b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/LambdaFunction.java @@ -1,39 +1,43 @@ package ch.zhaw.splab.podilizerproc.awslambda; import ch.zhaw.splab.podilizerproc.annotations.Lambda; +import ch.zhaw.splab.podilizerproc.depdencies.CompilationUnitInfo; import com.sun.source.tree.*; import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeMirror; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Set; /** - *Entity of the AWS lambda function + * Entity of the AWS lambda function */ public class LambdaFunction { - private MethodTree method; - private ClassTree clazz; - private CompilationUnitTree cu; - private List fields = new ArrayList<>(); - private Filer awsFiler; - private Lambda lambdaAnnotation; - - public LambdaFunction(MethodTree method, ClassTree clazz, CompilationUnitTree cu, Lambda lambdaAnnotation) { + private final MethodTree method; + private final ClassTree clazz; + private final CompilationUnitTree cu; + private final Filer awsFiler; + private final Lambda lambdaAnnotation; + private final Set requiredCompilationUnits; + private final List inputTypes; + private final TypeMirror resultType; + + public LambdaFunction(MethodTree method, + ClassTree clazz, + CompilationUnitTree cu, + Lambda lambdaAnnotation, + Set requiredCompilationUnits, + List inputTypes, + TypeMirror resultType) { this.method = method; this.clazz = clazz; this.cu = cu; this.lambdaAnnotation = lambdaAnnotation; - for (Tree tree : - clazz.getMembers()) { - if (tree.getKind() == Tree.Kind.VARIABLE) { - VariableTree field = (VariableTree)tree; - //exclude static and final fields from list - if (!field.getModifiers().getFlags().contains(Modifier.STATIC) & - !field.getModifiers().getFlags().contains(Modifier.FINAL)){ - fields.add(field); - } - } - } + this.requiredCompilationUnits = requiredCompilationUnits; + this.inputTypes = inputTypes; + this.resultType = resultType; awsFiler = new Filer(cu, clazz, method); } @@ -47,40 +51,42 @@ public Filer getAwsFiler() { /** * Generates java code lambda function for appropriate method + * * @return {@link String} of generated java code */ - public String create(){ + public String create() { String result = importsToString(imports()); result += "\n" + getClassSpecification(); - //result += "\n" + Utility.fieldsToString(fields); result += "\n" + generateHandler(); - result += "\n" + removeAnnotations(method); return result + "\n}"; } /** * Creates InputType class java code + * * @return java code of InputType class as a {@link String} */ - public String createInputType(){ - InputTypeEntity inputType = new InputTypeEntity("InputType", method.getParameters()); + public String createInputType() { + InputTypeEntity inputType = new InputTypeEntity("InputType", getQualifiedParentType(), method, inputTypes); return inputType.create(); } /** * Creates OutputType class java code + * * @return java code of OutputType class as a {@link String} */ - public String createOutputType(){ - OutputTypeEntity outputType = new OutputTypeEntity("OutputType", method.getReturnType()); + public String createOutputType() { + OutputTypeEntity outputType = new OutputTypeEntity("OutputType", method.getReturnType(), resultType); return outputType.create(); } /** * Creates list of imports to be added to lambda function compilation unit + * * @return {@link String[]} of imports; */ - public List imports(){ + public List imports() { List imports = new ArrayList<>(); String[] defaultImports = { "com.amazonaws.services.lambda.runtime.Context", @@ -89,29 +95,34 @@ public List imports(){ "com.amazonaws.services.lambda.runtime.RequestStreamHandler", "com.amazonaws.util.IOUtils", "com.fasterxml.jackson.databind.*", - "com.amazonaws.services.lambda.runtime.RequestHandler" + "com.amazonaws.services.lambda.runtime.RequestHandler", + "com.fasterxml.jackson.annotation.JsonAutoDetect", + "com.fasterxml.jackson.annotation.PropertyAccessor;" }; -// for (ImportTree importTree : -// cu.getImports()) { -// imports.add(importTree.getQualifiedIdentifier().toString()); -// } - for (String importStr : - defaultImports) { - imports.add(importStr); + for (ImportTree importTree : + cu.getImports()) { + imports.add(importTree.getQualifiedIdentifier().toString()); + } + if (cu.getPackageName() != null) { + // Import the target class itself, to make direct use of the annotated method + imports.add(getQualifiedParentType()); } + + Collections.addAll(imports, defaultImports); return imports; } /** * Turns list of imports into string of java code + * * @param imports is list of imports(Strings) * @return {@link String} of combined imports ready ti insert as java code */ - private String importsToString(List imports){ + private String importsToString(List imports) { StringBuilder result = new StringBuilder(); for (String importStr : imports) { - result.append("import " + importStr + ";\n"); + result.append("import ").append(importStr).append(";\n"); } return String.valueOf(result); } @@ -119,16 +130,17 @@ private String importsToString(List imports){ /** * Generates lambda function class specification. Based on 'implements' and 'extends' of external class + * * @return {@link String} of class declaration */ - private String getClassSpecification(){ + private String getClassSpecification() { String result = "public class LambdaFunction"; String implementsString = "implements RequestStreamHandler"; - for (Tree implement: + for (Tree implement : clazz.getImplementsClause()) { implementsString += ", " + implement.toString(); } - if (clazz.getExtendsClause() == null){ + if (clazz.getExtendsClause() == null) { return result + " " + implementsString + " {"; } String extendsString = "extends " + clazz.getExtendsClause().toString(); @@ -136,27 +148,28 @@ private String getClassSpecification(){ return result + " " + extendsString + " " + implementsString + " {"; } + public Set getRequiredCompilationUnits() { + return requiredCompilationUnits; + } + /** * Generates handler code + * * @return {@link String} of handler java code */ - private String generateHandler(){ + private String generateHandler() { String result = "\tpublic void handleRequest(InputStream inputStream, OutputStream outputStream, " + "Context context) throws IOException {\n" + "\t\tlong time = System.currentTimeMillis();" + "\t\tObjectMapper objectMapper = new ObjectMapper();\n" + + "\t\tobjectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);\n" + "\t\tobjectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);\n" + "\t\tString inputString = IOUtils.toString(inputStream);\n" + "\t\tInputType inputType = objectMapper.readValue(inputString, InputType.class);\n"; -// for (VariableTree field : -// fields) { -// String var = field.getName().toString(); -// result += "\t\tthis." + var + " = inputType.get" + Utility.firstLetterToUpperCase(var) + "();\n"; -// } - result += "\t\t" + generateMethodCall() + ";\n"; + result += "\t\t" + generateMethodCall(); result += "\t\tOutputType outputType = new OutputType(\"Lambda environment\""; result += ", System.currentTimeMillis() - time"; - if (!method.getReturnType().toString().equals("void")){ + if (!method.getReturnType().toString().equals("void")) { result += ", result"; } result += ");\n"; @@ -166,40 +179,29 @@ private String generateHandler(){ } - /** - * Removes "@Lambda" and "@Invoker" annotations from method and adds "\t" to every string - * @param method to be formatted - * @return {@link String} of formatted method - */ - private String removeAnnotations(MethodTree method){ - String methodString = method.toString(); - String[] lines = methodString.split("\n"); - StringBuilder result = new StringBuilder(); - for (int i = 0; i < lines.length; i++){ - if (!lines[i].startsWith("@Lambda") && !lines[i].startsWith("@Invoker")){ - result.append("\t"); - result.append(lines[i]); - result.append("\n"); - } - } - return String.valueOf(result); - } - /** * Generates method call expression for lambda class + * * @return java code line as {@link String} */ - private String generateMethodCall(){ + private String generateMethodCall() { + // TODO: Handle non static methods by adding another field to input type containing the target object AND + // another field to the output type which will contain the modified object String result = ""; - if (!method.getReturnType().toString().equals("void")){ - result += "" + method.getReturnType().toString() + " result = "; + if (!method.getReturnType().toString().equals("void")) { + result += "" + method.getReturnType().toString() + " result = "; + } + if (method.getModifiers().getFlags().contains(Modifier.STATIC)) { + result += clazz.getSimpleName(); + } else { + result += "inputType.getTermiteExplicitThis()"; } - result += method.getName().toString() + "("; + result += "." + method.getName().toString() + "("; int i = 0; for (VariableTree param : - method.getParameters()){ + method.getParameters()) { String getCall = "inputType.get" + Utility.firstLetterToUpperCase(param.getName().toString()) + "()"; - if (i == 0){ + if (i == 0) { result += getCall; } else { result += ", " + getCall; @@ -212,9 +214,10 @@ private String generateMethodCall(){ /** * Generates package declaration line for InputType + * * @return generated {@link String} with format 'aws.originalPackage.originalClass.originalMethod#argsNumber' */ - public String generateInputPackage(){ + public String generateInputPackage() { String result = "package aws."; result += getLambdaFunctionName().replace('_', '.'); return result + ";"; @@ -222,17 +225,18 @@ public String generateInputPackage(){ /** * Generates name for lambda function + * * @return {@link String} in format 'originalPackage_originalClass_originalMethod#argsNumber' */ - public String getLambdaFunctionName(){ + public String getLambdaFunctionName() { String result = ""; - if (cu.getPackageName() != null){ + if (cu.getPackageName() != null) { result += cu.getPackageName().toString().replace('.', '_'); result += "_"; } result += clazz.getSimpleName(); result += "_" + method.getName().toString(); - if (method.getParameters() != null){ + if (method.getParameters() != null) { result += method.getParameters().size(); } return result; @@ -250,4 +254,8 @@ public String toString() { ", cu=" + cu + '}'; } + + private String getQualifiedParentType() { + return cu.getPackageName().toString() + "." + clazz.getSimpleName(); + } } diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/OutputTypeEntity.java b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/OutputTypeEntity.java index 94e9edf..58f133a 100644 --- a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/OutputTypeEntity.java +++ b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/OutputTypeEntity.java @@ -1,19 +1,29 @@ package ch.zhaw.splab.podilizerproc.awslambda; import com.sun.source.tree.Tree; -import javafx.util.Pair; + +import javax.lang.model.type.TypeMirror; +import java.util.AbstractMap; /** * PoJo class entity for output type */ public class OutputTypeEntity extends PoJoEntity { - public OutputTypeEntity(String className, Tree returnType) { + public OutputTypeEntity(String className, Tree returnType, TypeMirror resultType) { super(className); - fields.add(new Pair<>("String", "defaultReturn")); - fields.add(new Pair<>("long", "time")); + fields.add(new AbstractMap.SimpleEntry<>("String", "defaultReturn")); + fields.add(new AbstractMap.SimpleEntry<>("long", "time")); if (!returnType.toString().equals("void")) { - fields.add(new Pair<>(returnType.toString(), "result")); + fields.add(new AbstractMap.SimpleEntry<>(returnType.toString(), "result")); + } + String resultTypeStr = resultType.toString(); + if (resultTypeStr.contains(".")) { + if (resultTypeStr.contains("<")) { + importStatments.addAll(resolveGenericsFromImport(resultTypeStr)); + } else { + importStatments.add(resultTypeStr); + } } } } diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/PoJoEntity.java b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/PoJoEntity.java index dca8c84..18ff600 100644 --- a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/PoJoEntity.java +++ b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/PoJoEntity.java @@ -1,13 +1,17 @@ package ch.zhaw.splab.podilizerproc.awslambda; -import javafx.util.Pair; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; public abstract class PoJoEntity { private String className; - protected List> fields; + protected List importStatments = new ArrayList<>(); + protected List> fields; public PoJoEntity(String className) { this.className = className; @@ -15,7 +19,8 @@ public PoJoEntity(String className) { } public String create() { - String result = "public class " + className + "{\n"; + String result = generateImportStatements(); + result += "public class " + className + "{\n"; result += generateFieldsDeclaration() + "\n"; result += generateDefaultConstructor(); if (fields.size() != 0){ @@ -27,6 +32,13 @@ public String create() { return result; } + private String generateImportStatements() { + StringBuilder stringBuilder = new StringBuilder(); + importStatments.forEach(statement -> stringBuilder.append("import ").append(statement).append(";\n")); + stringBuilder.append("\n"); // Make it look nice :) + return stringBuilder.toString(); + } + /** * Generates default constructor * @@ -46,7 +58,7 @@ private String generateConstructor() { String result = "\tpublic " + className + "("; String constructorBody = ""; int i = 0; - for (Pair entry : fields) { + for (AbstractMap.SimpleEntry entry : fields) { String var = entry.getKey() + " " + entry.getValue(); if (i == 0) { result += var; @@ -67,7 +79,7 @@ private String generateConstructor() { */ private String generateGetters() { String result = ""; - for (Pair entry : fields) { + for (AbstractMap.SimpleEntry entry : fields) { result += "\tpublic " + entry.getKey() + " get" + Utility.firstLetterToUpperCase(entry.getValue() + "(){\n" + "\t\treturn " + entry.getValue() + ";\n\t}\n"); @@ -82,7 +94,7 @@ private String generateGetters() { */ private String generateSetters() { String result = ""; - for (Pair entry : fields) { + for (AbstractMap.SimpleEntry entry : fields) { result += "\tpublic void set" + Utility.firstLetterToUpperCase(entry.getValue() + "(" + entry.getKey() + " " + entry.getValue() + "){\n" + "\t\tthis." + entry.getValue() + " = " + entry.getValue() + ";\n\t}\n"); @@ -97,9 +109,17 @@ private String generateSetters() { */ private String generateFieldsDeclaration() { String result = ""; - for (Pair entry : fields) { + for (AbstractMap.SimpleEntry entry : fields) { result += "\tprivate " + entry.getKey() + " " + entry.getValue() + ";\n"; } return result; } + + protected List resolveGenericsFromImport(String str) { + // This is a cheap workaround to have all classes of a generic be its own import + return Stream.of(str.split("[<>]")) + .filter(Predicate.not(String::isBlank)) + .filter(typeName -> typeName.contains(".")) // filter out build-in types + .collect(Collectors.toList()); + } } diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/PomGenerator.java b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/PomGenerator.java index 8ffc85b..4507961 100644 --- a/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/PomGenerator.java +++ b/src/main/java/ch/zhaw/splab/podilizerproc/awslambda/PomGenerator.java @@ -35,8 +35,8 @@ public void create() { } Properties proper = model.getProperties(); - proper.setProperty("maven.compiler.source", "1.8"); - proper.setProperty("maven.compiler.target", "1.8"); + proper.setProperty("maven.compiler.source", "11"); + proper.setProperty("maven.compiler.target", "11"); Build build = new Build(); Plugin plugin = new Plugin(); diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/depdencies/CompilationUnitInfo.java b/src/main/java/ch/zhaw/splab/podilizerproc/depdencies/CompilationUnitInfo.java new file mode 100644 index 0000000..bfd63f0 --- /dev/null +++ b/src/main/java/ch/zhaw/splab/podilizerproc/depdencies/CompilationUnitInfo.java @@ -0,0 +1,50 @@ +package ch.zhaw.splab.podilizerproc.depdencies; + +import javax.tools.JavaFileObject; +import java.util.*; + +public class CompilationUnitInfo { + + private final String name; + private final String packageName; + private final Set importedPackages; + private final JavaFileObject sourceFile; + + public CompilationUnitInfo(String name, String packageName, Set importedPackages, JavaFileObject sourceFile) { + this.name = name; + this.packageName = packageName; + this.importedPackages = new HashSet<>(importedPackages); + this.sourceFile = sourceFile; + } + + public String getName() { + return name; + } + + public String getPackageName() { + return packageName; + } + + public Set getAllImportedPackages() { + return importedPackages; + } + + public JavaFileObject getSourceFile() { + return sourceFile; + } + + @Override + public boolean equals(Object o) { + // Note: The equals method relies on the name being unique to the contained package + // This is not really clean oop, but it is way easier than comparing the imports every time. + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CompilationUnitInfo that = (CompilationUnitInfo) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/depdencies/DependencyResolver.java b/src/main/java/ch/zhaw/splab/podilizerproc/depdencies/DependencyResolver.java new file mode 100644 index 0000000..a3ec46d --- /dev/null +++ b/src/main/java/ch/zhaw/splab/podilizerproc/depdencies/DependencyResolver.java @@ -0,0 +1,115 @@ +package ch.zhaw.splab.podilizerproc.depdencies; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ImportTree; +import com.sun.source.util.SimpleTreeVisitor; +import com.sun.source.util.TreePath; +import com.sun.source.util.Trees; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import java.io.File; +import java.util.*; +import java.util.stream.Collectors; + +public class DependencyResolver { + + private Trees trees; + RoundEnvironment roundEnv; + + private final JavaPackageInfo rootPckg = new JavaPackageInfo(""); + + public DependencyResolver(Trees trees, RoundEnvironment roundEnv) { + this.trees = trees; + this.roundEnv = roundEnv; + loadCompleteSourceInformation(); + } + + /** + * This method is responsible for loading the general package structure of the project. + * This will later be used to get compilation units which are related to each other. + */ + public void loadCompleteSourceInformation() { + for (Element rootElement : roundEnv.getRootElements()) { + // TODO: Build a strucutre which keeps track of all dependencies a element has + // TODO: This might fail if there is incredemental processing + // https://stackoverflow.com/questions/18038514/annotation-processor-only-processing-a-modified-class-after-first-run + TreePath rootElemPath = trees.getPath(rootElement); + + CUVisitor rootVisistor = new CUVisitor(); + rootVisistor.visit(rootElemPath, trees); + CompilationUnitTree cu = rootVisistor.getCu(); + + String pckgName = cu.getPackageName() == null? "": cu.getPackageName().toString(); + String srcName = new File(cu.getSourceFile().toUri()).getName(); + srcName = srcName.replace(".java", ""); + + + Set allImports = cu.getImports().stream() + .map(ImportTree::getQualifiedIdentifier) + .map(Object::toString) + .map(completeImport -> completeImport.substring(0, completeImport.lastIndexOf('.'))) + .collect(Collectors.toSet()); + + CompilationUnitInfo compilationUnitInfo = + new CompilationUnitInfo(srcName, pckgName, allImports, cu.getSourceFile()); + + List pckNames = Arrays.asList(pckgName.split("\\.")); + rootPckg.addCompilationUnit(pckNames, compilationUnitInfo); + } + } + + public Set resolveDependencies(Element element) { + TreePath elemPath = trees.getPath(element); + CUVisitor cuVisitor = new CUVisitor(); + cuVisitor.visit(elemPath, trees); + CompilationUnitTree compilationUnit = cuVisitor.cu; + + // We are using the package name, not the file name itself to determin which files are relevant + // This is done because currently only the imports are used for the analysis + // this makes the analysis itself a lot easier, but might come with a small overhead compared + // to a more complex analysis of all used types in the classes themself + String packageName = compilationUnit.getPackageName().toString(); + + JavaPackageInfo packageInfo = rootPckg.findPackageInfo(packageName); + if (packageInfo == null) { + System.out.println("[TERMITE] WARNING: Unable to find package " + packageName); + return Collections.emptySet(); + } else { + System.out.println("[TERMITE] Found required dependencies for package " + packageName); + return packageInfo.getRelevantDependencies(rootPckg) + .stream() + .map(JavaPackageInfo::getCompilationUnits) + .flatMap(Set::stream) + .peek(cu -> System.out.println("Adding: " + cu.getSourceFile().getName())) + .collect(Collectors.toSet()); + } + } + + + + /** + * Compilation Unit visitor + */ + private class CUVisitor extends SimpleTreeVisitor { + private List cuList = new ArrayList<>(); + private CompilationUnitTree cu = null; + + @Override + public Object visitCompilationUnit(CompilationUnitTree compilationUnitTree, Object o) { + cuList.add(compilationUnitTree); + cu = compilationUnitTree; + return null; + } + + public List getCuList() { + return cuList; + } + + public CompilationUnitTree getCu() { + return cu; + } + } + + +} diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/depdencies/JavaPackageInfo.java b/src/main/java/ch/zhaw/splab/podilizerproc/depdencies/JavaPackageInfo.java new file mode 100644 index 0000000..02563e7 --- /dev/null +++ b/src/main/java/ch/zhaw/splab/podilizerproc/depdencies/JavaPackageInfo.java @@ -0,0 +1,97 @@ +package ch.zhaw.splab.podilizerproc.depdencies; + +import java.util.*; + +public class JavaPackageInfo { + + private final Set subpackages = new HashSet<>(); + private final Set compilationUnitInfos = new HashSet<>(); + + private final String name; + + public JavaPackageInfo(String name) { + this.name = name; + } + + public void addCompilationUnit(List parentPackages, CompilationUnitInfo cuInfo) { + if (parentPackages.isEmpty()) { + compilationUnitInfos.add(cuInfo); + } else { + String nextPckgName = parentPackages.get(0); + Optional foundPackage = subpackages.stream() + .filter(pckgInfo -> nextPckgName.equals(pckgInfo.name)) + .findFirst(); + JavaPackageInfo nextPackage; + if (foundPackage.isPresent()) { + nextPackage = foundPackage.get(); + } else { + nextPackage = new JavaPackageInfo(nextPckgName); + subpackages.add(nextPackage); + } + nextPackage.addCompilationUnit(parentPackages.subList(1, parentPackages.size()), cuInfo); + } + } + + /** + * @return A set of all package infos which the package depends on. + */ + public Set getRelevantDependencies(JavaPackageInfo rootPckg) { + + HashSet allRelevantPackageInfos = new HashSet<>(); + + addDependenciesToSet(rootPckg, allRelevantPackageInfos); + + return allRelevantPackageInfos; + } + + public Set getCompilationUnits() { + return compilationUnitInfos; + } + + private void addDependenciesToSet(JavaPackageInfo rootPckg, Set relevantDependencies) { + if (relevantDependencies.contains(this)) { + return; + } + // Add itself first, to avoid infinite recursion + relevantDependencies.add(this); + // Add all the dependencies which the contained CUs rely on + compilationUnitInfos + .stream() + .flatMap(cuInfo -> cuInfo.getAllImportedPackages().stream()) + .distinct() + .map(rootPckg::findPackageInfo) + .filter(Objects::nonNull) + .forEach((relevantPckg) -> relevantPckg.addDependenciesToSet(rootPckg, relevantDependencies)); + } + + public JavaPackageInfo findPackageInfo(String completePackageName) { + return findPackageInfo(Arrays.asList(completePackageName.split("\\."))); + } + + public JavaPackageInfo findPackageInfo(List pckgNames) { + if (pckgNames.isEmpty()) { + return this; + } + Optional subPckg = subpackages.stream() + .filter(subpckg -> pckgNames.get(0).equals(subpckg.name)) + .findAny(); + + return subPckg + .map(pckg -> + pckg.findPackageInfo(pckgNames.subList(1, pckgNames.size()))) + .orElse(null); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + JavaPackageInfo that = (JavaPackageInfo) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/src/main/java/ch/zhaw/splab/podilizerproc/util/AsyncLambda.java b/src/main/java/ch/zhaw/splab/podilizerproc/util/AsyncLambda.java new file mode 100644 index 0000000..a397076 --- /dev/null +++ b/src/main/java/ch/zhaw/splab/podilizerproc/util/AsyncLambda.java @@ -0,0 +1,28 @@ +package ch.zhaw.splab.podilizerproc.util; + +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; + +public class AsyncLambda { + + private AsyncLambda() {} + + public static CompletableFuture dispatch(Callable func) { + CompletableFuture result = new CompletableFuture<>(); + new Thread(() -> { + try { + result.complete(func.call()); + } catch (Exception e) { + result.completeExceptionally(e); + } + }).start(); + return result; + } + + public static CompletableFuture dispatch(Runnable func) { + return AsyncLambda.dispatch(() -> { + func.run(); + return null; + }); + } +}