001package fi.jyu.mit.ohj2;
002
003import java.util.Enumeration;
004
005/**
006 * Luokka StringTokenizerin korvaajaksi.  Erona
007 * on se että jonon loppuessa ei tule ongelmia ja 
008 * peräkkäisten erottimien välistä tulee tyhjä jono.
009 * <pre>
010 * Esimerkki:
011 * public static void main(String[] args) {
012 *     Erottelija erottaja = new Erottelija("12;3.5:kissa,,,istuu puussa,3.4",
013 *                                          ";:,");
014 *     System.out.println("Palasia: " + erottaja.countTokens());
015 *     for (int i=1; erottaja.hasMoreTokens(); i++ )
016 *       System.out.println(i + ": |" + erottaja.nextToken()+"|");
017 *     System.err.println("8: |"+erottaja.nextToken()+"|");
018 *     erottaja.reset();
019 *     System.out.println(erottaja.nextToken(0));
020 *     System.out.println(erottaja.countRemaininTokens());
021 *     System.out.println(erottaja.rest());
022 *     System.out.println(erottaja.nextToken(0.0));
023 *     System.out.println(erottaja.nextToken(2));
024 *     System.out.println(erottaja.nextToken(2.1));
025 *     System.out.println(erottaja.countRemainingTokens());
026 *     System.out.println(erottaja.rest());
027 *   }
028 *
029 * Tulostaa:
030 * 
031 * Palasia: 7
032 * 1: |12|
033 * 2: |3.5|
034 * 3: |kissa|
035 * 4: ||
036 * 5: ||
037 * 6: |istuu puussa|
038 * 7: |3.4|
039 * 8: ||
040 * 12
041 * 6
042 * 3.5:kissa,,,istuu puussa,3.4
043 * 3
044 * ,istuu puussa,3.4
045 * 12
046 * 3.5
047 * 2
048 * 2.1
049 * </pre>
050 * @author vesal
051 * @version 11.3.2007
052 * 
053 * @example
054 * <pre name="testErottelija">
055 * Erottelija erottaja = new Erottelija("12;3.5:kissa,,,istuu puussa,3.4",";:,");
056 * erottaja.nextToken() === $s; erottaja.hasMoreTokens() === $tokens;
057 * erottaja.countRemainingTokens() === $n; erottaja.rest() === $rest;   
058 * 
059 *   $i  |  $s             | $tokens | $n | $rest
060 *  --------------------------------------------------------------------------  
061 *   0   | ---             | true    | 7  | "12;3.5:kissa,,,istuu puussa,3.4"
062 *   1   | "12"            | true    | 6  | "3.5:kissa,,,istuu puussa,3.4"
063 *   2   | "3.5"           | true    | 5  | "kissa,,,istuu puussa,3.4"
064 *   3   | "kissa"         | true    | 4  | ",,istuu puussa,3.4"
065 *   4   | ""              | true    | 3  | ",istuu puussa,3.4"
066 *   5   | ""              | true    | 2  | "istuu puussa,3.4"
067 *   6   | "istuu puussa"  | true    | 1  | "3.4"
068 *   7   | "3.4"           | false   | 0  | ""
069 *   8   | ""              | false   | 0  | ""
070 *   9   | ""              | false   | 0  | ""
071 *   
072 *   erottaja.nextToken(";","kissa") === "kissa";
073 * 
074 * @example
075 * </pre>
076 * <pre name="testErottelijaEnd">
077 * Erottelija erottaja = new Erottelija("12;3.5:kissa,,,istuu puussa,3.4;;",";:,");
078 * erottaja.nextToken() === $s; erottaja.hasMoreTokens() === $tokens;
079 * erottaja.countRemainingTokens() === $n; erottaja.rest() === $rest;   
080 * 
081 *   $i  |  $s             | $tokens | $n | $rest
082 *  --------------------------------------------------------------------------  
083 *   0   | ---             | true    | 9  | "12;3.5:kissa,,,istuu puussa,3.4;;"
084 *   1   | "12"            | true    | 8  | "3.5:kissa,,,istuu puussa,3.4;;"
085 *   2   | "3.5"           | true    | 7  | "kissa,,,istuu puussa,3.4;;"
086 *   3   | "kissa"         | true    | 6  | ",,istuu puussa,3.4;;"
087 *   4   | ""              | true    | 5  | ",istuu puussa,3.4;;"
088 *   5   | ""              | true    | 4  | "istuu puussa,3.4;;"
089 *   6   | "istuu puussa"  | true    | 3  | "3.4;;"
090 *   7   | "3.4"           | true    | 2  | ";"
091 *   8   | ""              | true    | 1  | ""
092 *   9   | ""              | false   | 0  | ""
093 *  10   | ""              | false   | 0  | ""
094 *   
095 *   erottaja.nextToken(";","kissa") === "kissa";
096 * </pre>
097 */
098public class Erottelija implements Enumeration<String> { // NOPMD - enum ok
099
100    /**
101     * Etsitään mistä kohti jonosta str löytyy ensimmäinen erotinmerkki 
102     * joukosta delim.  Etsintä aloitetaan paikasta pos.
103     * @param str    mistä jonosta etsitään
104     * @param delim  joukko erotinmerkkejä
105     * @param pos    paikka josta aloitetaan 
106     * @return       palauttaa ensimmäisen esiintymän tai -1 jos ei löydy
107     * @example
108     * <pre name="test">
109     *   indexOfAny("a;, b",",; ",0) === 1
110     *   indexOfAny("a;, b",",; ",2) === 2
111     *   indexOfAny("a;, b"," ",0)   === 3
112     *   indexOfAny("a;, b",".",0)   === -1
113     *   indexOfAny(null,",; ",0)    === -1
114     *   indexOfAny("a b",",; ",-1)  === 1
115     * </pre>
116     */
117    public static int indexOfAny(String str, String delim, int pos) {
118        int i = pos;
119        if (i < 0)
120            i = 0;
121        if (str == null || delim == null)
122            return -1;
123        for (; i < str.length(); i++) {
124            char c = str.charAt(i);
125            if (delim.indexOf(c) >= 0)
126                return i;
127        }
128        return -1;
129    }
130
131
132    /**
133     * Etsitään mistä kohti jonosta str löytyy ensimmäinen erotinmerkki 
134     * joukosta delim.  Etsintä aloitetaan alusta.
135     * @param str    mistä jonosta etsitään
136     * @param delim  joukko erotinmerkkejä
137     * @return       palauttaa ensimmäisen esiintymän tai -1 jos ei löydy
138     * @example
139     * <pre name="test">
140     *   indexOfAny("a;, b",",; ") === 1
141     * </pre>
142     */
143    public static int indexOfAny(String str, String delim) {
144        return indexOfAny(str, delim, 0);
145    }
146
147    private String originalString;
148    private final String delimiters;
149    private int startPos = 0;
150
151
152    /**
153     * Luodaan erottelija, joka erottelee jonosta str palasia minkä tahansa
154     * joukosta delim löytyvän merkin kohdalta. 
155     * @param str    jono josta erotellaan
156     * @param delim  erottavien merkkien joukko
157     * @example
158     * <pre name="test">
159     *                                     //01234 
160     * Erottelija erottaja = new Erottelija("a b "," ");
161     * erottaja.countTokens() === 3;
162     * erottaja = new Erottelija("a b ",",");
163     * erottaja.countTokens() === 1;
164     * </pre>
165     */
166    public Erottelija(String str, String delim) {
167        this.originalString = str;
168        this.delimiters = delim;
169    }
170
171
172    /**
173     * Luodaan erottelija, joka erottelee jonosta str palasia välilyönnin 
174     * kohdalta.
175     * @param str    jono josta erotellaan
176     * @example
177     * <pre name="test">
178     *                                     //01234 
179     * Erottelija erottaja = new Erottelija("a b ");
180     * erottaja.countTokens() === 3;
181     * </pre>
182     */
183    public Erottelija(String str) {
184        this.originalString = str;
185        this.delimiters = " ";
186    }
187
188
189    /**
190     * Palauttaa seuraavan palasen jonosta
191     * @return jonon seuraava palanen
192     * @example
193     * <pre name="test">
194     * Erottelija erottaja = new Erottelija("a;b;",";");
195     * erottaja.nextToken() === $s; erottaja.hasMoreTokens() === $tokens;
196     * erottaja.countRemainingTokens() === $n; erottaja.rest() === $rest;   
197     * 
198     *   $i  |  $s             | $tokens | $n | $rest
199     *  --------------------------------------------------------------------------  
200     *   0   | ---             | true    | 3  | "a;b;"
201     *   1   | "a"             | true    | 2  | "b;"
202     *   2   | "b"             | true    | 1  | ""
203     *   3   | ""              | false   | 0  | ""
204     *   4   | ""              | false   | 0  | ""
205     * </pre>
206     */
207    public String nextToken() {
208        return nextToken(delimiters);
209    }
210
211
212    /**
213     * Tarkistaa onko erotinmerkkiä juuri paikan vasemmalla puolella
214     * @param pos paikka josta tutkitaan
215     * @param delim kelpaavat erotinmerkit
216     * @return true jos on erotin merkki vasemmalla puolella, false muuten
217     * @example
218     * <pre name="test">
219     *                                     //01234 
220     * Erottelija erottaja = new Erottelija("a;b;",";");
221     * erottaja.isDelimBefore(0,";") === false;
222     * erottaja.isDelimBefore(1,";") === false;
223     * erottaja.isDelimBefore(2,";") === true;
224     * erottaja.isDelimBefore(3,";") === false;
225     * erottaja.isDelimBefore(4,";") === true;
226     * erottaja.isDelimBefore(5,";") === false;
227     * </pre>
228     */
229    public boolean isDelimBefore(int pos, String delim) {
230        if (pos <= 0)
231            return false;
232        if (pos > originalString.length())
233            return false;
234        String usedDelim = delim;
235        if (delim == null)
236            usedDelim = this.delimiters;
237        char c = originalString.charAt(pos - 1);
238        return (usedDelim.indexOf(c) >= 0);
239    }
240
241
242    /**
243     * Tarkistaa onko erotinmerkkiä juuri paikan vasemmalla puolella
244     * @param pos paikka josta tutkitaan
245     * @return true jos on erotin merkki vasemmalla puolella, false muuten
246     * @example
247     * <pre name="test">
248     *                                     //01234 
249     * Erottelija erottaja = new Erottelija("a;b;",";");
250     * erottaja.isDelimBefore(0) === false;
251     * erottaja.isDelimBefore(1) === false;
252     * erottaja.isDelimBefore(2) === true;
253     * erottaja.isDelimBefore(3) === false;
254     * erottaja.isDelimBefore(4) === true;
255     * erottaja.isDelimBefore(5) === false;
256     * </pre>
257     */
258    public boolean isDelimBefore(int pos) {
259        return isDelimBefore(pos, null);
260    }
261
262
263    /**
264     * Tarkistaa onko erotinmerkkiä juuri nykypaikan vasemmalla puolella
265     * @return true jos on erotin merkki vasemmalla puolella, false muuten
266     * @example
267     * <pre name="test">
268     *                                     //01234 
269     * Erottelija erottaja = new Erottelija("a;b;",";");
270     *                               erottaja.isDelimBefore() === false;
271     * erottaja.nextToken() === "a"; erottaja.isDelimBefore() === true;
272     * erottaja.nextToken() === "b"; erottaja.isDelimBefore() === true;
273     * erottaja.nextToken() === "";  erottaja.isDelimBefore() === false;
274     * erottaja.nextToken() === "";  erottaja.isDelimBefore() === false;
275     * </pre>
276     */
277    public boolean isDelimBefore() {
278        return isDelimBefore(startPos, null);
279    }
280
281
282    /**
283     * Palauttaa seuraavan palasen jonosta.
284     * @param delim erotinjoukko, jonka perusteella perusteella erotetaan
285     * @return jonon seuraava palanen
286     * @example
287     * <pre name="test">
288     *   Erottelija erottaja = new Erottelija("a b;c");
289     *   erottaja.nextToken(" ") === "a";
290     *   erottaja.nextToken(" ") === "b;c";
291     *   erottaja = new Erottelija("a b;c");
292     *   erottaja.nextToken(" ") === "a";
293     *   erottaja.nextToken(";") === "b";
294     *   erottaja.nextToken(" ") === "c";
295     *   erottaja.nextToken(" ") === "";
296     *   erottaja = new Erottelija(null);
297     *   erottaja.nextToken(" ") === "";
298     *   erottaja = new Erottelija("a b");
299     *   erottaja.nextToken(null) === "a";
300     * </pre>
301     */
302    public String nextToken(String delim) {
303        if (originalString == null)
304            return "";
305        int len = originalString.length();
306        if (startPos > len)
307            return "";
308        if (startPos == len) {
309            startPos = len + 1;
310            return "";
311        }
312        String usedDelim = delim;
313        if (delim == null)
314            usedDelim = this.delimiters;
315        int nextpos = indexOfAny(originalString, usedDelim, startPos);
316        if (nextpos < 0)
317            nextpos = len;
318        String result = originalString.substring(startPos, nextpos);
319        startPos = nextpos;
320        if (startPos < len)
321            startPos++;
322        return result;
323    }
324
325
326    /**
327     * Ottaa seuraavan palasen ja jos se on tyhjä, niin palauttaa def-jonon.
328     * @param delim erotinjoukko, jonka perusteella perusteella erotetaan
329     * @param def oletusarvo jos seuraava palanen on tyhjä
330     * @return jonon seuraava palanen tai oletus
331     * @example
332     * <pre name="test">
333     *   Erottelija erottaja = new Erottelija("a b;c");
334     *   erottaja.nextToken(" ","d") === "a";
335     *   erottaja.nextToken(" ","d") === "b;c";
336     *   erottaja.nextToken(" ","d") === "d";
337     * </pre>  
338     */
339    public String nextToken(String delim, String def) {
340        String piece = nextToken(delim);
341        if (piece.length() > 0)
342            return piece;
343        return def;
344    }
345
346
347    /**
348     * Palauttaa jonosta seuraavan kokonaisluvun ja oletuksen jos luku ei ole
349     * kunnollinen.
350     * @param delim erotinjoukko, jonka perusteella perusteella erotetaan
351     * @param def oletusarvo jos luku ei ole kunnollinen
352     * @return seuraava kokonaisluku tai oletus
353     * @example
354     * <pre name="test">
355     *   Erottelija erottaja = new Erottelija("1;2");
356     *   erottaja.nextToken(";",3) === 1; 
357     *   erottaja.nextToken(";",3) === 2; 
358     *   erottaja.nextToken(";",3) === 3; 
359     * </pre>
360     */
361    public int nextToken(String delim, int def) {
362        String piece = nextToken(delim);
363        return Mjonot.erotaInt(piece, def);
364    }
365
366
367    /**
368     * Palauttaa jonosta seuraavan kokonaisluvun ja oletuksen jos luku ei ole
369     * kunnollinen.
370     * @param def oletusarvo jos luku ei ole kunnollinen
371     * @return seuraava kokonaisluku tai oletus
372     * @example
373     * <pre name="test">
374     *   Erottelija erottaja = new Erottelija("1 2");
375     *   erottaja.nextToken(3) === 1; 
376     *   erottaja.nextToken(3) === 2; 
377     *   erottaja.nextToken(3) === 3; 
378     * </pre>
379     */
380    public int nextToken(int def) {
381        return nextToken(null, def);
382    }
383
384
385    /**
386     * Palauttaa jonosta seuraavan kokonaisluvun ja 0 jos luku ei ole
387     * kunnollinen.
388     * @return seuraava kokonaisluku tai 0
389     * @example
390     * <pre name="test">
391     *   Erottelija erottaja = new Erottelija("1 2");
392     *   erottaja.nextInt() === 1; 
393     *   erottaja.nextInt() === 2; 
394     *   erottaja.nextInt() === 0; 
395     * </pre>
396     */
397    public int nextInt() {
398        return nextToken(0);
399    }
400
401
402    /**
403     * Palauttaa jonosta seuraavan reaaliluvun ja oletuksen jos luku ei ole
404     * kunnollinen.
405     * @param delim erotinjoukko, jonka perusteella perusteella erotetaan
406     * @param def oletusarvo jos luku ei ole kunnollinen
407     * @return seuraava reaaliluku tai oletus
408     * @example
409     * <pre name="test">
410     *   Erottelija erottaja = new Erottelija("1;2");
411     *   erottaja.nextToken(";",3.1) ~~~ 1.0; 
412     *   erottaja.nextToken(";",3.1) ~~~ 2.0; 
413     *   erottaja.nextToken(";",3.1) ~~~ 3.1; 
414     * </pre>
415     */
416    public double nextToken(String delim, double def) {
417        String piece = nextToken(delim);
418        return Mjonot.erotaDouble(piece, def);
419    }
420
421
422    /**
423     * Palauttaa jonosta seuraavan reaaliluvun ja oletuksen jos luku ei ole
424     * kunnollinen.
425     * @param def oletusarvo jos luku ei ole kunnollinen
426     * @return seuraava reaaliluku tai oletus
427     * @example
428     * <pre name="test">
429     *   Erottelija erottaja = new Erottelija("1 2");
430     *   erottaja.nextToken(3.1) ~~~ 1.0; 
431     *   erottaja.nextToken(3.1) ~~~ 2.0; 
432     *   erottaja.nextToken(3.1) ~~~ 3.1; 
433     * </pre>
434     */
435    public double nextToken(double def) {
436        return nextToken(null, def);
437    }
438
439
440    /**
441     * Palauttaa jonosta seuraavan reaaliluvun ja 0.0 jos luku ei ole
442     * kunnollinen.
443     * @return seuraava reaaliluku tai 0.0
444     * @example
445     * <pre name="test">
446     *   Erottelija erottaja = new Erottelija("1 2");
447     *   erottaja.nextDouble() ~~~ 1.0; 
448     *   erottaja.nextDouble() ~~~ 2.0; 
449     *   erottaja.nextDouble() ~~~ 0.0; 
450     * </pre>
451     */
452    public double nextDouble() {
453        return nextToken(0.0);
454    }
455
456
457    /**
458     * Laskee palasten lukumäärän.
459     * @param pos paikka josta laskeminen aloitetaan
460     * @return palasten lukumäärä.
461     * @example
462     * <pre name="test">
463     *   Erottelija erottaja = new Erottelija("1 2");
464     *   erottaja.countTokens(0) === 2; 
465     *   erottaja.countTokens(2) === 1; 
466     *   erottaja = new Erottelija("1");
467     *   erottaja.countTokens(0) === 1; 
468     *   erottaja.countTokens(1) === 0; 
469     * </pre>
470     */
471    public int countTokens(int pos) {
472        int n = 1;
473        int len = originalString.length();
474        if (pos > len)
475            return 0;
476        if (pos == len)
477            return isDelimBefore(pos) ? 1 : 0;
478        for (int i = pos; i < len; i++) {
479            char c = originalString.charAt(i);
480            if (delimiters.indexOf(c) >= 0)
481                n++;
482        }
483        return n;
484    }
485
486
487    /**
488     * Laskee palasten lukumäärän.
489     * @return palasten lukumäärä.
490     * @example
491     * <pre name="test">
492     *   Erottelija erottaja = new Erottelija("1 2");
493     *   erottaja.countTokens() === 2; 
494     * </pre>
495     */
496    public int countTokens() {
497        return countTokens(0);
498    }
499
500
501    /**
502     * Laskee palasten lukumäärän.
503     * @return palasten lukumäärä.
504     * @example
505     * <pre name="test">
506     *   Erottelija erottaja = new Erottelija("1 2");
507     *   erottaja.countRemainingTokens() === 2;
508     *   erottaja.nextDouble() ~~~ 1.0; 
509     *   erottaja.countRemainingTokens() === 1;
510     *   erottaja.nextDouble() ~~~ 2.0; 
511     *   erottaja.countRemainingTokens() === 0;
512     *   erottaja.nextDouble() ~~~ 0.0; 
513     * </pre>
514     */
515    public int countRemainingTokens() {
516        return countTokens(startPos);
517    }
518
519
520    /**
521     * Tarkistaa että vieläkö palasia on jäljellä.
522     * @return onko palasia jäljellä
523     * @example
524     * <pre name="test">
525     *   Erottelija erottaja = new Erottelija("1 2");
526     *   erottaja.hasMoreElements() === true;
527     *   erottaja.nextDouble() ~~~ 1.0; 
528     *   erottaja.hasMoreElements() === true;
529     *   erottaja.nextDouble() ~~~ 2.0; 
530     *   erottaja.hasMoreElements() === false;
531     *   erottaja.nextDouble() ~~~ 0.0; 
532     * </pre>
533     */
534    @Override
535    public boolean hasMoreElements() {
536        if (isDelimBefore())
537            return true;
538        return (startPos < originalString.length());
539    }
540
541
542    /**
543     * Tarkistaa että vieläkö palasia on jäljellä.
544     * @return onko palasia jäljellä
545     * @example
546     * <pre name="test">
547     *   Erottelija erottaja = new Erottelija("1 2");
548     *   erottaja.hasMoreTokens() === true;
549     *   erottaja.nextDouble() ~~~ 1.0; 
550     *   erottaja.nextDouble() ~~~ 2.0; 
551     *   erottaja.hasMoreTokens() === false;
552     *   erottaja.nextDouble() ~~~ 0.0; 
553     * </pre>
554     */
555    public boolean hasMoreTokens() {
556        return hasMoreElements();
557    }
558
559
560    /**
561     * Palauttaa seuraavan palasen Objectina.
562     * @return seuraava palanen
563     * @example
564     * <pre name="test">
565     *   Erottelija erottaja = new Erottelija("1 2");
566     *   erottaja.nextElement() === "1";
567     *   erottaja.nextElement() === "2";
568     *   erottaja.nextElement() === "";
569     * </pre>
570     */
571    @Override
572    public String nextElement() {
573        return nextToken();
574    }
575
576
577    /**
578     * Siivoaa palasteltavan jonon turhista välilyönneistä
579     * @example
580     * <pre name="test">
581     *   Erottelija erottaja = new Erottelija(" 1   2 ");
582     *   erottaja.countTokens() === 6;
583     *   erottaja.trim();
584     *   erottaja.countTokens() === 4;
585     * </pre>
586     */
587    public void trim() {
588        originalString = Mjonot.poista_2_tyhjat(originalString);
589        startPos = 0;
590    }
591
592
593    /**
594     * Palauttaa jäljellä olevan jonon.
595     * @return jäljellä oleva jono.
596     * @example
597     * <pre name="test">
598     *   Erottelija erottaja = new Erottelija(" 1   2 ");
599     *   erottaja.trim();
600     *   erottaja.rest() === " 1 2 ";
601     * </pre>
602     */
603    public String rest() {
604        if (!hasMoreTokens())
605            return "";
606        return originalString.substring(startPos);
607    }
608
609
610    /**
611     * Palauttaa paikan erottelijan alkuun.
612     * @example
613     * <pre name="test">
614     *   Erottelija erottaja = new Erottelija("1 2");
615     *   erottaja.nextDouble() ~~~ 1.0; 
616     *   erottaja.nextDouble() ~~~ 2.0; 
617     *   erottaja.nextDouble() ~~~ 0.0;
618     *   erottaja.reset(); 
619     *   erottaja.nextDouble() ~~~ 1.0; 
620     *   erottaja.reset(); 
621     *   erottaja.nextDouble() ~~~ 1.0; 
622     * </pre>
623     */
624    public void reset() {
625        startPos = 0;
626    }
627
628    /**
629     * Testataan Erottelijaluokkaa
630     * @param args ei käytössä
631     */
632    /*
633      public static void main(String[] args) {
634        Erottelija erottaja = new Erottelija("12;3.5:kissa,,,istuu puussa,3.4",";:,");
635        System.out.println("Palasia: " + erottaja.countTokens());
636        for (int i=1; erottaja.hasMoreTokens(); i++ )
637          System.out.println(i + ": |" + erottaja.nextToken()+"|");
638        System.out.println("8: |"+erottaja.nextToken()+"|");
639        erottaja.reset();
640        System.out.println(erottaja.nextToken(0));
641        System.out.println(erottaja.countRemainingTokens());
642        System.out.println(erottaja.rest());
643        System.out.println(erottaja.nextToken(0.0));
644        System.out.println(erottaja.nextToken(2));
645        System.out.println(erottaja.nextToken(2.1));
646        System.out.println(erottaja.countRemainingTokens());
647        System.out.println(erottaja.rest());
648      }
649    */
650}