/* * Copyright 2011 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.Joiner; import com.google.common.base.Preconditions; import com.google.common.io.CharSource; import java.io.IOException; import java.io.LineNumberReader; import java.io.StringReader; import java.nio.CharBuffer; import java.util.ArrayList; import java.util.List; import javax.tools.JavaFileObject; /** * Representation of a mutable Java source file. * * This class is not thread-safe. * * @author sjnickerson@google.com (Simon Nickerson) * @author alexeagle@google.com (Alex Eagle) */ public class SourceFile { private final String path; private final StringBuilder sourceBuilder; public static SourceFile create(JavaFileObject fileObject) throws IOException { return new SourceFile(fileObject.toUri().getPath(), fileObject.getCharContent(false)); } public SourceFile(String path, CharSequence source) { this.path = path; sourceBuilder = new StringBuilder(source); } /** * Returns the path for this source file */ public String getPath() { return path; } /** * Returns a copy of code as a list of lines. */ public List<String> getLines() { try { return CharSource.wrap(sourceBuilder).readLines(); } catch (IOException e) { throw new AssertionError("IOException not possible, as the string is in-memory"); } } /** * Returns a copy of the code as a string. */ public String getSourceText() { return sourceBuilder.toString(); } public CharSequence getAsSequence() { return CharBuffer.wrap(sourceBuilder).asReadOnlyBuffer(); } /** * Clears the current source test for this SourceFile and resets it to * the passed-in value. */ public void setSourceText(CharSequence source) { sourceBuilder.setLength(0); // clear StringBuilder sourceBuilder.append(source); } /** * Returns a fragment of the source code as a string. * * <p>This method uses the same conventions as {@link String#substring(int, int)} for its start * and end parameters. */ public String getFragmentByChars(int startPosition, int endPosition) { return sourceBuilder.substring(startPosition, endPosition); } /** * Returns a fragment of the source code between the two stated line numbers. The parameters * represent <b>inclusive</b> line numbers. * * <p>The returned fragment will end in a newline. */ public String getFragmentByLines(int startLine, int endLine) { Preconditions.checkArgument(startLine <= endLine); return Joiner.on("\n").join(getLines(startLine, endLine)) + "\n"; } private List<String> getLines(int startLine, int endLine) { LineNumberReader reader = new LineNumberReader(new StringReader(sourceBuilder.toString())); List<String> lines = new ArrayList<>(endLine - startLine + 1); String line; try { while ((line = reader.readLine()) != null) { if (reader.getLineNumber() >= startLine) { lines.add(line); } if (reader.getLineNumber() >= endLine) { break; } } return lines; } catch (IOException e) { throw new AssertionError("Wrapped StringReader should not produce I/O exceptions"); } } /** * Replace the source code with the new lines of code. */ public void replaceLines(List<String> lines) { sourceBuilder.replace(0, sourceBuilder.length(), Joiner.on("\n").join(lines) + "\n"); } /** * Replace the source code between the start and end lines with some new lines of code. */ public void replaceLines(int startLine, int endLine, List<String> replacementLines) { Preconditions.checkArgument(startLine <= endLine); List<String> originalLines = getLines(); List<String> newLines = new ArrayList<>(); for (int i = 0; i < originalLines.size(); i++) { int lineNum = i + 1; if (lineNum == startLine) { newLines.addAll(replacementLines); } else if (lineNum > startLine && lineNum <= endLine) { // Skip } else { newLines.add(originalLines.get(i)); } } replaceLines(newLines); } /** * Replace the source code between the start and end character positions with a new string. * * <p>This method uses the same conventions as {@link String#substring(int, int)} for its start * and end parameters. */ public void replaceChars(int startPosition, int endPosition, String replacement) { try { sourceBuilder.replace(startPosition, endPosition, replacement); } catch (StringIndexOutOfBoundsException e) { throw new IndexOutOfBoundsException( String.format("Replacement cannot be made. Source file %s has length %d, requested start " + "position %d, requested end position %d, replacement %s", path, sourceBuilder.length(), startPosition, endPosition, replacement)); } } }