/*
* 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.jdt;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
/**
* Removes elements annotated as "J2ObjCIncompatible".
*/
class JdtJ2ObjCIncompatibleStripper extends ASTVisitor {
private final TreeSet<ASTNode> nodesToStrip = Sets.newTreeSet(START_POS_COMPARATOR);
private final Map<String, ImportDeclaration> unusedImports = Maps.newHashMap();
private final Map<String, ImportDeclaration> unusedStaticImports = Maps.newHashMap();
private static final Comparator<ASTNode> START_POS_COMPARATOR = new Comparator<ASTNode>() {
@Override
public int compare(ASTNode a, ASTNode b) {
return a.getStartPosition() - b.getStartPosition();
}
};
private JdtJ2ObjCIncompatibleStripper(CompilationUnit unit) {
@SuppressWarnings("unchecked")
List<ImportDeclaration> imports = unit.imports();
for (ImportDeclaration importNode : imports) {
String name = getLastComponent(importNode.getName());
if (importNode.isStatic()) {
unusedStaticImports.put(name, importNode);
} else {
unusedImports.put(name, importNode);
}
}
}
public static String strip(String source, CompilationUnit unit) {
JdtJ2ObjCIncompatibleStripper stripper = new JdtJ2ObjCIncompatibleStripper(unit);
unit.accept(stripper);
return stripper.stripSource(source);
}
private boolean isJ2ObjCIncompatible(IExtendedModifier modifier) {
if (modifier instanceof Annotation) {
String name = getLastComponent(((Annotation) modifier).getTypeName());
return name.equals("J2ObjCIncompatible");
}
return false;
}
private String getFirstComponent(Name name) {
if (name.isSimpleName()) {
return ((SimpleName) name).getIdentifier();
} else {
return getFirstComponent(((QualifiedName) name).getQualifier());
}
}
private String getLastComponent(Name name) {
if (name.isSimpleName()) {
return ((SimpleName) name).getIdentifier();
} else {
return ((QualifiedName) name).getName().getIdentifier();
}
}
private boolean visitBodyDeclaration(BodyDeclaration node) {
@SuppressWarnings("unchecked")
List<IExtendedModifier> modifiers = node.modifiers();
for (IExtendedModifier modifier : modifiers) {
if (isJ2ObjCIncompatible(modifier)) {
nodesToStrip.add(node);
return false;
}
}
return true;
}
@Override
public boolean visit(ImportDeclaration node) {
// Prevents marking this import as "used".
return false;
}
@Override
public boolean visit(SimpleType node) {
unusedImports.remove(getFirstComponent(node.getName()));
return false;
}
@Override
public void endVisit(SimpleName node) {
unusedImports.remove(node.getIdentifier());
unusedStaticImports.remove(node.getIdentifier());
}
@Override
public boolean visit(QualifiedName node) {
unusedImports.remove(getFirstComponent(node));
unusedStaticImports.remove(getFirstComponent(node));
return false;
}
@Override
public boolean visit(AnnotationTypeDeclaration node) {
return visitBodyDeclaration(node);
}
@Override
public boolean visit(AnnotationTypeMemberDeclaration node) {
return visitBodyDeclaration(node);
}
@Override
public boolean visit(EnumDeclaration node) {
return visitBodyDeclaration(node);
}
@Override
public boolean visit(FieldDeclaration node) {
return visitBodyDeclaration(node);
}
@Override
public boolean visit(MethodDeclaration node) {
return visitBodyDeclaration(node);
}
@Override
public boolean visit(TypeDeclaration node) {
return visitBodyDeclaration(node);
}
private String stripSource(String source) {
nodesToStrip.addAll(unusedImports.values());
nodesToStrip.addAll(unusedStaticImports.values());
StringBuilder sb = new StringBuilder();
int currentIdx = 0;
for (ASTNode node : nodesToStrip) {
int startPos = node.getStartPosition();
if (startPos < currentIdx) {
continue;
}
int endPos = startPos + node.getLength();
sb.append(source.substring(currentIdx, startPos));
// Preserve newlines from the stripped node so that we can add line
// directives consistent with the original source file.
for (int i = startPos; i < endPos; i++) {
if (source.charAt(i) == '\n') {
sb.append('\n');
}
}
currentIdx = endPos;
}
sb.append(source.substring(currentIdx));
return sb.toString();
}
}