001package components.simplereader;
002
003import java.io.BufferedReader;
004import java.io.IOException;
005import java.io.InputStreamReader;
006import java.net.URI;
007import java.net.URISyntaxException;
008import java.net.URL;
009import java.nio.file.Files;
010import java.nio.file.Path;
011import java.nio.file.Paths;
012
013/**
014 * {@code SimpleReader} represented as {@link java.io.BufferedReader
015 * java.io.BufferedReader} with implementations of primary methods.
016 *
017 * @correspondence <pre>
018 * this.is_open = [$this.rep is open] and
019 *  this.ext_name = $this.name and
020 *  if $this.lookAheadIsValid then
021 *    this.contents = [$this.lookAhead * the contents of $this.rep]
022 *  else
023 *    this.contents = [the contents of $this.rep]
024 * </pre>
025 * @convention {@code
026 * [$this.rep is not null when the stream is open and
027 *  lookAheadIsValid is true iff the contents of $this.rep is not <>]
028 * }
029 */
030public class SimpleReader1L extends SimpleReaderSecondary {
031
032    /*
033     * Private members --------------------------------------------------------
034     */
035
036    /**
037     * The input stream.
038     */
039    private BufferedReader rep;
040    /**
041     * The stream name.
042     */
043    private String name;
044    /**
045     * The look ahead character buffer.
046     */
047    private char lookAhead;
048    /**
049     * Flag to keep track of whether the lookAhead contains the first character
050     * in this.content.
051     */
052    private boolean lookAheadIsValid;
053
054    /**
055     * Creator of initial representation.
056     */
057    private void createNewRep() {
058        this.rep = new BufferedReader(new InputStreamReader(System.in));
059        this.name = "";
060    }
061
062    /**
063     * Inputs one character from $this.rep.
064     *
065     * @requires $this.lookAheadIsValid = false
066     * @ensures <pre>
067     * [$this.lookAhead contains the first character from #$this.rep and
068     * $thislookAheadIsValid is true unless the end of the stream has
069     * been reached. It is non-static because it needs to be able to
070     * modify the representation.]
071     * </pre>
072     */
073    private void getOneChar() {
074        try {
075            int nextCh = this.rep.read();
076            if (nextCh != -1) {
077                this.lookAhead = (char) nextCh;
078                this.lookAheadIsValid = true;
079            }
080        } catch (IOException e) {
081            throw new AssertionError("Violation of: can read from reader");
082        }
083
084    }
085
086    /*
087     * Constructors -----------------------------------------------------------
088     */
089
090    /**
091     * No-argument constructor (for input from stdin).
092     */
093    public SimpleReader1L() {
094        this.createNewRep();
095    }
096
097    /**
098     * Constructor for input from given file.
099     *
100     * @param source
101     *            the name of the file or of a URL to input from
102     */
103    public SimpleReader1L(String source) {
104        assert source != null : "Violation of: name is not null";
105        this.name = source;
106
107        URI uri = null;
108        boolean isUri = false;
109
110        // check if source is a valid URI
111        try {
112            uri = new URI(source);
113            isUri = (uri.getScheme() != null);
114        } catch (URISyntaxException e) {
115            // not a valid URI — likely a local file path
116        }
117
118        if (isUri) {
119            try {
120                URL url = uri.toURL();
121                this.rep = new BufferedReader(new InputStreamReader(url.openStream()));
122            } catch (IOException e) {
123                throw new AssertionError(
124                        "Violation of: " + source + " is a valid URL and is accessible");
125            }
126        } else {
127            // treat as local file path
128            Path path = Paths.get(source);
129            if (!Files.exists(path)) {
130                throw new AssertionError("Violation of: " + source + " exists");
131            }
132            if (!Files.isReadable(path)) {
133                throw new AssertionError("Violation of: " + source + " is readable");
134            }
135            try {
136                this.rep = Files.newBufferedReader(path);
137            } catch (IOException e) {
138                throw new AssertionError("Violation of: " + source + " can be opened");
139            }
140        }
141    }
142
143    /*
144     * Standard methods -------------------------------------------------------
145     */
146
147    @Override
148    public final SimpleReader newInstance() {
149        try {
150            return this.getClass().getConstructor().newInstance();
151        } catch (ReflectiveOperationException e) {
152            throw new AssertionError(
153                    "Cannot construct object of type " + this.getClass());
154        }
155    }
156
157    @Override
158    public final void clear() {
159        this.createNewRep();
160    }
161
162    @Override
163    public final void transferFrom(SimpleReader source) {
164        assert source != null : "Violation of: source is not null";
165        assert source != this : "Violation of: source is not this";
166        assert source instanceof SimpleReader1L
167                : "Violation of: source is of dynamic type SimpleReader1L";
168        /*
169         * This cast cannot fail since the assert above would have stopped
170         * execution in that case.
171         */
172        SimpleReader1L localSource = (SimpleReader1L) source;
173        this.rep = localSource.rep;
174        this.name = localSource.name;
175        this.lookAhead = localSource.lookAhead;
176        this.lookAheadIsValid = localSource.lookAheadIsValid;
177        localSource.createNewRep();
178    }
179
180    /*
181     * Kernel methods ---------------------------------------------------------
182     */
183
184    @Override
185    public final char read() {
186        assert this.rep != null : "Violation of: this is open";
187        if (!this.lookAheadIsValid) {
188            this.getOneChar();
189        }
190        assert this.lookAheadIsValid : "Violation of: this is not at end-of-stream";
191        this.lookAheadIsValid = false;
192        return this.lookAhead;
193    }
194
195    @Override
196    public final char peek() {
197        assert this.rep != null : "Violation of: this is open";
198        if (!this.lookAheadIsValid) {
199            this.getOneChar();
200        }
201        assert this.lookAheadIsValid : "Violation of: this is not at end-of-stream";
202        return this.lookAhead;
203    }
204
205    @Override
206    public final String name() {
207        return this.name;
208    }
209
210    @Override
211    public final boolean isOpen() {
212        return this.rep != null;
213    }
214
215    @Override
216    public final boolean atEOS() {
217        assert this.rep != null : "Violation of: this is open";
218        if (!this.lookAheadIsValid) {
219            this.getOneChar();
220        }
221        return !this.lookAheadIsValid;
222    }
223
224    @Override
225    public final void close() {
226        assert this.rep != null : "Violation of: this is open";
227        try {
228            this.rep.close();
229            this.rep = null;
230        } catch (IOException e) {
231            throw new AssertionError("Violation of: this can be closed");
232        }
233    }
234
235}