/*
* 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.javac;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.sun.tools.javac.tree.EndPosTable;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotatedType;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCImport;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.tree.TreeScanner;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
/**
* Removes elements annotated as "J2ObjCIncompatible".
*/
class JavacJ2ObjCIncompatibleStripper extends TreeScanner {
private final EndPosTable endPositions;
private final TreeSet<JCTree> nodesToStrip = Sets.newTreeSet(START_POS_COMPARATOR);
private final Map<String, JCImport> unusedImports = Maps.newHashMap();
private final Map<String, JCImport> unusedStaticImports = Maps.newHashMap();
private static final Comparator<JCTree> START_POS_COMPARATOR = new Comparator<JCTree>() {
@Override
public int compare(JCTree a, JCTree b) {
return a.getStartPosition() - b.getStartPosition();
}
};
public static String strip(String source, JCCompilationUnit unit) {
JavacJ2ObjCIncompatibleStripper stripper = new JavacJ2ObjCIncompatibleStripper(unit);
unit.accept(stripper);
return stripper.stripSource(source);
}
private JavacJ2ObjCIncompatibleStripper(JCCompilationUnit unit) {
endPositions = unit.endPositions;
}
// TreeScanner methods.
@Override
public void visitTopLevel(JCCompilationUnit unit) {
for (JCImport importNode : unit.getImports()) {
String name = getLastComponent(importNode.getQualifiedIdentifier());
if (importNode.isStatic()) {
unusedStaticImports.put(name, importNode);
} else {
unusedImports.put(name, importNode);
}
}
scan(unit.getPackageAnnotations());
for (JCTree tree : unit.getTypeDecls()) {
scan(tree);
}
}
@Override
public void visitAnnotatedType(JCAnnotatedType node) {
if (checkAnnotations(node.getAnnotations(), node)) {
super.visitAnnotatedType(node);
}
}
@Override
public void visitClassDef(JCClassDecl node) {
if (checkAnnotations(node.getModifiers().getAnnotations(), node)) {
super.visitClassDef(node);
}
}
@Override
public void visitIdent(JCIdent node) {
String name = node.getName().toString();
unusedImports.remove(name);
unusedStaticImports.remove(name);
}
@Override
public void visitMethodDef(JCMethodDecl node) {
if (checkAnnotations(node.getModifiers().getAnnotations(), node)) {
super.visitMethodDef(node);
}
}
@Override
public void visitVarDef(JCVariableDecl node) {
if (checkAnnotations(node.getModifiers().getAnnotations(), node)) {
super.visitVarDef(node);
}
}
// Private methods.
/**
* Checks for any J2ObjCIncompatible annotations. Returns whether
* the caller should to continue scanning this node.
*/
private boolean checkAnnotations(List<JCAnnotation> annotations, JCTree node) {
for (JCAnnotation annotation : annotations) {
if (isJ2ObjCIncompatible(annotation)) {
nodesToStrip.add(node);
return false;
}
}
return true;
}
private int startPosition(JCTree node) {
return TreeInfo.getStartPos(node);
}
private int endPosition(JCTree node) {
return TreeInfo.getEndPos(node, endPositions);
}
private String stripSource(String source) {
nodesToStrip.addAll(unusedImports.values());
nodesToStrip.addAll(unusedStaticImports.values());
StringBuilder sb = new StringBuilder();
int currentIdx = 0;
for (JCTree node : nodesToStrip) {
int startPos = startPosition(node);
if (startPos < currentIdx) {
continue;
}
int endPos = endPosition(node);
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();
}
private boolean isJ2ObjCIncompatible(JCAnnotation modifier) {
String name = getLastComponent(modifier.annotationType);
return name.equals("J2ObjCIncompatible");
}
private String getLastComponent(JCTree name) {
switch (name.getKind()) {
case IDENTIFIER:
return ((JCIdent) name).getName().toString();
case MEMBER_SELECT:
return ((JCFieldAccess) name).getIdentifier().toString();
default:
return "";
}
}
static class Node implements Comparable<Node> {
private final JCTree tree;
private final int startPos;
Node(JCTree tree) {
this.tree = tree;
this.startPos = TreeInfo.getStartPos(tree);
}
@Override
public int compareTo(Node other) {
return Integer.compare(this.startPos, other.startPos);
}
JCTree getTree() {
return tree;
}
int getStartpos() {
return startPos;
}
@Override
public boolean equals(Object obj) {
return obj instanceof Node && tree.equals(((Node) obj).tree);
}
@Override
public int hashCode() {
return tree.hashCode();
}
}
}