com.google.common.css.compiler.passes.CodeBuffer Maven / Gradle / Ivy
Show all versions of closure-stylesheets Show documentation
/*
* Copyright 2015 Google Inc.
*
* 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.common.css.compiler.passes;
import com.google.common.base.Preconditions;
import javax.annotation.Nullable;
/**
* Class to write/delete characters used by {@link CodePrinter} subclasses to print out code.
* It has finalized core public APIs to endure correct update of character index and line index
* while operating on the buffer.
*
* This class can be extended and with helper methods if necessary.
* @see TemplateCompactPrinter
*
*
{@code char} is used as operation unit for methods, there is no support for surrogates.
*
* @author [email protected] (Chenyun Yang)
*/
public class CodeBuffer {
private final StringBuilder sb;
/**
* The index within the line of the next character to be written to the buffer.
* Indices start at 0, following source map v3.
*/
private int nextCharIndex;
/**
* The index of the line that will contain the character at nextCharIndex.
* Indices start at 0, following source map v3.
*/
private int nextLineIndex;
/**
* The index of the last character written to the buffer.
* Indices start at 0, following source map v3.
*/
private int lastCharIndex;
CodeBuffer () {
this.sb = new StringBuilder();
resetIndex();
}
/** Returns buffer as String. */
public final String getOutput() {
return sb.toString();
}
/** Returns the current length of the buffer. */
public final int getCurrentLength() {
return sb.length();
}
/** Returns the last character in the buffer. */
public final char getLastChar() {
return sb.charAt(sb.length() - 1);
}
/**
* Returns {@link #nextCharIndex}.
*/
public final int getNextCharIndex() {
return nextCharIndex;
}
/**
* Returns {@link #nextLineIndex}.
*/
public final int getNextLineIndex() {
return nextLineIndex;
}
/**
* Returns {@link #lastCharIndex}.
*/
public final int getLastCharIndex() {
return lastCharIndex;
}
/**
* Returns the index of the line which contains the last character at lastCharIndex.
* It is always the same or 1 index behind {@link #nextLineIndex}.
*/
public final int getLastLineIndex() {
return nextLineIndex - (
(sb.length() > 0 && sb.charAt(sb.length() - 1) == '\n') ? 1 : 0);
}
/**
* Appends {@code str} to the buffer. The string is safe to contain newlines.
* {@code nextCharIndex} and {@code nextLineIndex} will be updated accordingly.
*
*
Prefer to use append(char c) API to append characters for efficiency.
*/
public final CodeBuffer append(@Nullable String str) {
if (str == null) {
return this;
}
if (str.length() == 1) {
append(str.charAt(0));
} else {
String[] parts = str.split("\n", -1);
for (int i = 0; i < parts.length; i++) {
sb.append(parts[i]);
incrementIndexBy(parts[i].length());
if (i != (parts.length - 1)) {
startNewLine();
}
}
}
return this;
}
/**
* Appends {@code c} to the buffer and update {@code nextCharIndex}.
*/
public final CodeBuffer append(char c) {
if (c == '\n') {
startNewLine();
} else {
sb.append(c);
incrementIndexBy(1);
}
return this;
}
/** Appends the {@code toString} representation of {@code o} to the buffer. */
public final CodeBuffer append(Object o) {
append(o.toString());
return this;
}
/**
* Appends a newline to buffer, resetting {@code nextCharIndex} and incrementing
* {@code nextLineIndex}.
*/
public final CodeBuffer startNewLine() {
sb.append('\n');
incrementIndexForNewline();
return this;
}
/**
* Deletes the last character in the buffer and updates the {@code nextCharIndex} and
* {@code nextLineIndex}.
*/
public final CodeBuffer deleteLastChar() {
if (getCurrentLength() > 0) {
decrementIndex();
sb.deleteCharAt(getCurrentLength() - 1);
}
return this;
}
/** Deletes last {@code n} characters in the buffer. */
public final CodeBuffer deleteLastChars(int n) {
for (int i = 0; i < n; i++) {
deleteLastChar();
}
return this;
}
/**
* Clears the contents of the buffer and resets {@code nextCharIndex}
* and {@code nextLineIndex}.
*/
public final CodeBuffer reset() {
resetIndex();
sb.setLength(0);
sb.trimToSize();
return this;
}
/**
* Deletes the last character from the string builder if the character is as given.
*
*
Subclasses can modify this method in order to delete more in cases where they've added extra
* delimiters.
*
* @param ch the character to delete
*/
public void deleteLastCharIfCharIs(char ch) {
if (getCurrentLength() > 0 && getLastChar() == ch) {
deleteLastChar();
}
}
/** Deletes the end of the buffer if it exactly equals {@code s}. */
public void deleteEndingIfEndingIs(String s) {
if (sb.subSequence(sb.length() - s.length(), sb.length()).equals(s)) {
deleteLastChars(s.length());
}
}
/**
* Updates character-related indexes before or after writing some non-newline characters
* to the buffer. Use {@link #incrementIndexForNewline} when writing '\n'.
*
* @param step numbers of characters writes to buffer
*/
private void incrementIndexBy(int step) {
if (step > 0) {
lastCharIndex = nextCharIndex + step - 1;
nextCharIndex = nextCharIndex + step;
}
}
/**
* Updates character-related indexes before/after writing a newline character to the buffer.
*/
private void incrementIndexForNewline() {
// '\n' is written at {@code nextCharIndex}
lastCharIndex = nextCharIndex;
nextCharIndex = 0;
// future writing starts at a new line
nextLineIndex++;
}
/**
* Updates character-related indexes before deleting a character in buffer.
*
*
Note: Indexes updates must take place before deletion as the last two chars determine
* the behavior.
*/
private void decrementIndex() {
Preconditions.checkState(getCurrentLength() > 0);
nextCharIndex = lastCharIndex;
// Need to look at the last character to determine how to update indexes
int lastIndex = getCurrentLength() - 1;
// As a '\n' will be removed, {@code nextLineIndex} should be moved to previous line
if (sb.charAt(lastIndex) == '\n') {
nextLineIndex--;
}
// When the second to last char is a newline, needs to recalculate the {@code lastCharIndex}
if (lastIndex - 1 > 0 && sb.charAt(lastIndex - 1) == '\n') {
int lastNewline = sb.lastIndexOf("\n");
int secondToLastNewLine = sb.substring(0, lastNewline - 1).lastIndexOf('\n');
if (secondToLastNewLine == -1) {
// when only one line left after deletion
lastCharIndex = lastNewline - 1;
} else {
// Adjust to 0-based index for {@code lastCharIndex}.
lastCharIndex = lastNewline - secondToLastNewLine - 1;
}
} else {
// Otherwise, when deletion happens on same line, move the index one character to the left
lastCharIndex = lastCharIndex - 1;
}
}
private void resetIndex() {
nextCharIndex = 0;
nextLineIndex = 0;
lastCharIndex = -1;
}
}