3

I'm trying to implement this automaton example : http://www.javacodegeeks.com/2012/03/automaton-implementation-in-java.html.

However, an error keeps on being displayed while running the program :

Exception in thread "main" java.lang.StringIndexOutOfBoundsException: 
           String index out of range: 3
at java.lang.String.charAt(String.java:686)
at mealy.Input.read(Input.java:7)
at mealy.States$4.next(Input.java:46)
at mealy.Test.main(Test.java:9)

I tried modifying the lines responsible for the error but nothing changed. Could someone please take a look at this program and help me find a solution?

I have the following .java :

State.java :

interface State {
    public State next(Input in);
}

NB : I had to change the original "public Stat next()" into "public State next(Input in);"

Input.java :

class Input {
    private String input;
    private int current;
    public Input(String input) {this.input = input;}
    char read() { return input.charAt(current++); }
}

enum States implements State {
    Init {
        @Override
        public State next(Input word) {
            switch(word.read()) {
                case 'a': return A;
                default: return Fail;
            }
        }
    },
    A {
        @Override
        public State next(Input word) {
            switch(word.read()) {
                case 'a': return A;
                case 'b': return B;
                case 'c': return C;
                case 'd': return null;
                default: return Fail;
            }
        }
    },
    B {
        @Override
        public State next(Input word) {
            switch(word.read()) {
                case 'b': return B;
                case 'c': return C;
                case 'd': return null;
                default: return Fail;
            }
        }
    },
    C {
        @Override
        public State next(Input word) {
            switch(word.read()) {
                case 'c': return C;
                case 'd': return null;
                default: return Fail;
            }
        }
    },
    Fail {
        @Override
        public State next(Input word) {
               return Fail;
        }
    };

    public abstract State next(Input word);
}

Test.java :

public class Test {
    public static void main(String args[]){
        State s;
        Input in = new Input("abc");

        for(s = States.Init; s != null || s != States.Fail; s = s.next(in)) {}
        if(s == States.Init) {System.out.println("Valid!");}
        else {System.out.println("Failed");}
    }
}
Boris Pavlović
  • 63,078
  • 28
  • 122
  • 148

3 Answers3

1

There appears to be a bug in the Input class. When you attempt to read the character after the last one, it throws an exception you are not handling in main. I would change Input so that it return a token you can handle in your state machine.

BTW I suggest you have a look at this for context. http://vanillajava.blogspot.co.uk/2011/06/java-secret-using-enum-as-state-machine.html

I assume Attila was looking to provide a simple, working example. I will see if he can fix his code.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
1

the error is in this line

char read() { return input.charAt(current++); }

You don't check the length of the String input (which i consider a bad name inside a class Input) and after calling read() three times you try to access the 4th letter of a 3 letter String, which then throws the exception you see.

Update:

Addressing your comment I'd suggest changing the return value of read() to a new interface ReadResult:

public interface ReadResult {
    boolean isOkay();
    char getReadCharacter();  
}

with two implementations. one for positive results...

public class ReadOkay implements ReadResult{
    private char readCharacter;

    public ReadOkay(char readCharacter) {
        this.readCharacter = readCharacter;
    }

    @Override
    public boolean isOkay() {
        return true;
    }

    @Override
    public char getReadCharacter() {
        return readCharacter;
    }
}

and one for negative results

public class ReadFailed implements ReadResult {

    @Override
    public boolean isOkay() {
        return false;
    }

    @Override
    public char getReadCharacter() {
        throw new IllegalStateException("Read failed! no character data there to return!");
    }
}

Having this you can change read() to let it return then new interface

public ReadResult read() {
        if (input != null && current >= 0 && current < input.length()) {
            return new ReadOkay(input.charAt(current++));
        } else {
            return new ReadFailed();
        }
    }

and update your States accordingly.

replace:

switch(word.read()) {

with:

ReadResult result = word.read();
if (!result.isOkay()) {
    return Fail;
}
switch (result.getReadCharacter()) {
Marco Forberg
  • 2,634
  • 5
  • 22
  • 33
  • Thank you, you're right. But since I cannot add any condition on "current" size, I'm unable to fix this problem. Would you have any idea ? – Fabrice Sopoglian Nov 05 '13 at 01:14
  • Marco Forberg, thanks a lot for your answer. I wasn't able to test it so far, that's why I answer this late. Thanks again for your help ! – Fabrice Sopoglian Nov 18 '13 at 02:12
1

you need to change read() method like below

char read() { 
    if(current>=input.length())      // this if condition should be checked
          return 'z';                // you need to change your character according to your need
    return input.charAt(current++); 
}
Prabhakaran Ramaswamy
  • 25,706
  • 10
  • 57
  • 64
  • umm... you'd change the logic so that it would return indefinitely char 'z' for all overflows? are you sure that would be the best way? – eis Nov 04 '13 at 08:56
  • @eis i agree you OP's will return his unused character from the switch statement. – Prabhakaran Ramaswamy Nov 04 '13 at 08:59
  • Thank you for your answers, but indeed ByteCode your solution seems to only move the problem on another point. I spent my day trying to find a way to control the reading of "current", I was just able to break the loop on Test.java once the state "C" is reached. It prevent the read() method from going further in the reading of the word, but I'm looking to a better way to control it. Would you have any idea ? – Fabrice Sopoglian Nov 05 '13 at 02:39