1   package fi.jyu.mit.ohj2;
2   
3   import java.util.Enumeration;
4   
5   /**
6    * Luokka StringTokenizerin korvaajaksi.  Erona
7    * on se että jonon loppuessa ei tule ongelmia ja 
8    * peräkkäisten erottimien välistä tulee tyhjä jono.
9    * <pre>
10   * Esimerkki:
11   * public static void main(String[] args) {
12   *     Erottelija erottaja = new Erottelija("12;3.5:kissa,,,istuu puussa,3.4",
13   *                                          ";:,");
14   *     System.out.println("Palasia: " + erottaja.countTokens());
15   *     for (int i=1; erottaja.hasMoreTokens(); i++ )
16   *       System.out.println(i + ": |" + erottaja.nextToken()+"|");
17   *     System.err.println("8: |"+erottaja.nextToken()+"|");
18   *     erottaja.reset();
19   *     System.out.println(erottaja.nextToken(0));
20   *     System.out.println(erottaja.countRemaininTokens());
21   *     System.out.println(erottaja.rest());
22   *     System.out.println(erottaja.nextToken(0.0));
23   *     System.out.println(erottaja.nextToken(2));
24   *     System.out.println(erottaja.nextToken(2.1));
25   *     System.out.println(erottaja.countRemainingTokens());
26   *     System.out.println(erottaja.rest());
27   *   }
28   *
29   * Tulostaa:
30   * 
31   * Palasia: 7
32   * 1: |12|
33   * 2: |3.5|
34   * 3: |kissa|
35   * 4: ||
36   * 5: ||
37   * 6: |istuu puussa|
38   * 7: |3.4|
39   * 8: ||
40   * 12
41   * 6
42   * 3.5:kissa,,,istuu puussa,3.4
43   * 3
44   * ,istuu puussa,3.4
45   * 12
46   * 3.5
47   * 2
48   * 2.1
49   * </pre>
50   * @author vesal
51   * @version 11.3.2007
52   * 
53   * @example
54   * <pre name="testErottelija">
55   * Erottelija erottaja = new Erottelija("12;3.5:kissa,,,istuu puussa,3.4",";:,");
56   * erottaja.nextToken() === $s; erottaja.hasMoreTokens() === $tokens;
57   * erottaja.countRemainingTokens() === $n; erottaja.rest() === $rest;   
58   * 
59   *   $i  |  $s             | $tokens | $n | $rest
60   *  --------------------------------------------------------------------------  
61   *   0   | ---             | true    | 7  | "12;3.5:kissa,,,istuu puussa,3.4"
62   *   1   | "12"            | true    | 6  | "3.5:kissa,,,istuu puussa,3.4"
63   *   2   | "3.5"           | true    | 5  | "kissa,,,istuu puussa,3.4"
64   *   3   | "kissa"         | true    | 4  | ",,istuu puussa,3.4"
65   *   4   | ""              | true    | 3  | ",istuu puussa,3.4"
66   *   5   | ""              | true    | 2  | "istuu puussa,3.4"
67   *   6   | "istuu puussa"  | true    | 1  | "3.4"
68   *   7   | "3.4"           | false   | 0  | ""
69   *   8   | ""              | false   | 0  | ""
70   *   9   | ""              | false   | 0  | ""
71   *   
72   *   erottaja.nextToken(";","kissa") === "kissa";
73   * 
74   * @example
75   * </pre>
76   * <pre name="testErottelijaEnd">
77   * Erottelija erottaja = new Erottelija("12;3.5:kissa,,,istuu puussa,3.4;;",";:,");
78   * erottaja.nextToken() === $s; erottaja.hasMoreTokens() === $tokens;
79   * erottaja.countRemainingTokens() === $n; erottaja.rest() === $rest;   
80   * 
81   *   $i  |  $s             | $tokens | $n | $rest
82   *  --------------------------------------------------------------------------  
83   *   0   | ---             | true    | 9  | "12;3.5:kissa,,,istuu puussa,3.4;;"
84   *   1   | "12"            | true    | 8  | "3.5:kissa,,,istuu puussa,3.4;;"
85   *   2   | "3.5"           | true    | 7  | "kissa,,,istuu puussa,3.4;;"
86   *   3   | "kissa"         | true    | 6  | ",,istuu puussa,3.4;;"
87   *   4   | ""              | true    | 5  | ",istuu puussa,3.4;;"
88   *   5   | ""              | true    | 4  | "istuu puussa,3.4;;"
89   *   6   | "istuu puussa"  | true    | 3  | "3.4;;"
90   *   7   | "3.4"           | true    | 2  | ";"
91   *   8   | ""              | true    | 1  | ""
92   *   9   | ""              | false   | 0  | ""
93   *  10   | ""              | false   | 0  | ""
94   *   
95   *   erottaja.nextToken(";","kissa") === "kissa";
96   * </pre>
97   */
98  public class Erottelija implements Enumeration<String> { // NOPMD - enum ok
99  
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 }
651