Skip to content

Commit ff930ca

Browse files
authored
Merge pull request #16 from cschanck/PushbackReader
Add pushback reader, keeps position in Reader stream
2 parents 00b5960 + 107daee commit ff930ca

File tree

3 files changed

+404
-4
lines changed

3 files changed

+404
-4
lines changed

src/main/java/org/sfj/ChiseledMap.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,18 @@ public synchronized boolean ioSet(K key, V v) throws IOException {
498498
return map.put(key, newAddr) != null;
499499
}
500500

501+
public synchronized V ioGetSet(K key, V v) throws IOException {
502+
Objects.requireNonNull(v);
503+
Long addr = map.get(key);
504+
V ret = null;
505+
if (addr != null) {
506+
ret = fetch(addr, false, null).getValue();
507+
}
508+
long newAddr = append(key, v);
509+
map.put(key, newAddr);
510+
return ret;
511+
}
512+
501513
/**
502514
* Copy only live entries to another file. Blocks writes to this map
503515
* while snapshotting.
@@ -542,11 +554,9 @@ public boolean set(K key, V value) {
542554
}
543555

544556
@Override
545-
public synchronized V put(K key, V value) {
557+
public V put(K key, V value) {
546558
try {
547-
V p = ioGet(key);
548-
ioSet(key, value);
549-
return p;
559+
return ioGetSet(key, value);
550560
} catch (IOException e) {
551561
throw new RuntimeIOException(e);
552562
}
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/*
2+
* Copyright 2020 C. Schanck
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+
package org.sfj;
17+
18+
import java.io.IOException;
19+
import java.io.LineNumberReader;
20+
import java.io.Reader;
21+
import java.io.StringReader;
22+
import java.nio.CharBuffer;
23+
24+
/**
25+
* This is a basically an infinite pushback Reader, which handles line endings
26+
* resiliently. It will not be super fast, as all calls end up going through
27+
* the single char read() method, but it will keep line/column position properly
28+
* across line endings, across character and line endings being pushed back, etc.
29+
* <p>Since it is inifinite, it keeps track of the line lengths of all read lines in
30+
* a humpty-dumpty linked list, so when newlines are pushed back, the column position
31+
* is maintained. A similarly linked list is used for pushback chars. I am not certain
32+
* of the need for a hand rolled linked list for this, but the overhead of boxing
33+
* seems blah.
34+
* <p>End-of-line is denoted by newlines only, and only newlines should be pushed back.
35+
* <p>It would have been nice to fit this into PegLeg, as it would be much
36+
* more efficient for parsing.
37+
*/
38+
public class PositionalPushbackReader extends LineNumberReader {
39+
40+
private static class IntNode {
41+
IntNode next;
42+
int val;
43+
44+
public IntNode(int val) {
45+
this.val = val;
46+
}
47+
}
48+
49+
private static class IntQueue {
50+
private IntNode head = null;
51+
private IntNode tail = null;
52+
53+
public void clear() {
54+
head = tail = null;
55+
}
56+
57+
void push(int val) {
58+
IntNode p = new IntNode(val);
59+
if (head == null) {
60+
head = tail = p;
61+
} else if (head == tail) {
62+
p.next = tail;
63+
head = p;
64+
} else {
65+
p.next = head;
66+
head = p;
67+
}
68+
}
69+
70+
int pop() {
71+
if (head == null) {
72+
throw new IllegalStateException();
73+
}
74+
int ret = head.val;
75+
if (head == tail) {
76+
head = tail = null;
77+
} else {
78+
head = head.next;
79+
}
80+
return ret;
81+
}
82+
83+
boolean isEmpty() {
84+
return head == null;
85+
}
86+
}
87+
88+
private final IntQueue pushback = new IntQueue();
89+
private final IntQueue lineLengths = new IntQueue();
90+
private int latestReadPosInLine = 0;
91+
private int latestReadLineNumber = 0;
92+
private boolean lastReadWasNewline = false;
93+
94+
public PositionalPushbackReader(String source) {
95+
this(new StringReader(source));
96+
}
97+
98+
public PositionalPushbackReader(Reader r) {
99+
super(r);
100+
}
101+
102+
/**
103+
* Core method. Reads a char, possibly from pushback, return a char, or -1
104+
* on end of input.
105+
* @return char as int, or -1 on end of input.
106+
* @throws IOException
107+
*/
108+
public int read() throws IOException {
109+
int ch = pushback.isEmpty() ? super.read() : pushback.pop();
110+
if (ch == -1) { return -1; }
111+
if (lastReadWasNewline) {
112+
lastReadWasNewline = false;
113+
latestReadPosInLine = 0;
114+
latestReadLineNumber++;
115+
}
116+
if (ch == '\n') {
117+
lastReadWasNewline = true;
118+
latestReadPosInLine++;
119+
lineLengths.push(latestReadPosInLine);
120+
} else {
121+
lastReadWasNewline = false;
122+
latestReadPosInLine++;
123+
}
124+
return ch;
125+
}
126+
127+
/**
128+
* Pushback a character. Use '\n' newline for end of line. If you
129+
* pushback a char that was not read, the line number/column position
130+
* maintanence behavior is undefined.
131+
* @param ch char to pushback.
132+
*/
133+
public void pushback(int ch) {
134+
if (ch == -1) { throw new IllegalArgumentException(); }
135+
if (--latestReadPosInLine == 0) {
136+
latestReadLineNumber--;
137+
latestReadPosInLine = lineLengths.pop();
138+
lastReadWasNewline = true;
139+
} else {
140+
lastReadWasNewline = false;
141+
}
142+
pushback.push(ch);
143+
}
144+
145+
@Override
146+
public long skip(long n) throws IOException {
147+
int cnt = 0;
148+
while (cnt < n) {
149+
int ch = read();
150+
if (ch == -1) { break; }
151+
cnt++;
152+
}
153+
return cnt;
154+
}
155+
156+
@Override
157+
public boolean ready() throws IOException {
158+
return !pushback.isEmpty() || super.ready();
159+
}
160+
161+
@Override
162+
public void reset() throws IOException {
163+
pushback.clear();
164+
latestReadLineNumber = 0;
165+
latestReadPosInLine = 0;
166+
super.reset();
167+
}
168+
169+
@Override
170+
public int read(CharBuffer target) throws IOException {
171+
return super.read(target);
172+
}
173+
174+
@Override
175+
public int read(char[] cbuf) throws IOException {
176+
return read(cbuf, 0, cbuf.length);
177+
}
178+
179+
@Override
180+
public boolean markSupported() {
181+
return false;
182+
}
183+
184+
@Override
185+
public void mark(int readAheadLimit) {
186+
throw new UnsupportedOperationException();
187+
}
188+
189+
@Override
190+
public int read(char[] cbuf, int off, int len) throws IOException {
191+
int cnt = 0;
192+
while (cnt < len) {
193+
int ch = read();
194+
if (ch == -1) { break; }
195+
cnt++;
196+
cbuf[off + cnt] = (char) ch;
197+
}
198+
return cnt;
199+
}
200+
201+
@Override
202+
public void close() throws IOException {
203+
pushback.clear();
204+
super.close();
205+
}
206+
207+
/**
208+
* Position of last read character in the current line. 1 is the first position.
209+
* @return position
210+
*/
211+
public int getColumnPosition() {
212+
return latestReadPosInLine;
213+
}
214+
215+
/**
216+
* Line number of last read character. 1 indexed.
217+
* @return line number
218+
*/
219+
public int getLineNumber() {
220+
return latestReadLineNumber + 1;
221+
}
222+
223+
@Override
224+
public void setLineNumber(int lineNumber) {
225+
throw new UnsupportedOperationException();
226+
}
227+
228+
@Override
229+
public String readLine() throws IOException {
230+
StringBuilder sb = new StringBuilder();
231+
232+
int ch = read();
233+
if (ch == -1) {
234+
return null;
235+
}
236+
for (; ch != -1 && ((char) ch != '\n'); ch = read()) {
237+
sb.append(ch);
238+
}
239+
return sb.toString();
240+
}
241+
}

0 commit comments

Comments
 (0)