1 /* 2 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * @test 28 * @bug 8003258 29 * @run testng Lines 30 */ 31 32 import java.io.BufferedReader; 33 import java.io.Reader; 34 import java.io.StringReader; 35 import java.io.LineNumberReader; 36 import java.io.IOException; 37 import java.io.UncheckedIOException; 38 import java.util.HashMap; 39 import java.util.Map; 40 import java.util.ArrayList; 41 import java.util.Iterator; 42 import java.util.NoSuchElementException; 43 import java.util.stream.Stream; 44 import java.util.concurrent.atomic.AtomicInteger; 45 import org.testng.annotations.Test; 46 import static org.testng.Assert.*; 47 48 @Test(groups = "unit") 49 public class Lines { 50 private static final Map<String, Integer> cases = new HashMap<>(); 51 52 static { 53 cases.put("", 0); 54 cases.put("Line 1", 1); 55 cases.put("Line 1\n", 1); 56 cases.put("Line 1\n\n\n", 3); 57 cases.put("Line 1\nLine 2\nLine 3", 3); 58 cases.put("Line 1\nLine 2\nLine 3\n", 3); 59 cases.put("Line 1\n\nLine 3\n\nLine5", 5); 60 } 61 62 /** 63 * Helper Reader class which generate specified number of lines contents 64 * with each line will be "<code>Line <line_number></code>". 65 * 66 * <p>This class also support to simulate {@link IOException} when read pass 67 * a specified line number. 68 */ 69 private static class MockLineReader extends Reader { 70 final int line_count; 71 boolean closed = false; 72 int line_no = 0; 73 String line = null; 74 int pos = 0; 75 int inject_ioe_after_line; 76 77 MockLineReader(int cnt) { 78 this(cnt, cnt); 79 } 80 81 MockLineReader(int cnt, int inject_ioe) { 82 line_count = cnt; 83 inject_ioe_after_line = inject_ioe; 84 } 85 86 public void reset() { 87 synchronized(lock) { 88 line = null; 89 line_no = 0; 90 pos = 0; 91 closed = false; 92 } 93 } 94 95 public void inject_ioe() { 96 inject_ioe_after_line = line_no; 97 } 98 99 public int getLineNumber() { 100 synchronized(lock) { 101 return line_no; 102 } 103 } 104 105 @Override 106 public void close() { closed = true; } 107 108 @Override 109 public int read(char[] buf, int off, int len) throws IOException { 110 synchronized(lock) { 111 if (closed) { 112 throw new IOException("Stream is closed."); 113 } 114 115 if (line == null) { 116 if (line_count > line_no) { 117 line_no += 1; 118 if (line_no > inject_ioe_after_line) { 119 throw new IOException("Failed to read line " + line_no); 120 } 121 line = "Line " + line_no + "\n"; 122 pos = 0; 123 } else { 124 return -1; // EOS reached 125 } 126 } 127 128 int cnt = line.length() - pos; 129 assert(cnt != 0); 130 // try to fill with remaining 131 if (cnt >= len) { 132 line.getChars(pos, pos + len, buf, off); 133 pos += len; 134 if (cnt == len) { 135 assert(pos == line.length()); 136 line = null; 137 } 138 return len; 139 } else { 140 line.getChars(pos, pos + cnt, buf, off); 141 off += cnt; 142 len -= cnt; 143 line = null; 144 /* hold for next read, so we won't IOE during fill buffer 145 int more = read(buf, off, len); 146 return (more == -1) ? cnt : cnt + more; 147 */ 148 return cnt; 149 } 150 } 151 } 152 } 153 154 private static void verify(Map.Entry<String, Integer> e) { 155 final String data = e.getKey(); 156 final int total_lines = e.getValue(); 157 try (BufferedReader br = new BufferedReader( 158 new StringReader(data))) { 159 assertEquals(br.lines() 160 .mapToInt(l -> 1).reduce(0, (x, y) -> x + y), 161 total_lines, 162 data + " should produce " + total_lines + " lines."); 163 } catch (IOException ioe) { 164 fail("Should not have any exception."); 165 } 166 } 167 168 public void testLinesBasic() { 169 // Basic test cases 170 cases.entrySet().stream().forEach(Lines::verify); 171 // Similar test, also verify MockLineReader is correct 172 for (int i = 0; i < 10; i++) { 173 try (BufferedReader br = new BufferedReader(new MockLineReader(i))) { 174 assertEquals(br.lines() 175 .peek(l -> assertTrue(l.matches("^Line \\d+$"))) 176 .mapToInt(l -> 1).reduce(0, (x, y) -> x + y), 177 i, 178 "MockLineReader(" + i + ") should produce " + i + " lines."); 179 } catch (IOException ioe) { 180 fail("Unexpected IOException."); 181 } 182 } 183 } 184 185 public void testUncheckedIOException() throws IOException { 186 MockLineReader r = new MockLineReader(10, 3); 187 ArrayList<String> ar = new ArrayList<>(); 188 try (BufferedReader br = new BufferedReader(r)) { 189 br.lines().limit(3L).forEach(ar::add); 190 assertEquals(ar.size(), 3, "Should be able to read 3 lines."); 191 } catch (UncheckedIOException uioe) { 192 fail("Unexpected UncheckedIOException"); 193 } 194 r.reset(); 195 try (BufferedReader br = new BufferedReader(r)) { 196 br.lines().forEach(ar::add); 197 fail("Should had thrown UncheckedIOException."); 198 } catch (UncheckedIOException uioe) { 199 assertEquals(r.getLineNumber(), 4, "should fail to read 4th line"); 200 assertEquals(ar.size(), 6, "3 + 3 lines read"); 201 } 202 for (int i = 0; i < ar.size(); i++) { 203 assertEquals(ar.get(i), "Line " + (i % 3 + 1)); 204 } 205 } 206 207 public void testIterator() throws IOException { 208 MockLineReader r = new MockLineReader(6); 209 BufferedReader br = new BufferedReader(r); 210 String line = br.readLine(); 211 assertEquals(r.getLineNumber(), 1, "Read one line"); 212 Stream<String> s = br.lines(); 213 Iterator<String> it = s.iterator(); 214 // Ensure iterate with only next works 215 for (int i = 0; i < 5; i++) { 216 String str = it.next(); 217 assertEquals(str, "Line " + (i + 2), "Addtional five lines"); 218 } 219 // NoSuchElementException 220 try { 221 it.next(); 222 fail("Should have run out of lines."); 223 } catch (NoSuchElementException nsse) {} 224 } 225 226 public void testPartialReadAndLineNo() throws IOException { 227 MockLineReader r = new MockLineReader(5); 228 LineNumberReader lr = new LineNumberReader(r); 229 char[] buf = new char[5]; 230 lr.read(buf, 0, 5); 231 assertEquals(0, lr.getLineNumber(), "LineNumberReader start with line 0"); 232 assertEquals(1, r.getLineNumber(), "MockLineReader start with line 1"); 233 assertEquals(new String(buf), "Line "); 234 String l1 = lr.readLine(); 235 assertEquals(l1, "1", "Remaining of the first line"); 236 assertEquals(1, lr.getLineNumber(), "Line 1 is read"); 237 assertEquals(1, r.getLineNumber(), "MockLineReader not yet go next line"); 238 lr.read(buf, 0, 4); 239 assertEquals(1, lr.getLineNumber(), "In the middle of line 2"); 240 assertEquals(new String(buf, 0, 4), "Line"); 241 ArrayList<String> ar = lr.lines() 242 .peek(l -> assertEquals(lr.getLineNumber(), r.getLineNumber())) 243 .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); 244 assertEquals(ar.get(0), " 2", "Remaining in the second line"); 245 for (int i = 1; i < ar.size(); i++) { 246 assertEquals(ar.get(i), "Line " + (i + 2), "Rest are full lines"); 247 } 248 } 249 250 public void testInterlacedRead() throws IOException { 251 MockLineReader r = new MockLineReader(10); 252 BufferedReader br = new BufferedReader(r); 253 char[] buf = new char[5]; 254 Stream<String> s = br.lines(); 255 Iterator<String> it = s.iterator(); 256 257 br.read(buf); 258 assertEquals(new String(buf), "Line "); 259 assertEquals(it.next(), "1"); 260 try { 261 s.iterator().next(); 262 fail("Should failed on second attempt to get iterator from s"); 263 } catch (IllegalStateException ise) {} 264 br.read(buf, 0, 2); 265 assertEquals(new String(buf, 0, 2), "Li"); 266 // Get stream again should continue from where left 267 // Only read remaining of the line 268 br.lines().limit(1L).forEach(line -> assertEquals(line, "ne 2")); 269 br.read(buf, 0, 2); 270 assertEquals(new String(buf, 0, 2), "Li"); 271 br.read(buf, 0, 2); 272 assertEquals(new String(buf, 0, 2), "ne"); 273 assertEquals(it.next(), " 3"); 274 // Line 4 275 br.readLine(); 276 // interator pick 277 assertEquals(it.next(), "Line 5"); 278 // Another stream instantiated by lines() 279 AtomicInteger line_no = new AtomicInteger(6); 280 br.lines().forEach(l -> assertEquals(l, "Line " + line_no.getAndIncrement())); 281 // Read after EOL 282 assertFalse(it.hasNext()); 283 } 284 }