156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson/*
256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Copyright (C) 2010 Google Inc.
356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Licensed under the Apache License, Version 2.0 (the "License");
556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * you may not use this file except in compliance with the License.
656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * You may obtain a copy of the License at
756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * http://www.apache.org/licenses/LICENSE-2.0
956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
1056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Unless required by applicable law or agreed to in writing, software
1156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * distributed under the License is distributed on an "AS IS" BASIS,
1256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * See the License for the specific language governing permissions and
1456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * limitations under the License.
1556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */
1656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
1756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonpackage com.google.streamhtmlparser.util;
1856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
1956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport com.google.common.base.Preconditions;
2056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
2156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonimport java.util.Arrays;
2256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
2356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson/**
2456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Implements a circular (ring) buffer of characters with specialized
2556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * application logic in order to determine the context of some
2656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * Javascript content that is being parsed.
2756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *
2856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * This is a specialized class - of no use to external code -
2956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * which aims to be 100% compatible with the corresponding logic
3056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * in the C-version of the HtmlParser, specifically
3156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <code>jsparser.c</code>. In particular:
3256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * <ul>
3356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *   <li> The API is odd, using negative indexes to access content in
3456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *        the buffer. Changing the API would mean changing the test
3556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *        cases and have more difficulty determining whether we are
3656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *        remaining compatible with the C-version. It is left as an
3756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *        exercise for once the code is very stable and proven.
3856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *   <li> Repeated whitespace is folded into just one character to
3956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *        use the space available efficiently.
4056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *   <li> The buffer size is fixed. There is currently no need to
4156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson *        make it variable so we avoid the need for constructors.
4256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson * </ul>
4356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson */
4456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodsonpublic class JavascriptTokenBuffer {
4556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
4656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
4756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Size of the ring buffer used to lookup the last token in the javascript
4856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * stream. The size is somewhat arbitrary but must be larger than
4956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * the biggest token we want to lookup plus three: Two delimiters plus
5056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * an empty ring buffer slot.
5156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
5256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private static final int BUFFER_SIZE = 18;
5356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
5456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /** Storage implementing the circular buffer. */
5556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private final char[] buffer;
5656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
5756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /** Index of the first item in our circular buffer. */
5856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private int startIndex;
5956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
6056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /** Index of the last item in our circular buffer. */
6156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private int endIndex;
6256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
6356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
6456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Constructs an empty javascript token buffer. The size is fixed,
6556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * see {@link #BUFFER_SIZE}.
6656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
6756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public JavascriptTokenBuffer() {
6856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    buffer = new char[BUFFER_SIZE];
6956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    startIndex = 0;
7056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    endIndex = 0;
7156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
7256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
7356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
7456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Constructs a javascript token buffer that is identical to
7556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * the one given. In particular, it has the same size and contents.
7656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
7756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param aJavascriptTokenBuffer the {@code JavascriptTokenBuffer} to copy
7856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
7956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public JavascriptTokenBuffer(JavascriptTokenBuffer aJavascriptTokenBuffer) {
8056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    buffer = Arrays.copyOf(aJavascriptTokenBuffer.buffer,
8156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson                           aJavascriptTokenBuffer.buffer.length);
8256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    startIndex = aJavascriptTokenBuffer.startIndex;
8356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    endIndex = aJavascriptTokenBuffer.endIndex;
8456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
8556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
8656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
8756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * A simple wrapper over <code>appendChar</code>, it appends a string
8856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * to the buffer. Sequences of whitespace and newlines
8956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * are folded into one character to save space. Null strings are
9056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * not allowed.
9156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
9256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param input the {@code String} to append, cannot be {@code null}
9356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
9456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  // TODO: Move to testing since not used in code.
9556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public void appendString(String input) {
9656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (input == null) {
9756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      throw new NullPointerException("input == null is not allowed");
9856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
9956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    for (int i = 0; i < input.length(); i++) {
10056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      appendChar(input.charAt(i));
10156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
10256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
10356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
10456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
10556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Appends a character to the buffer. We fold sequences of whitespace and
10656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * newlines into one to save space.
10756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
10856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param input the {@code char} to append
10956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
11056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public void appendChar(char input) {
11156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (HtmlUtils.isJavascriptWhitespace(input) &&
11256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        HtmlUtils.isJavascriptWhitespace(getChar(-1))) {
11356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return;
11456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
11556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    buffer[endIndex] = input;
11656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    endIndex = (endIndex + 1) % buffer.length;
11756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (endIndex == startIndex) {
11856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      startIndex = (endIndex + 1) % buffer.length;
11956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
12056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
12156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
12256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
12356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Returns the last character in the buffer and removes it from the buffer
12456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * or the NUL character '\0' if the buffer is empty.
12556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
12656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @return last character in the buffer or '\0' if the buffer is empty
12756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
12856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public char popChar() {
12956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (startIndex == endIndex) {
13056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return '\0';
13156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
13256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    endIndex--;
13356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (endIndex < 0) {
13456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      endIndex += buffer.length;
13556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
13656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return buffer[endIndex];
13756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
13856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
13956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
14056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Returns the character at a given index in the buffer or nul ('\0')
14156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * if the index is outside the range of the buffer. Such could happen
14256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * if the buffer is not filled enough or the index is larger than the
14356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * size of the buffer.
14456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
14556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * <p>Position must be negative where -1 is the index of the last
14656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * character in the buffer.
14756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
14856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param position The index into the buffer
14956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
15056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @return character at the requested index
15156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
15256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public char getChar(int position) {
15356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    assert(position < 0);   // Developer error if it triggers.
15456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
15556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int absolutePosition = getAbsolutePosition(position);
15656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (absolutePosition < 0) {
15756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return '\0';
15856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
15956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
16056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return buffer[absolutePosition];
16156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
16256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
16356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
16456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Sets the given {@code input} at the given {@code position} of the buffer.
16556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Returns {@code true} if we succeeded or {@code false} if we
16656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * failed (i.e. the write was beyond the buffer boundary).
16756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
16856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * <p>Index positions are negative where -1 is the index of the
16956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * last character in the buffer.
17056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
17156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param position The index at which to set the character
17256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param input The character to set in the buffer
17356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @return {@code true} if we succeeded, {@code false} otherwise
17456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
17556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public boolean setChar(int position, char input) {
17656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    assert(position < 0);   // Developer error if it triggers.
17756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
17856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int absolutePosition = getAbsolutePosition(position);
17956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (absolutePosition < 0) {
18056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return false;
18156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
18256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
18356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    buffer[absolutePosition] = input;
18456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return true;
18556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
18656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
18756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
18856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
18956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Returns the last javascript identifier/keyword in the buffer.
19056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
19156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @return the last identifier or {@code null} if none was found
19256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
19356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public String getLastIdentifier() {
19456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int end = -1;
19556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
19656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (HtmlUtils.isJavascriptWhitespace(getChar(-1))) {
19756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      end--;
19856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
19956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int position;
20056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    for (position = end; HtmlUtils.isJavascriptIdentifier(getChar(position));
20156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson         position--) {
20256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
20356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if ((position + 1) >= end) {
20456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return null;
20556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
20656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return slice(position + 1, end);
20756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
20856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
20956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
21056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Returns a slice of the buffer delimited by the given indices.
21156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
21256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * The start and end indexes represent the start and end of the
21356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * slice to copy. If the start argument extends beyond the beginning
21456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * of the buffer, the slice will only contain characters
21556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * starting from the beginning of the buffer.
21656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
21756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param start The index of the first character the copy
21856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param end the index of the last character to copy
21956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
22056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @return {@code String} between the given indices
22156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
22256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  public String slice(int start, int end) {
22356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    // Developer error if any of the asserts below fail.
22456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    Preconditions.checkArgument(start <= end);
22556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    Preconditions.checkArgument(start < 0);
22656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    Preconditions.checkArgument(end < 0);
22756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
22856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    StringBuffer output = new StringBuffer();
22956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    for (int position = start; position <= end; position++) {
23056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      char c = getChar(position);
23156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      if (c != '\0') {
23256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson        output.append(c);
23356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      }
23456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
23556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return new String(output);
23656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
23756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson
23856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  /**
23956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * Returns the position relative to the start of the buffer or -1
24056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * if the position is past the size of the buffer.
24156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   *
24256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @param position the index to be translated
24356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   * @return the position relative to the start of the buffer
24456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson   */
24556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  private int getAbsolutePosition(int position) {
24656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    assert (position < 0);   // Developer error if it triggers.
24756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (position <= -buffer.length) {
24856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return -1;
24956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
25056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int len = endIndex - startIndex;
25156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (len < 0) {
25256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      len += buffer.length;
25356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
25456ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (position < -len) {
25556ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      return -1;
25656ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
25756ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    int absolutePosition = (position + endIndex) % buffer.length;
25856ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    if (absolutePosition < 0) {
25956ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson      absolutePosition += buffer.length;
26056ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    }
26156ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson    return absolutePosition;
26256ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson  }
26356ed4167b942ec265f9cee70ac4d71d10b3835ceBen Dodson}
264