1/*
2 * Copyright (C) 2010 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.streamhtmlparser.util;
18
19/**
20 * Records (stores) characters supplied one at a time conditional on
21 * whether recording is currently enabled.
22 *
23 * <p>When {@link #maybeRecord(char)} is called, it will add the
24 * supplied character to the recording buffer but only if
25 * recording is in progress. This is useful in our
26 * {@link com.google.security.streamhtmlparser.HtmlParser}
27 * as the caller logic to enable/disable recording is decoupled from the logic
28 * of recording.
29 *
30 * <p>This is a specialized class - of no use to external code -
31 * which aims to be 100% compatible with the corresponding logic
32 * in the C-version of the HtmlParser, specifically in
33 * <code>statemachine.c</code>. In particular:
34 * <ul>
35 *   <li>The {@code startRecording()} and {@code stopRecording()} methods
36 *       may be called repeatedly without interleaving since the C version is
37 *       not guaranteed to interleave them.
38 *   <li>There is a size limit to the recording buffer as set in
39 *       {@link #RECORDING_BUFFER_SIZE}. Once the size is
40 *       reached, no further characters are recorded regardless of whether
41 *       recording is currently enabled.
42 * </ul>
43 */
44public class CharacterRecorder {
45
46  /**
47   * How many characters can be recorded before stopping to accept new
48   * ones. Set to one less than in the C-version as we do not need
49   * to reserve a character for the terminating null.
50   */
51  public static final int RECORDING_BUFFER_SIZE = 255;
52
53  /**
54   * This is where characters provided for recording are stored. Given
55   * that the <code>CharacterRecorder</code> object is re-used, might as well
56   * allocate the full size from the get-go.
57   */
58  private final StringBuilder sb;
59
60  /** Holds whether we are currently recording characters or not. */
61  private boolean recording;
62
63  /**
64   * Constructs an empty character recorder of fixed size currently
65   * not recording. See {@link #RECORDING_BUFFER_SIZE} for the size.
66   */
67  public CharacterRecorder() {
68    sb = new StringBuilder(RECORDING_BUFFER_SIZE);
69    recording = false;
70  }
71
72  /**
73   * Constructs a character recorder of fixed size that is a copy
74   * of the one provided. In particular it has the same recording
75   * setting and the same contents.
76   *
77   * @param aCharacterRecorder the {@code CharacterRecorder} to copy
78   */
79  public CharacterRecorder(CharacterRecorder aCharacterRecorder) {
80    recording = aCharacterRecorder.recording;
81    sb = new StringBuilder(RECORDING_BUFFER_SIZE);
82    sb.append(aCharacterRecorder.getContent());
83  }
84
85  /**
86   * Enables recording for incoming characters. The recording buffer is cleared
87   * of content it may have contained.
88   */
89  public void startRecording() {
90    // This is very fast, no memory (re-) allocation will take place.
91    sb.setLength(0);
92    recording = true;
93  }
94
95  /**
96   * Disables recording further characters.
97   */
98  public void stopRecording() {
99    recording = false;
100  }
101
102  /**
103   * Records the {@code input} if recording is currently on and we
104   * have space available in the buffer. If recording is not
105   * currently on, this method will not perform any action.
106   *
107   * @param input the character to record
108   */
109  public void maybeRecord(char input) {
110    if (recording && (sb.length() < RECORDING_BUFFER_SIZE)) {
111      sb.append(input);
112    }
113  }
114
115  /**
116   * Empties the underlying storage but does not change the recording
117   * state [i.e whether we are recording or not incoming characters].
118   */
119  public void clear() {
120    sb.setLength(0);
121  }
122
123  /**
124   * Empties the underlying storage and resets the recording indicator
125   * to indicate we are not recording currently.
126   */
127  public void reset() {
128    clear();
129    recording = false;
130  }
131
132  /**
133   * Returns the characters recorded in a {@code String} form. This
134   * method has no side-effects, the characters remain stored as is.
135   *
136   * @return the contents in a {@code String} form
137   */
138  public String getContent() {
139    return sb.toString();
140  }
141
142  /**
143   * Returns whether or not we are currently recording incoming characters.
144   *
145   * @return {@code true} if we are recording, {@code false} otherwise
146   */
147  public boolean isRecording() {
148    return recording;
149  }
150
151  /**
152   * Returns the full state of the object in a human readable form. The
153   * format of the returned {@code String} is not specified and is
154   * subject to change.
155   *
156   * @return the full state of this object
157   */
158  @Override
159  public String toString() {
160    return String.format("In recording: %s; Value: %s", isRecording(),
161                         sb.toString());
162  }
163}
164