/*
* 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.devtools.j2objc.translate;
import com.google.devtools.j2objc.ast.AnnotationTypeDeclaration;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.EnumDeclaration;
import com.google.devtools.j2objc.ast.LambdaExpression;
import com.google.devtools.j2objc.ast.SimpleName;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.TypeDeclaration;
import com.google.devtools.j2objc.ast.UnitTreeVisitor;
import com.google.devtools.j2objc.util.ElementUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
/**
* Detects variable name collision scenarios and renames variables accordingly.
*
* @author Keith Stanger
*/
public class VariableRenamer extends UnitTreeVisitor {
private List<Set<String>> fieldNameStack = new ArrayList<>();
private Set<TypeElement> renamedTypes = new HashSet<>();
public VariableRenamer(CompilationUnit unit) {
super(unit);
}
private void collectAndRenameFields(TypeElement type, Set<VariableElement> fields) {
if (type == null) {
return;
}
collectAndRenameFields(ElementUtil.getSuperclass(type), fields);
if (!renamedTypes.contains(type)) {
renamedTypes.add(type);
Set<String> superFieldNames = new HashSet<>();
for (VariableElement superField : fields) {
superFieldNames.add(superField.getSimpleName().toString());
}
// Look for methods that might conflict with a static variable when functionized.
Set<String> staticMethodNames = new HashSet<>();
for (ExecutableElement method : ElementUtil.getExecutables(type)) {
if (method.getParameters().size() == 0) {
staticMethodNames.add(nameTable.getFunctionName(method));
}
}
for (VariableElement field : ElementUtil.getDeclaredFields(type)) {
String fieldName = field.getSimpleName().toString();
if (ElementUtil.isGlobalVar(field)) {
if (staticMethodNames.contains(fieldName)) {
while (staticMethodNames.contains(fieldName)) {
fieldName += "_";
}
nameTable.setVariableName(field, fieldName);
}
} else if (!ElementUtil.isStatic(field) && superFieldNames.contains(fieldName)) {
fieldName += "_" + type.getSimpleName();
nameTable.setVariableName(field, fieldName);
}
}
}
for (VariableElement field : ElementUtil.getDeclaredFields(type)) {
if (!ElementUtil.isStatic(field)) {
fields.add(field);
}
}
}
private void pushType(TypeElement type) {
Set<VariableElement> fields = new HashSet<>();
collectAndRenameFields(type, fields);
Set<String> fullFieldNames = new HashSet<>();
for (VariableElement field : fields) {
fullFieldNames.add(nameTable.getVariableShortName(field));
}
fieldNameStack.add(fullFieldNames);
}
private void popType() {
fieldNameStack.remove(fieldNameStack.size() - 1);
}
@Override
public void endVisit(SimpleName node) {
VariableElement var = TreeUtil.getVariableElement(node);
if (var == null) {
return;
}
if (var.getKind().isField()) {
// Make sure fields for the declaring type are renamed.
collectAndRenameFields(ElementUtil.getDeclaringClass(var), new HashSet<VariableElement>());
} else {
// Local variable or parameter. Rename if it shares a name with a field.
String varName = ElementUtil.getName(var);
assert fieldNameStack.size() > 0;
Set<String> fieldNames = fieldNameStack.get(fieldNameStack.size() - 1);
if (fieldNames.contains(varName)) {
nameTable.setVariableName(var, varName + "Arg");
}
}
}
@Override
public boolean visit(TypeDeclaration node) {
pushType(node.getTypeElement());
return true;
}
@Override
public void endVisit(TypeDeclaration node) {
popType();
}
@Override
public boolean visit(EnumDeclaration node) {
pushType(node.getTypeElement());
return true;
}
@Override
public void endVisit(EnumDeclaration node) {
popType();
}
@Override
public boolean visit(AnnotationTypeDeclaration node) {
pushType(node.getTypeElement());
return true;
}
@Override
public void endVisit(AnnotationTypeDeclaration node) {
popType();
}
@Override
public boolean visit(LambdaExpression node) {
pushType(node.getTypeElement());
return true;
}
@Override
public void endVisit(LambdaExpression node) {
popType();
}
}