/*
* Copyright 2017 TNG Technology Consulting GmbH
*
* 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 com.tngtech.archunit.core.importer;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Set;
import com.tngtech.archunit.base.Optional;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType;
import com.tngtech.archunit.core.importer.JavaClassProcessor.AccessHandler;
import com.tngtech.archunit.core.importer.JavaClassProcessor.DeclarationHandler;
import com.tngtech.archunit.core.importer.RawAccessRecord.CodeUnit;
import com.tngtech.archunit.core.importer.RawAccessRecord.MethodTargetInfo;
import com.tngtech.archunit.core.importer.RawAccessRecord.TargetInfo;
import com.tngtech.archunit.core.importer.resolvers.ClassResolver;
import com.tngtech.archunit.core.importer.resolvers.ClassResolver.ClassUriImporter;
import org.objectweb.asm.ClassReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME;
import static org.objectweb.asm.Opcodes.ASM5;
class ClassFileProcessor {
private static final Logger LOG = LoggerFactory.getLogger(ClassFileProcessor.class);
static final int ASM_API_VERSION = ASM5;
private final ClassResolver.Factory classResolverFactory = new ClassResolver.Factory();
JavaClasses process(ClassFileSource source) {
ClassFileImportRecord importRecord = new ClassFileImportRecord();
RecordAccessHandler accessHandler = new RecordAccessHandler(importRecord);
ClassDetailsRecorder classDetailsRecorder = new ClassDetailsRecorder(importRecord);
for (ClassFileLocation location : source) {
try (InputStream s = location.openStream()) {
JavaClassProcessor javaClassProcessor =
new JavaClassProcessor(location.getUri(), classDetailsRecorder, accessHandler);
new ClassReader(s).accept(javaClassProcessor, 0);
importRecord.addAll(javaClassProcessor.createJavaClass().asSet());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return new ClassGraphCreator(importRecord, getClassResolver(classDetailsRecorder)).complete();
}
private static class ClassDetailsRecorder implements DeclarationHandler {
private final ClassFileImportRecord importRecord;
private String ownerName;
private ClassDetailsRecorder(ClassFileImportRecord importRecord) {
this.importRecord = importRecord;
}
@Override
public boolean isNew(String className) {
return !importRecord.getClasses().containsKey(className);
}
@Override
public void onNewClass(String className, Optional<String> superClassName, Set<String> interfaceNames) {
ownerName = className;
if (superClassName.isPresent()) {
importRecord.setSuperClass(ownerName, superClassName.get());
}
importRecord.addInterfaces(ownerName, interfaceNames);
}
@Override
public void onDeclaredField(DomainBuilders.JavaFieldBuilder fieldBuilder) {
importRecord.addField(ownerName, fieldBuilder);
}
@Override
public void onDeclaredConstructor(DomainBuilders.JavaConstructorBuilder builder) {
importRecord.addConstructor(ownerName, builder);
}
@Override
public void onDeclaredMethod(DomainBuilders.JavaMethodBuilder builder) {
importRecord.addMethod(ownerName, builder);
}
@Override
public void onDeclaredStaticInitializer(DomainBuilders.JavaStaticInitializerBuilder builder) {
importRecord.setStaticInitializer(ownerName, builder);
}
@Override
public void onDeclaredAnnotations(Set<DomainBuilders.JavaAnnotationBuilder> annotations) {
importRecord.addAnnotations(ownerName, annotations);
}
@Override
public void registerEnclosingClass(String ownerName, String enclosingClassName) {
importRecord.setEnclosingClass(ownerName, enclosingClassName);
}
}
private static class RecordAccessHandler implements AccessHandler {
private static final Logger LOG = LoggerFactory.getLogger(RecordAccessHandler.class);
private final ClassFileImportRecord importRecord;
private CodeUnit codeUnit;
private int lineNumber;
private RecordAccessHandler(ClassFileImportRecord importRecord) {
this.importRecord = importRecord;
}
@Override
public void setContext(CodeUnit codeUnit) {
this.codeUnit = codeUnit;
}
@Override
public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
@Override
public void handleFieldInstruction(int opcode, String owner, String name, String desc) {
AccessType accessType = AccessType.forOpCode(opcode);
LOG.debug("Found {} access to field {}.{}:{} in line {}", accessType, owner, name, desc, lineNumber);
TargetInfo target = new RawAccessRecord.FieldTargetInfo(owner, name, desc);
importRecord.registerFieldAccess(filled(new RawAccessRecord.ForField.Builder(), target)
.withAccessType(accessType)
.build());
}
@Override
public void handleMethodInstruction(String owner, String name, String desc) {
LOG.debug("Found call of method {}.{}:{} in line {}", owner, name, desc, lineNumber);
if (CONSTRUCTOR_NAME.equals(name)) {
TargetInfo target = new RawAccessRecord.ConstructorTargetInfo(owner, name, desc);
importRecord.registerConstructorCall(filled(new RawAccessRecord.Builder(), target).build());
} else {
TargetInfo target = new MethodTargetInfo(owner, name, desc);
importRecord.registerMethodCall(filled(new RawAccessRecord.Builder(), target).build());
}
}
private <BUILDER extends RawAccessRecord.BaseBuilder<BUILDER>> BUILDER filled(BUILDER builder, TargetInfo target) {
return builder
.withCaller(codeUnit)
.withTarget(target)
.withLineNumber(lineNumber);
}
}
private ClassResolver getClassResolver(ClassDetailsRecorder classDetailsRecorder) {
ClassResolver classResolver = classResolverFactory.create();
classResolver.setClassUriImporter(new UriImporterOfProcessor(classDetailsRecorder));
return classResolver;
}
private static class UriImporterOfProcessor implements ClassUriImporter {
private final DeclarationHandler declarationHandler;
UriImporterOfProcessor(DeclarationHandler declarationHandler) {
this.declarationHandler = declarationHandler;
}
@Override
public Optional<JavaClass> tryImport(URI uri) {
try (InputStream inputStream = uri.toURL().openStream()) {
JavaClassProcessor classProcessor = new JavaClassProcessor(uri, declarationHandler);
new ClassReader(inputStream).accept(classProcessor, 0);
return classProcessor.createJavaClass();
} catch (Exception e) {
LOG.warn(String.format("Error during import from %s, falling back to simple import", uri), e);
return Optional.absent();
}
}
}
}