4

We know that nextInt(), nextDouble(), nextLine(), etc methods of Java Scanner class parses something (int, double, line, etc) and advances the position of the scanner. But I need a way only for parsing something but not for advancing the position. That means, I need some way for something like peekInt(), peekDouble(), peekLine(), etc methods.

Here is an example for why it may be necessary. Suppose, I have an abstract class Server which has an abstract method respond(Scanner in, PrintWriter out, String clientIp) to be implemented by other classes. Here is the portion of code:

public abstract class Server {
    // ... (some initialization variables)

    public static final String endSocketMarker = "END";

    public final void runServer() {
        // ... (multithreading code)

        clientSocket = serverSocket.accept();
        Scanner in = new Scanner(new BufferedReader(new InputStreamReader(clientSocket.getInputStream())));
        PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);

        String clientIp = in.nextLine();
        String marker = "";
        do {
            respond(in, out, clientIp); // call abstract method
            marker = in.nextLine(); //TODO: find a way so that input line is peeked (but not skipped)
        } while(!marker.equals(endSocketMarker));

        // ... (close client socket)
    }

    protected abstract void respond(Scanner in, PrintWriter out, String clientIp);

    // ... (other methods)
}

Here marker = in.nextLine(); parses the line until a line separator found and then advances the position to the beginning of the next line. If marker.equals(endSocketMarker) is false, then the string assigned in marker cannot be read inside respond(in, out, clientIp) method at all. It must be avoided somehow. I may pass the variable marker into respond(in, out, clientIp), but it will make the code cluttered.

Is there any better way for achieving my goal?

arnobpl
  • 1,126
  • 4
  • 16
  • 32

2 Answers2

0

The java.util.Scanner.hasNext() method Returns true if this scanner has another token in its input. This method may block while waiting for input to scan. The scanner does not advance past any input.

There's an example here, hasNext() example

cmb28
  • 421
  • 9
  • 24
  • 1
    `hasNext()` method returns true if the scanner has another token in its input but it does not return the token. But I need that token *without advancing the position of the scanner*. I think, I could not explain my question to you. – arnobpl Jun 14 '17 at 10:43
  • The scanner doesn't advance past that token, it stays there. – cmb28 Jun 14 '17 at 10:48
  • All the methods containing `next` (for example `nextInt()`, `nextDouble()`, `nextLine()`) advance the position of the scanner, otherwise always same string would be returned for consecitive method calls. [Here](https://docs.oracle.com/javase/7/docs/api/java/util/Scanner.html#nextLine()) is a line 'The position is set to the beginning of the next line.' It is similar for the other methods. – arnobpl Jun 14 '17 at 11:00
0

You can do this by checking for the next token via a Pattern with the corresponding hasNext. This way you are not advancing the position, but you are able to peek the next token (via its pattern)...

import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.regex.Pattern;

public class Main {
    
    public static void main(final String[] args) {
        final Charset charset = StandardCharsets.UTF_8;
        final String endSocketMarker = "END";
        final String testString = "This is some test text.\n1234567890\n" + endSocketMarker + "\nThis is still writing...";
        final Scanner scan = new Scanner(new ByteArrayInputStream(testString.getBytes(charset)), charset.name());
        final Pattern endPattern = Pattern.compile(Pattern.quote(endSocketMarker));
        while (!scan.hasNext(endPattern))
            System.out.println("Processing input, with first line being: \"" + scan.nextLine() + "\".");
        System.out.println("Reached the \"" + scan.nextLine() + "\".");
    }
}

The point of converting the testString to an InputStream which is supplied to the Scanner exists just to demonstrate how to use an InputStream with the Scanner. Otherwise I could just simply supply the testString directly to the Scanner's constructor.


I was thinking also about supplying the Scanner with a BufferedInputStream and using mark/reset on the latter, but after some tests and a bit of searching, I found out that Scanner internally uses a buffer. Which means that the Scanner's buffer is getting filled with all the data from the BufferedInputStream, thus advancing the position of the BufferedInputStream without letting us mark the proper location. Even if you create a new Scanner object every time, using the same BufferedInputStream will have the same effect, as the BufferedInputStream will be advanced anyway.

gthanop
  • 3,035
  • 2
  • 10
  • 27