/** * Copyright 2014 SAP AG * * 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.aim.mainagent.builder; import japa.parser.JavaParser; import japa.parser.ast.CompilationUnit; import japa.parser.ast.ImportDeclaration; import japa.parser.ast.Node; import japa.parser.ast.body.BodyDeclaration; import japa.parser.ast.body.MethodDeclaration; import japa.parser.ast.body.TypeDeclaration; import japa.parser.ast.expr.AnnotationExpr; import japa.parser.ast.expr.MemberValuePair; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.aim.api.exceptions.InstrumentationException; import org.aim.api.instrumentation.AbstractEnclosingProbe; import org.aim.api.instrumentation.GenericProbe; import org.aim.api.instrumentation.ProbeAfterPart; import org.aim.api.instrumentation.ProbeBeforePart; import org.aim.api.instrumentation.ProbeIncrementalInstrumentation; import org.aim.api.instrumentation.ProbeVariable; import org.apache.tools.ant.filters.StringInputStream; import com.strobel.decompiler.Decompiler; import com.strobel.decompiler.DecompilerSettings; import com.strobel.decompiler.PlainTextOutput; /** * Provides single snippets for different probes. * * @author Alexander Wert * */ public final class SnippetProvider { private static SnippetProvider instance; public static final String METHOD_NAME_REQUIREMENT_KEY = "requiredMethodName"; /** * Returns singleton instance. * * @return singleton */ public static SnippetProvider getInstance() { if (instance == null) { instance = new SnippetProvider(); } return instance; } private MultiSnippet genericSnippet = null; private final Map<Class<? extends AbstractEnclosingProbe>, MultiSnippet> snippets; private SnippetProvider() { snippets = new HashMap<Class<? extends AbstractEnclosingProbe>, MultiSnippet>(); } /** * Retrieves snippet for the given probe type. * * @param probeType * probe type for which a snippet shell be retrieved * @return a snippet * @throws InstrumentationException * if snippet cannot be retrieved */ public MultiSnippet getSnippet(Class<? extends AbstractEnclosingProbe> probeType) throws InstrumentationException { if (snippets.containsKey(probeType)) { return snippets.get(probeType); } else { MultiSnippet snippet = createSnippet(probeType); snippets.put(probeType, snippet); return snippet; } } /** * Retrieves the generic probe snippet. * * @return snippet for common code * @throws InstrumentationException * if snippet cannot be retrieved */ public MultiSnippet getGenericSnippet() throws InstrumentationException { if (genericSnippet == null) { genericSnippet = createSnippet(GenericProbe.class); } return genericSnippet; } private MultiSnippet createSnippet(Class<?> probeClass) throws InstrumentationException { MultiSnippet snippet = new MultiSnippet(); setInjectionCode(probeClass, snippet); for (Field field : probeClass.getFields()) { if (field.isAnnotationPresent(ProbeVariable.class)) { snippet.getVariables().put(field.getName(), field.getType()); } } return snippet; } private void setInjectionCode(Class<?> probeClass, MultiSnippet snippet) throws InstrumentationException { try { DecompilerSettings dcSettings = new DecompilerSettings(); dcSettings.setForceExplicitImports(true); PlainTextOutput pto = new PlainTextOutput(); Decompiler.decompile(probeClass.getName(), pto, dcSettings); StringInputStream sis = new StringInputStream(pto.toString()); CompilationUnit cu = JavaParser.parse(sis); TypeDeclaration type = cu.getTypes().get(0); String packageName = cu.getPackage().getName().toString(); Map<String, String> imports = getImports(cu); String className = type.getName(); Set<String> nonProbeMethods = getAllNonProbeMethods(probeClass); List<BodyDeclaration> members = type.getMembers(); for (BodyDeclaration member : members) { boolean beforeMethod = hasAnnotation(member, ProbeBeforePart.class.getSimpleName()); boolean afterMethod = hasAnnotation(member, ProbeAfterPart.class.getSimpleName()); boolean incrementalInstrumentation = hasAnnotation(member, ProbeIncrementalInstrumentation.class.getSimpleName()); if (member instanceof MethodDeclaration && (beforeMethod || afterMethod || incrementalInstrumentation)) { MethodDeclaration method = (MethodDeclaration) member; String body = correctCode(imports, className, packageName, method, nonProbeMethods); Set<String> methodNameRequirements = getMethodNameRequirements(method); if (beforeMethod) { snippet.getBeforePart().put(methodNameRequirements, body); } else if (afterMethod) { snippet.getAfterPart().put(methodNameRequirements, body); } else if (incrementalInstrumentation) { snippet.setIncrementalPart(body); } } } } catch (Exception e) { throw new InstrumentationException( "Invalid Instrumentation Probe: Error in parsing instrumentation probe.", e); } } private String correctCode(Map<String, String> imports, String className, String packageName, MethodDeclaration method, Set<String> nonProbeMethods) { String thisFullClassName = packageName + "." + className; String body = method.getBody().toString(); // remove enclosing braces int fi = method.getBody().toString().indexOf("{"); int li = method.getBody().toString().lastIndexOf("}"); body = body.substring(fi + 1, li); // replace all occurrences of "this." in front of a method (non-probe // variables / methods) body = body.replaceAll("this\\.(?![(_" + className + "_)(_" + GenericProbe.class.getSimpleName() + "_)(__)])", thisFullClassName + "."); // remove remaining occurrences of "this." body = body.replaceAll("this\\.", ""); // correct calls to non probe methods for (String npm : nonProbeMethods) { body = body.replaceAll("(\\s" + npm + "\\()", thisFullClassName + "." + "$1"); } // complements package prefix to all classes for (String classToReplace : imports.keySet()) { body = body.replaceAll("([^\\.\\w])" + classToReplace, "$1" + imports.get(classToReplace)); } body = body.replace(AbstractEnclosingProbe.RETURN_OBJECT_PLACE_HOLDER, "$_"); body = body.replaceAll("(" + AbstractEnclosingProbe.PARAMETER_PLACE_HOLDER + "\\[)(.+?)(\\])", "\\$$2"); return body; } private Set<String> getAllNonProbeMethods(Class<?> probeClass) { Set<String> result = new HashSet<>(); for (Method method : probeClass.getDeclaredMethods()) { if (!method.isAnnotationPresent(ProbeBeforePart.class) && !method.isAnnotationPresent(ProbeAfterPart.class)) { result.add(method.getName()); } } for (Method method : probeClass.getMethods()) { if (!method.isAnnotationPresent(ProbeBeforePart.class) && !method.isAnnotationPresent(ProbeAfterPart.class)) { result.add(method.getName()); } } return result; } private Map<String, String> getImports(CompilationUnit cu) { Map<String, String> imports = new HashMap<>(); for (ImportDeclaration imp : cu.getImports()) { String value = imp.getName().toString(); int li = value.lastIndexOf("."); String key = value.substring(li + 1); imports.put(key, value); } return imports; } private boolean hasAnnotation(BodyDeclaration member, String annotation) { if (member.getAnnotations() == null) { return false; } for (AnnotationExpr annExpr : member.getAnnotations()) { if (annExpr.getName().toString().contains(annotation)) { return true; } } return false; } @SuppressWarnings("unchecked") private Set<String> getMethodNameRequirements(BodyDeclaration member) { if (member.getAnnotations() == null) { return Collections.EMPTY_SET; } HashSet<String> result = new HashSet<>(); for (AnnotationExpr annExpr : member.getAnnotations()) { for (Node node : annExpr.getChildrenNodes()) { if (node instanceof MemberValuePair) { MemberValuePair mvp = (MemberValuePair) node; if (mvp.getName().equals(METHOD_NAME_REQUIREMENT_KEY)) { String strValue = mvp.getValue().toString(); if (strValue.startsWith("{") && strValue.endsWith("}")) { strValue = strValue.substring(1, strValue.length() - 1); } String[] values = strValue.split(","); for (String value : values) { value = value.trim(); result.add(value.substring(1, value.length() - 1)); } return result; } } } } return Collections.EMPTY_SET; } }