aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/yaml/snakeyaml/reader/StreamReader.java
blob: 6799e76e9824bf0a86525502ee2db22ab3c82825 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/**
 * Copyright (c) 2008, SnakeYAML
 *
 * 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 org.yaml.snakeyaml.reader;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Arrays;
import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.scanner.Constant;

/**
 * Reader: checks if code points are in allowed range. Returns '\0' when end of data has been
 * reached.
 */
public class StreamReader {

  private String name;
  private final Reader stream;
  /**
   * Read data (as a moving window for input stream)
   */
  private int[] dataWindow;

  /**
   * Real length of the data in dataWindow
   */
  private int dataLength;

  /**
   * The variable points to the current position in the data array
   */
  private int pointer = 0;
  private boolean eof;
  private int index = 0; // in code points
  private int line = 0;
  private int column = 0; // in code points
  private final char[] buffer; // temp buffer for one read operation (to avoid
  // creating the array in stack)

  private static final int BUFFER_SIZE = 1025;

  public StreamReader(String stream) {
    this(new StringReader(stream));
    this.name = "'string'";
  }

  public StreamReader(Reader reader) {
    this.name = "'reader'";
    this.dataWindow = new int[0];
    this.dataLength = 0;
    this.stream = reader;
    this.eof = false;
    this.buffer = new char[BUFFER_SIZE];
  }

  public static boolean isPrintable(final String data) {
    final int length = data.length();
    for (int offset = 0; offset < length;) {
      final int codePoint = data.codePointAt(offset);

      if (!isPrintable(codePoint)) {
        return false;
      }

      offset += Character.charCount(codePoint);
    }

    return true;
  }

  public static boolean isPrintable(final int c) {
    return (c >= 0x20 && c <= 0x7E) || c == 0x9 || c == 0xA || c == 0xD || c == 0x85
        || (c >= 0xA0 && c <= 0xD7FF) || (c >= 0xE000 && c <= 0xFFFD)
        || (c >= 0x10000 && c <= 0x10FFFF);
  }

  public Mark getMark() {
    return new Mark(name, this.index, this.line, this.column, this.dataWindow, this.pointer);
  }

  public void forward() {
    forward(1);
  }

  /**
   * read the next length characters and move the pointer. if the last character is high surrogate
   * one more character will be read
   *
   * @param length amount of characters to move forward
   */
  public void forward(int length) {
    for (int i = 0; i < length && ensureEnoughData(); i++) {
      int c = dataWindow[pointer++];
      this.index++;
      if (Constant.LINEBR.has(c)
          || (c == '\r' && (ensureEnoughData() && dataWindow[pointer] != '\n'))) {
        this.line++;
        this.column = 0;
      } else if (c != 0xFEFF) {
        this.column++;
      }
    }
  }

  public int peek() {
    return (ensureEnoughData()) ? dataWindow[pointer] : '\0';
  }

  /**
   * Peek the next index-th code point
   *
   * @param index to peek
   * @return the next index-th code point
   */
  public int peek(int index) {
    return (ensureEnoughData(index)) ? dataWindow[pointer + index] : '\0';
  }

  /**
   * peek the next length code points
   *
   * @param length amount of the characters to peek
   * @return the next length code points
   */
  public String prefix(int length) {
    if (length == 0) {
      return "";
    } else if (ensureEnoughData(length)) {
      return new String(this.dataWindow, pointer, length);
    } else {
      return new String(this.dataWindow, pointer, Math.min(length, dataLength - pointer));
    }
  }

  /**
   * prefix(length) immediately followed by forward(length)
   *
   * @param length amount of characters to get
   * @return the next length code points
   */
  public String prefixForward(int length) {
    final String prefix = prefix(length);
    this.pointer += length;
    this.index += length;
    // prefix never contains new line characters
    this.column += length;
    return prefix;
  }

  private boolean ensureEnoughData() {
    return ensureEnoughData(0);
  }

  private boolean ensureEnoughData(int size) {
    if (!eof && pointer + size >= dataLength) {
      update();
    }
    return (this.pointer + size) < dataLength;
  }

  private void update() {
    try {
      int read = stream.read(buffer, 0, BUFFER_SIZE - 1);
      if (read > 0) {
        int cpIndex = (dataLength - pointer);
        dataWindow = Arrays.copyOfRange(dataWindow, pointer, dataLength + read);

        if (Character.isHighSurrogate(buffer[read - 1])) {
          if (stream.read(buffer, read, 1) == -1) {
            eof = true;
          } else {
            read++;
          }
        }

        int nonPrintable = ' ';
        for (int i = 0; i < read; cpIndex++) {
          int codePoint = Character.codePointAt(buffer, i);
          dataWindow[cpIndex] = codePoint;
          if (isPrintable(codePoint)) {
            i += Character.charCount(codePoint);
          } else {
            nonPrintable = codePoint;
            i = read;
          }
        }

        dataLength = cpIndex;
        pointer = 0;
        if (nonPrintable != ' ') {
          throw new ReaderException(name, cpIndex - 1, nonPrintable,
              "special characters are not allowed");
        }
      } else {
        eof = true;
      }
    } catch (IOException ioe) {
      throw new YAMLException(ioe);
    }
  }


  public int getColumn() {
    return column;
  }

  /**
   * @return current position as number (in characters) from the beginning of the stream
   */
  public int getIndex() {
    return index;
  }

  public int getLine() {
    return line;
  }
}