/*
* 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.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.SetMultimap;
import com.tngtech.archunit.base.Optional;
import com.tngtech.archunit.core.domain.JavaClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkState;
class ClassFileImportRecord {
private static final Logger LOG = LoggerFactory.getLogger(ClassFileImportRecord.class);
private final Map<String, JavaClass> classes = new HashMap<>();
private final Map<String, String> superClassNamesByOwner = new HashMap<>();
private final SetMultimap<String, String> interfaceNamesByOwner = HashMultimap.create();
private final SetMultimap<String, DomainBuilders.JavaFieldBuilder> fieldBuildersByOwner = HashMultimap.create();
private final SetMultimap<String, DomainBuilders.JavaMethodBuilder> methodBuildersByOwner = HashMultimap.create();
private final SetMultimap<String, DomainBuilders.JavaConstructorBuilder> constructorBuildersByOwner = HashMultimap.create();
private final Map<String, DomainBuilders.JavaStaticInitializerBuilder> staticInitializerBuildersByOwner = new HashMap<>();
private final SetMultimap<String, DomainBuilders.JavaAnnotationBuilder> annotationsByOwner = HashMultimap.create();
private final EnclosingClassesByInnerClasses enclosingClassNamesByOwner = new EnclosingClassesByInnerClasses();
private final Set<RawAccessRecord.ForField> rawFieldAccessRecords = new HashSet<>();
private final Set<RawAccessRecord> rawMethodCallRecords = new HashSet<>();
private final Set<RawAccessRecord> rawConstructorCallRecords = new HashSet<>();
void setSuperClass(String ownerName, String superClassName) {
checkState(!superClassNamesByOwner.containsKey(ownerName),
"Attempted to add %s as a second superclass to %s, this is most likely a bug",
superClassName, ownerName);
superClassNamesByOwner.put(ownerName, superClassName);
}
void addInterfaces(String ownerName, Set<String> interfaceNames) {
interfaceNamesByOwner.putAll(ownerName, interfaceNames);
}
void addField(String ownerName, DomainBuilders.JavaFieldBuilder fieldBuilder) {
fieldBuildersByOwner.put(ownerName, fieldBuilder);
}
void addMethod(String ownerName, DomainBuilders.JavaMethodBuilder methodBuilder) {
methodBuildersByOwner.put(ownerName, methodBuilder);
}
void addConstructor(String ownerName, DomainBuilders.JavaConstructorBuilder constructorBuilder) {
constructorBuildersByOwner.put(ownerName, constructorBuilder);
}
void setStaticInitializer(String ownerName, DomainBuilders.JavaStaticInitializerBuilder builder) {
checkState(!staticInitializerBuildersByOwner.containsKey(ownerName),
"Tried to add a second static initializer to %s, this is most likely a bug",
ownerName);
staticInitializerBuildersByOwner.put(ownerName, builder);
}
void addAnnotations(String ownerName, Set<DomainBuilders.JavaAnnotationBuilder> annotations) {
this.annotationsByOwner.putAll(ownerName, annotations);
}
void setEnclosingClass(String ownerName, String enclosingClassName) {
enclosingClassNamesByOwner.register(ownerName, enclosingClassName);
}
Optional<String> getSuperClassFor(String name) {
return Optional.fromNullable(superClassNamesByOwner.get(name));
}
Set<String> getInterfaceNamesFor(String ownerName) {
return interfaceNamesByOwner.get(ownerName);
}
Set<DomainBuilders.JavaFieldBuilder> getFieldBuildersFor(String ownerName) {
return fieldBuildersByOwner.get(ownerName);
}
Set<DomainBuilders.JavaMethodBuilder> getMethodBuildersFor(String ownerName) {
return methodBuildersByOwner.get(ownerName);
}
Set<DomainBuilders.JavaConstructorBuilder> getConstructorBuildersFor(String ownerName) {
return constructorBuildersByOwner.get(ownerName);
}
Optional<DomainBuilders.JavaStaticInitializerBuilder> getStaticInitializerBuilderFor(String ownerName) {
return Optional.fromNullable(staticInitializerBuildersByOwner.get(ownerName));
}
Set<DomainBuilders.JavaAnnotationBuilder> getAnnotationsFor(String ownerName) {
return annotationsByOwner.get(ownerName);
}
Optional<String> getEnclosingClassFor(String ownerName) {
return enclosingClassNamesByOwner.get(ownerName);
}
void registerFieldAccess(RawAccessRecord.ForField record) {
rawFieldAccessRecords.add(record);
}
void registerMethodCall(RawAccessRecord record) {
rawMethodCallRecords.add(record);
}
void registerConstructorCall(RawAccessRecord record) {
rawConstructorCallRecords.add(record);
}
Set<RawAccessRecord.ForField> getRawFieldAccessRecords() {
return ImmutableSet.copyOf(rawFieldAccessRecords);
}
Set<RawAccessRecord> getRawMethodCallRecords() {
return ImmutableSet.copyOf(rawMethodCallRecords);
}
Set<RawAccessRecord> getRawConstructorCallRecords() {
return ImmutableSet.copyOf(rawConstructorCallRecords);
}
void addAll(Collection<JavaClass> javaClasses) {
for (JavaClass javaClass : javaClasses) {
classes.put(javaClass.getName(), javaClass);
}
}
Map<String, JavaClass> getClasses() {
return classes;
}
Set<RawAccessRecord> getAccessRecords() {
return ImmutableSet.<RawAccessRecord>builder()
.addAll(rawFieldAccessRecords)
.addAll(rawMethodCallRecords)
.addAll(rawConstructorCallRecords)
.build();
}
Map<String, String> getSuperClassNamesBySubClass() {
return superClassNamesByOwner;
}
SetMultimap<String, String> getInterfaceNamesBySubInterface() {
return interfaceNamesByOwner;
}
// NOTE: ASM calls visitInnerClass and visitOuterClass several times, sometimes when the outer class is imported
// and sometimes again when the inner class is imported. To make it easier, we'll just deal with duplicate
// registrations, as there is no harm, as long as no conflicting information is recorded.
private static class EnclosingClassesByInnerClasses {
private final Map<String, String> innerToOuter = new HashMap<>();
void register(String innerName, String outerName) {
if (registeringAllowed(innerName, outerName)) {
innerToOuter.put(innerName, outerName);
}
}
private boolean registeringAllowed(String innerName, String outerName) {
boolean registeringAllowed = !innerToOuter.containsKey(innerName) ||
innerToOuter.get(innerName).equals(outerName);
if (!registeringAllowed) {
LOG.warn("Skipping registering outer class {} for inner class {}, since already outer class {} was registered",
outerName, innerName, innerToOuter.get(innerName));
}
return registeringAllowed;
}
public Optional<String> get(String ownerName) {
return Optional.fromNullable(innerToOuter.get(ownerName));
}
}
}