Change build scripts
[jvcard.git] / src / com / googlecode / lanterna / input / InputDecoder.java
1 /*
2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
3 *
4 * lanterna is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Copyright (C) 2010-2015 Martin
18 */
19 package com.googlecode.lanterna.input;
20
21 import com.googlecode.lanterna.input.CharacterPattern.Matching;
22
23 import java.io.BufferedReader;
24 import java.io.IOException;
25 import java.io.Reader;
26 import java.util.*;
27
28 /**
29 * Used to read the input stream character by character and generate {@code Key} objects to be put in the input queue.
30 *
31 * @author Martin, Andreas
32 */
33 public class InputDecoder {
34 private final Reader source;
35 private final List<CharacterPattern> bytePatterns;
36 private final List<Character> currentMatching;
37 private boolean seenEOF;
38 private int timeoutUnits;
39
40 /**
41 * Creates a new input decoder using a specified Reader as the source to read characters from
42 * @param source Reader to read characters from, will be wrapped by a BufferedReader
43 */
44 public InputDecoder(final Reader source) {
45 this.source = new BufferedReader(source);
46 this.bytePatterns = new ArrayList<CharacterPattern>();
47 this.currentMatching = new ArrayList<Character>();
48 this.seenEOF = false;
49 this.timeoutUnits = 0; // default is no wait at all
50 }
51
52 /**
53 * Adds another key decoding profile to this InputDecoder, which means all patterns from the profile will be used
54 * when decoding input.
55 * @param profile Profile to add
56 */
57 public void addProfile(KeyDecodingProfile profile) {
58 for (CharacterPattern pattern : profile.getPatterns()) {
59 synchronized(bytePatterns) {
60 //If an equivalent pattern already exists, remove it first
61 bytePatterns.remove(pattern);
62 bytePatterns.add(pattern);
63 }
64 }
65 }
66
67 /**
68 * Returns a collection of all patterns registered in this InputDecoder.
69 * @return Collection of patterns in the InputDecoder
70 */
71 public synchronized Collection<CharacterPattern> getPatterns() {
72 synchronized(bytePatterns) {
73 return new ArrayList<CharacterPattern>(bytePatterns);
74 }
75 }
76
77 /**
78 * Removes one pattern from the list of patterns in this InputDecoder
79 * @param pattern Pattern to remove
80 * @return {@code true} if the supplied pattern was found and was removed, otherwise {@code false}
81 */
82 public boolean removePattern(CharacterPattern pattern) {
83 synchronized(bytePatterns) {
84 return bytePatterns.remove(pattern);
85 }
86 }
87
88 /**
89 * Sets the number of 1/4-second units for how long to try to get further input
90 * to complete an escape-sequence for a special Key.
91 *
92 * Negative numbers are mapped to 0 (no wait at all), and unreasonably high
93 * values are mapped to a maximum of 240 (1 minute).
94 */
95 public void setTimeoutUnits(int units) {
96 timeoutUnits = (units < 0) ? 0 :
97 (units > 240) ? 240 :
98 units;
99 }
100 /**
101 * queries the current timeoutUnits value. One unit is 1/4 second.
102 * @return The timeout this InputDecoder will use when waiting for additional input, in units of 1/4 seconds
103 */
104 public int getTimeoutUnits() {
105 return timeoutUnits;
106 }
107
108 /**
109 * Reads and decodes the next key stroke from the input stream
110 * @return Key stroke read from the input stream, or {@code null} if none
111 * @throws IOException If there was an I/O error when reading from the input stream
112 */
113 public synchronized KeyStroke getNextCharacter(boolean blockingIO) throws IOException {
114
115 KeyStroke bestMatch = null;
116 int bestLen = 0;
117 int curLen = 0;
118
119 while(true) {
120
121 if ( curLen < currentMatching.size() ) {
122 // (re-)consume characters previously read:
123 curLen++;
124 }
125 else {
126 // If we already have a bestMatch but a chance for a longer match
127 // then we poll for the configured number of timeout units:
128 // It would be much better, if we could just read with a timeout,
129 // but lacking that, we wait 1/4s units and check for readiness.
130 if (bestMatch != null) {
131 int timeout = getTimeoutUnits();
132 while (timeout > 0 && ! source.ready() ) {
133 try {
134 timeout--; Thread.sleep(250);
135 } catch (InterruptedException e) { timeout = 0; }
136 }
137 }
138 // if input is available, we can just read a char without waiting,
139 // otherwise, for readInput() with no bestMatch found yet,
140 // we have to wait blocking for more input:
141 if ( source.ready() || ( blockingIO && bestMatch == null ) ) {
142 int readChar = source.read();
143 if (readChar == -1) {
144 seenEOF = true;
145 if(currentMatching.isEmpty()) {
146 return new KeyStroke(KeyType.EOF);
147 }
148 break;
149 }
150 currentMatching.add( (char)readChar );
151 curLen++;
152 } else { // no more available input at this time.
153 // already found something:
154 if (bestMatch != null) {
155 break; // it's something...
156 }
157 // otherwise: no KeyStroke yet
158 return null;
159 }
160 }
161
162 List<Character> curSub = currentMatching.subList(0, curLen);
163 Matching matching = getBestMatch( curSub );
164
165 // fullMatch found...
166 if (matching.fullMatch != null) {
167 bestMatch = matching.fullMatch;
168 bestLen = curLen;
169
170 if (! matching.partialMatch) {
171 // that match and no more
172 break;
173 } else {
174 // that match, but maybe more
175 continue;
176 }
177 }
178 // No match found yet, but there's still potential...
179 else if ( matching.partialMatch ) {
180 continue;
181 }
182 // no longer match possible at this point:
183 else {
184 if (bestMatch != null ) {
185 // there was already a previous full-match, use it:
186 break;
187 } else { // invalid input!
188 // remove the whole fail and re-try finding a KeyStroke...
189 curSub.clear(); // or just 1 char? currentMatching.remove(0);
190 curLen = 0;
191 continue;
192 }
193 }
194 }
195
196 //Did we find anything? Otherwise return null
197 if(bestMatch == null) {
198 if(seenEOF) {
199 currentMatching.clear();
200 return new KeyStroke(KeyType.EOF);
201 }
202 return null;
203 }
204
205 List<Character> bestSub = currentMatching.subList(0, bestLen );
206 bestSub.clear(); // remove matched characters from input
207 return bestMatch;
208 }
209
210 private Matching getBestMatch(List<Character> characterSequence) {
211 boolean partialMatch = false;
212 KeyStroke bestMatch = null;
213 synchronized(bytePatterns) {
214 for(CharacterPattern pattern : bytePatterns) {
215 Matching res = pattern.match(characterSequence);
216 if (res != null) {
217 if (res.partialMatch) { partialMatch = true; }
218 if (res.fullMatch != null) { bestMatch = res.fullMatch; }
219 }
220 }
221 }
222 return new Matching(partialMatch, bestMatch);
223 }
224 }