aboutsummaryrefslogtreecommitdiff
path: root/src/vogar/monitor/InterleavedReader.java
blob: a97ba5619108996ff1990d387253a47f677f4de2 (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

/*
 * 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.
 */

// This file is a copy of caliper's InterleavedReader.java
// (from code.google.com, last change Sep 9 2011)
// with Android specific changes marked with "BEGIN android-changed".

package vogar.monitor;

import com.google.gson.JsonParser;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.Reader;

/**
 * Reads a stream containing inline JSON objects. Each JSON object is prefixed
 * by a marker string and suffixed by a newline character.
 */
public final class InterleavedReader implements Closeable {

  /**
   * The length of the scratch buffer to search for markers in. Also acts as an
   * upper bound on the length of returned strings. Not used as an I/O buffer.
   */
  private static final int BUFFER_LENGTH = 80;

  private final String marker;
  private final BufferedReader reader;
  private final JsonParser jsonParser = new JsonParser();

  public static final String DEFAULT_MARKER = "//ZxJ/";

  public InterleavedReader(Reader reader) {
    this(DEFAULT_MARKER, reader);
  }

  public InterleavedReader(String marker, Reader reader) {
    if (marker.length() > BUFFER_LENGTH) {
      throw new IllegalArgumentException("marker.length() > BUFFER_LENGTH");
    }
    this.marker = marker;
    this.reader = reader instanceof BufferedReader
        ? (BufferedReader) reader
        : new BufferedReader(reader);
  }

  /**
   * Returns the next value in the stream: either a String, a JsonElement, or
   * null to indicate the end of the stream. Callers should use instanceof to
   * inspect the return type.
   */
  public Object read() throws IOException {
    char[] buffer = new char[BUFFER_LENGTH];
    // BEGIN android-changed:
    // Double the lookahead size. This is a safety measure in the presence of
    // new lines, where the line feed character is being skipped. The lookahead
    // check in the BufferedReader class does not take these skipped characters
    // into account.
    reader.mark(BUFFER_LENGTH << 1);
    // END android-changed.
    int count = 0;
    int textEnd;

    while (true) {
      int r = -1;

      try {
          r = reader.read(buffer, count, buffer.length - count);
      } catch (IOException e) {
          // When running under gcstress, the output stream may be closed outside our control when
          // the target process exits. In order to allow this, catch the stream closed exception and
          // return whatever has been buffered already. (b/308917607)
      }

      if (r == -1) {
        // the input is exhausted; return the remaining characters
        textEnd = count;
        break;
      }

      count += r;
      int possibleMarker = findPossibleMarker(buffer, count);

      if (possibleMarker != 0) {
        // return the characters that precede the marker
        textEnd = possibleMarker;
        break;
      }

      if (count < marker.length()) {
        // the buffer contains only the prefix of a marker so we must read more
        continue;
      }

      // we've read a marker so return the value that follows
      reader.reset();
      String json = reader.readLine().substring(marker.length());
      return jsonParser.parse(json);
    }

    if (count == 0) {
      return null;
    }

    // return characters
    reader.reset();
    count = reader.read(buffer, 0, textEnd);
    return new String(buffer, 0, count);
  }

  @Override public void close() throws IOException {
    reader.close();
  }

  /**
   * Returns the index of marker in {@code chars}, stopping at {@code limit}.
   * Should the chars end with a prefix of marker, the offset of that prefix
   * is returned.
   */
  int findPossibleMarker(char[] chars, int limit) {
    search:
    for (int i = 0; true; i++) {
      for (int m = 0; m < marker.length() && i + m < limit; m++) {
        if (chars[i + m] != marker.charAt(m)) {
          continue search;
        }
      }
      return i;
    }
  }
}