/*
* Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE2.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 com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode;
import com.sebastian_daschner.jaxrs_analyzer.LogProvider;
import com.sebastian_daschner.jaxrs_analyzer.analysis.bytecode.reduction.RelevantInstructionReducer;
import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.ContextClassReader;
import com.sebastian_daschner.jaxrs_analyzer.analysis.classes.ProjectMethodClassVisitor;
import com.sebastian_daschner.jaxrs_analyzer.model.instructions.Instruction;
import com.sebastian_daschner.jaxrs_analyzer.model.instructions.InvokeInstruction;
import com.sebastian_daschner.jaxrs_analyzer.model.methods.MethodIdentifier;
import com.sebastian_daschner.jaxrs_analyzer.model.methods.ProjectMethod;
import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* Analyzes the content of a method. Sub classes have to be thread-safe.
*
* @author Sebastian Daschner
*/
abstract class MethodContentAnalyzer {
/**
* The number of package hierarchies which are taken to identify project resources.
*/
private static final int PROJECT_PACKAGE_HIERARCHIES = 2;
private final RelevantInstructionReducer instructionReducer = new RelevantInstructionReducer();
private String projectPackagePrefix;
/**
* Interprets the relevant instructions for the given method.
*
* @param instructions The instructions to reduce
* @return The reduced instructions
*/
List<Instruction> interpretRelevantInstructions(final List<Instruction> instructions) {
return instructionReducer.reduceInstructions(instructions);
}
/**
* Builds the project package prefix for the class of given method.
* The current project which is analyzed is identified by the first two package nodes.
*/
void buildPackagePrefix(final String className) {
// TODO test
final int lastPackageSeparator = className.lastIndexOf('/');
final String packageName = className.substring(0, lastPackageSeparator == -1 ? className.length() : lastPackageSeparator);
final String[] splitPackage = packageName.split("/");
if (splitPackage.length >= PROJECT_PACKAGE_HIERARCHIES) {
projectPackagePrefix = IntStream.range(0, PROJECT_PACKAGE_HIERARCHIES).mapToObj(i -> splitPackage[i]).collect(Collectors.joining("/"));
} else {
projectPackagePrefix = packageName;
}
}
/**
* Searches for own project method invoke instructions in the given list.
*
* @param instructions The instructions where to search
* @return The found project methods
*/
Set<ProjectMethod> findProjectMethods(final List<Instruction> instructions) {
final Set<ProjectMethod> projectMethods = new HashSet<>();
addProjectMethods(instructions, projectMethods);
return projectMethods;
}
/**
* Adds all project methods called in the given {@code instructions} to the {@code projectMethods} recursively.
*
* @param instructions The instructions of the current method
* @param projectMethods All found project methods
*/
private void addProjectMethods(final List<Instruction> instructions, final Set<ProjectMethod> projectMethods) {
Set<MethodIdentifier> projectMethodIdentifiers = findUnhandledProjectMethodIdentifiers(instructions, projectMethods);
for (MethodIdentifier identifier : projectMethodIdentifiers) {
// TODO cache results -> singleton pool?
final MethodResult methodResult = visitProjectMethod(identifier);
if (methodResult == null) {
continue;
}
final List<Instruction> nestedMethodInstructions = interpretRelevantInstructions(methodResult.getInstructions());
projectMethods.add(new ProjectMethod(identifier, nestedMethodInstructions));
addProjectMethods(nestedMethodInstructions, projectMethods);
}
}
private MethodResult visitProjectMethod(MethodIdentifier identifier) {
try {
final ClassReader classReader = new ContextClassReader(identifier.getContainingClass());
final MethodResult methodResult = new MethodResult();
methodResult.setOriginalMethodSignature(identifier);
final ClassVisitor visitor = new ProjectMethodClassVisitor(methodResult, identifier);
classReader.accept(visitor, ClassReader.EXPAND_FRAMES);
return methodResult;
} catch (IOException e) {
LogProvider.error("Could not analyze project method " + identifier.getContainingClass() + "#" + identifier.getMethodName());
LogProvider.debug(e);
return null;
}
}
/**
* Returns project method identifiers of invoke instructions which are not included in the {@code projectMethods}.
*
* @param instructions The instructions of the current method
* @param projectMethods All found project methods
* @return The new method identifiers of unhandled project method invoke instructions
*/
private Set<MethodIdentifier> findUnhandledProjectMethodIdentifiers(final List<Instruction> instructions, final Set<ProjectMethod> projectMethods) {
// find own methods
return instructions.stream().filter(i -> i.getType() == Instruction.InstructionType.INVOKE || i.getType() == Instruction.InstructionType.METHOD_HANDLE)
.map(i -> (InvokeInstruction) i).filter(this::isProjectMethod).map(InvokeInstruction::getIdentifier)
.filter(i -> projectMethods.stream().noneMatch(m -> m.matches(i)))
.collect(Collectors.toSet());
}
/**
* Checks if the given instruction invokes a method defined in the analyzed project.
*
* @param instruction The invoke instruction
* @return {@code true} if method was defined in the project
*/
private boolean isProjectMethod(final InvokeInstruction instruction) {
final MethodIdentifier identifier = instruction.getIdentifier();
// check if method is in own package
return identifier.getContainingClass().startsWith(projectPackagePrefix);
}
}