/*
* Copyright (C) 2010 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.streamhtmlparser.util;
import com.google.common.base.Preconditions;
import java.util.Arrays;
/**
* Implements a circular (ring) buffer of characters with specialized
* application logic in order to determine the context of some
* Javascript content that is being parsed.
*
* This is a specialized class - of no use to external code -
* which aims to be 100% compatible with the corresponding logic
* in the C-version of the HtmlParser, specifically
* jsparser.c
. In particular:
*
appendChar
, it appends a string
* to the buffer. Sequences of whitespace and newlines
* are folded into one character to save space. Null strings are
* not allowed.
*
* @param input the {@code String} to append, cannot be {@code null}
*/
// TODO: Move to testing since not used in code.
public void appendString(String input) {
if (input == null) {
throw new NullPointerException("input == null is not allowed");
}
for (int i = 0; i < input.length(); i++) {
appendChar(input.charAt(i));
}
}
/**
* Appends a character to the buffer. We fold sequences of whitespace and
* newlines into one to save space.
*
* @param input the {@code char} to append
*/
public void appendChar(char input) {
if (HtmlUtils.isJavascriptWhitespace(input) &&
HtmlUtils.isJavascriptWhitespace(getChar(-1))) {
return;
}
buffer[endIndex] = input;
endIndex = (endIndex + 1) % buffer.length;
if (endIndex == startIndex) {
startIndex = (endIndex + 1) % buffer.length;
}
}
/**
* Returns the last character in the buffer and removes it from the buffer
* or the NUL character '\0' if the buffer is empty.
*
* @return last character in the buffer or '\0' if the buffer is empty
*/
public char popChar() {
if (startIndex == endIndex) {
return '\0';
}
endIndex--;
if (endIndex < 0) {
endIndex += buffer.length;
}
return buffer[endIndex];
}
/**
* Returns the character at a given index in the buffer or nul ('\0')
* if the index is outside the range of the buffer. Such could happen
* if the buffer is not filled enough or the index is larger than the
* size of the buffer.
*
* Position must be negative where -1 is the index of the last * character in the buffer. * * @param position The index into the buffer * * @return character at the requested index */ public char getChar(int position) { assert(position < 0); // Developer error if it triggers. int absolutePosition = getAbsolutePosition(position); if (absolutePosition < 0) { return '\0'; } return buffer[absolutePosition]; } /** * Sets the given {@code input} at the given {@code position} of the buffer. * Returns {@code true} if we succeeded or {@code false} if we * failed (i.e. the write was beyond the buffer boundary). * *
Index positions are negative where -1 is the index of the * last character in the buffer. * * @param position The index at which to set the character * @param input The character to set in the buffer * @return {@code true} if we succeeded, {@code false} otherwise */ public boolean setChar(int position, char input) { assert(position < 0); // Developer error if it triggers. int absolutePosition = getAbsolutePosition(position); if (absolutePosition < 0) { return false; } buffer[absolutePosition] = input; return true; } /** * Returns the last javascript identifier/keyword in the buffer. * * @return the last identifier or {@code null} if none was found */ public String getLastIdentifier() { int end = -1; if (HtmlUtils.isJavascriptWhitespace(getChar(-1))) { end--; } int position; for (position = end; HtmlUtils.isJavascriptIdentifier(getChar(position)); position--) { } if ((position + 1) >= end) { return null; } return slice(position + 1, end); } /** * Returns a slice of the buffer delimited by the given indices. * * The start and end indexes represent the start and end of the * slice to copy. If the start argument extends beyond the beginning * of the buffer, the slice will only contain characters * starting from the beginning of the buffer. * * @param start The index of the first character the copy * @param end the index of the last character to copy * * @return {@code String} between the given indices */ public String slice(int start, int end) { // Developer error if any of the asserts below fail. Preconditions.checkArgument(start <= end); Preconditions.checkArgument(start < 0); Preconditions.checkArgument(end < 0); StringBuffer output = new StringBuffer(); for (int position = start; position <= end; position++) { char c = getChar(position); if (c != '\0') { output.append(c); } } return new String(output); } /** * Returns the position relative to the start of the buffer or -1 * if the position is past the size of the buffer. * * @param position the index to be translated * @return the position relative to the start of the buffer */ private int getAbsolutePosition(int position) { assert (position < 0); // Developer error if it triggers. if (position <= -buffer.length) { return -1; } int len = endIndex - startIndex; if (len < 0) { len += buffer.length; } if (position < -len) { return -1; } int absolutePosition = (position + endIndex) % buffer.length; if (absolutePosition < 0) { absolutePosition += buffer.length; } return absolutePosition; } }