/*
* Copyright 2012 Google Inc. All rights reserved.
*
* 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.errorprone.apply;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.sun.tools.javac.tree.EndPosTable;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCImport;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Represents a list of import statements. Supports adding and removing import statements and pretty
* printing the result as source code. Sorts and organizes the imports using the given {@code
* importOrganizer}.
*
* @author eaftan@google.com (Eddie Aftandilian)
*/
public class ImportStatements {
private final ImportOrganizer importOrganizer;
private int startPos = Integer.MAX_VALUE;
private int endPos = -1;
private final Set<String> importStrings;
private boolean hasExistingImports;
public static ImportStatements create(
JCCompilationUnit compilationUnit, ImportOrganizer importOrganizer) {
return new ImportStatements(
(JCExpression) compilationUnit.getPackageName(),
compilationUnit.getImports(),
compilationUnit.endPositions,
importOrganizer);
}
public ImportStatements(
JCExpression packageTree, List<JCImport> importTrees, EndPosTable endPositions) {
this(packageTree, importTrees, endPositions, ImportOrganizer.STATIC_FIRST_ORGANIZER);
}
ImportStatements(
JCExpression packageTree,
List<JCImport> importTrees,
EndPosTable endPositions,
ImportOrganizer importOrganizer) {
// find start, end positions for current list of imports (for replacement)
if (importTrees.isEmpty()) {
// start/end positions are just after the package expression
hasExistingImports = false;
startPos =
packageTree != null
? packageTree.getEndPosition(endPositions) + 2 // +2 for semicolon and newline
: 0;
endPos = startPos;
} else {
// process list of imports and find start/end positions
hasExistingImports = true;
for (JCImport importTree : importTrees) {
int currStartPos = importTree.getStartPosition();
int currEndPos = importTree.getEndPosition(endPositions);
startPos = Math.min(startPos, currStartPos);
endPos = Math.max(endPos, currEndPos);
}
}
// sanity check for start/end positions
Preconditions.checkState(startPos <= endPos);
this.importOrganizer = importOrganizer;
// convert list of JCImports to set of unique strings
importStrings = new LinkedHashSet<>();
importStrings.addAll(
Lists.transform(
importTrees,
new Function<JCImport, String>() {
@Override
public String apply(JCImport input) {
String importExpr = input.toString();
return CharMatcher.whitespace()
.or(CharMatcher.is(';'))
.trimTrailingFrom(importExpr);
}
}));
}
/**
* Return the start position of the import statements.
*/
public int getStartPos() {
return startPos;
}
/**
* Return the end position of the import statements.
*/
public int getEndPos() {
return endPos;
}
/**
* Add an import to the list of imports. If the import is already in the list, does nothing. The
* import should be of the form "import foo.bar".
*
* @param importToAdd a string representation of the import to add
* @return true if the import was added
*/
public boolean add(String importToAdd) {
return importStrings.add(importToAdd);
}
/**
* Add all imports in a collection to this list of imports. Does not add any imports that are
* already in the list.
*
* @param importsToAdd a collection of imports to add
* @return true if any imports were added to the list
*/
public boolean addAll(Collection<String> importsToAdd) {
return importStrings.addAll(importsToAdd);
}
/**
* Remove an import from the list of imports. If the import is not in the list, does nothing. The
* import should be of the form "import foo.bar".
*
* @param importToRemove a string representation of the import to remove
* @return true if the import was removed
*/
public boolean remove(String importToRemove) {
return importStrings.remove(importToRemove);
}
/**
* Removes all imports in a collection to this list of imports. Does not remove any imports that
* are not in the list.
*
* @param importsToRemove a collection of imports to remove
* @return true if any imports were removed from the list
*/
public boolean removeAll(Collection<String> importsToRemove) {
return importStrings.removeAll(importsToRemove);
}
/** Returns a string representation of the imports as Java code in correct order. */
@Override
public String toString() {
if (importStrings.size() == 0) {
return "";
}
StringBuilder result = new StringBuilder();
if (!hasExistingImports) {
// insert a newline after the package expression, then add imports
result.append('\n');
}
// output organized imports
for (String importString : importOrganizer.organizeImports(importStrings)) {
if (!importString.isEmpty()) {
result.append(importString).append(';');
}
result.append('\n');
}
String replacementString = result.toString();
if (!hasExistingImports) {
return replacementString;
} else {
return CharMatcher.whitespace().trimTrailingFrom(replacementString); // trim last newline
}
}
}