/*
* Copyright 2015 Google Inc.
*
* 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.google.template.soy.jbcsrc;
import com.google.common.collect.ImmutableList;
import com.google.template.soy.base.internal.UniqueNameGenerator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.objectweb.asm.ClassVisitor;
/**
* Tracks a collection of inner classes and aids in name management and calling
* {@link ClassVisitor#visitInnerClass(String, String, String, int)} to ensure that they are
* registered correctly.
*/
final class InnerClasses {
private final TypeInfo outer;
private final Map<TypeInfo, ClassData> innerClasses = new LinkedHashMap<>();
private final Map<TypeInfo, Integer> innerClassesAccessModifiers = new LinkedHashMap<>();
private final UniqueNameGenerator classNames = JbcSrcNameGenerators.forClassNames();
InnerClasses(TypeInfo outer) {
this.outer = outer;
}
/** Returns all the {@link ClassData} for every InnerClass registered. */
ImmutableList<ClassData> getInnerClassData() {
return ImmutableList.copyOf(innerClasses.values());
}
/**
* Register the given name as an inner class with the given access modifiers.
*
* @return A {@link TypeInfo} with the full class name
*/
TypeInfo registerInnerClass(String simpleName, int accessModifiers) {
classNames.claimName(simpleName);
TypeInfo innerClass = outer.innerClass(simpleName);
innerClassesAccessModifiers.put(innerClass, accessModifiers);
return innerClass;
}
/**
* Register the name (or a simpl mangling of it) as an inner class with the given access
* modifiers.
*
* @return A {@link TypeInfo} with the full (possibly mangled) class name
*/
TypeInfo registerInnerClassWithGeneratedName(String simpleName, int accessModifiers) {
simpleName = classNames.generateName(simpleName);
TypeInfo innerClass = outer.innerClass(simpleName);
innerClassesAccessModifiers.put(innerClass, accessModifiers);
return innerClass;
}
/**
* Adds the data for an inner class.
*
* @throws java.lang.IllegalArgumentException if the class wasn't previous registered via
* {@link #registerInnerClass(String, int)} or
* {@link #registerInnerClassWithGeneratedName(String, int)}.
*/
void add(ClassData classData) {
checkRegistered(classData.type());
innerClasses.put(classData.type(), classData);
}
private void checkRegistered(TypeInfo type) {
if (!classNames.hasName(type.simpleName())) {
throw new IllegalArgumentException(type + " wasn't registered");
}
}
/**
* Registers this factory as an inner class on the given class writer.
*
* <p>Registering an inner class is confusing. The inner class needs to call this and so does
* the outer class. Confirmed by running ASMIfier. Also, failure to call visitInnerClass on both
* classes either breaks reflective apis (like class.getSimpleName()/getEnclosingClass), or
* causes verifier errors (like IncompatibleClassChangeError).
*/
void registerAsInnerClass(ClassVisitor visitor, TypeInfo innerClass) {
checkRegistered(innerClass);
doRegister(visitor, innerClass);
}
/**
* Registers all inner classes to the given outer class.
*/
void registerAllInnerClasses(ClassVisitor visitor) {
for (Map.Entry<TypeInfo, Integer> entry : innerClassesAccessModifiers.entrySet()) {
TypeInfo innerClass = entry.getKey();
doRegister(visitor, innerClass);
}
}
private void doRegister(ClassVisitor visitor, TypeInfo innerClass) {
visitor.visitInnerClass(
innerClass.internalName(),
outer.internalName(),
innerClass.simpleName(),
innerClassesAccessModifiers.get(innerClass));
}
}