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 
17 package 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  */
44 public 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    */
CharacterRecorder()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    */
CharacterRecorder(CharacterRecorder aCharacterRecorder)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    */
startRecording()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    */
stopRecording()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    */
maybeRecord(char input)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    */
clear()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    */
reset()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    */
getContent()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    */
isRecording()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
toString()159   public String toString() {
160     return String.format("In recording: %s; Value: %s", isRecording(),
161                          sb.toString());
162   }
163 }
164