1   package fi.jyu.mit.ohj2;
2   import java.util.Enumeration;
3   
4   /**
5    * Luokka StringTokenizerin korvaajaksi.  Erona
6    * on se että jonon loppuessa ei tule ongelmia ja 
7    * peräkkäisten erottimien välistä tulee tyhjä jono.
8    * <pre>
9    * Esimerkki:
10   * public static void main(String[] args) {
11   *     Erottelija erottaja = new Erottelija("12;3.5:kissa,,,istuu puussa,3.4",
12   *                                          ";:,");
13   *     System.out.println("Palasia: " + erottaja.countTokens());
14   *     for (int i=1; erottaja.hasMoreTokens(); i++ )
15   *       System.out.println(i + ": |" + erottaja.nextToken()+"|");
16   *     System.err.println("8: |"+erottaja.nextToken()+"|");
17   *     erottaja.reset();
18   *     System.out.println(erottaja.nextToken(0));
19   *     System.out.println(erottaja.countRemaininTokens());
20   *     System.out.println(erottaja.rest());
21   *     System.out.println(erottaja.nextToken(0.0));
22   *     System.out.println(erottaja.nextToken(2));
23   *     System.out.println(erottaja.nextToken(2.1));
24   *     System.out.println(erottaja.countRemainingTokens());
25   *     System.out.println(erottaja.rest());
26   *   }
27   *
28   * Tulostaa:
29   * 
30   * Palasia: 7
31   * 1: |12|
32   * 2: |3.5|
33   * 3: |kissa|
34   * 4: ||
35   * 5: ||
36   * 6: |istuu puussa|
37   * 7: |3.4|
38   * 8: ||
39   * 12
40   * 6
41   * 3.5:kissa,,,istuu puussa,3.4
42   * 3
43   * ,istuu puussa,3.4
44   * 12
45   * 3.5
46   * 2
47   * 2.1
48   * </pre>
49   * @author vesal
50   * @version 11.3.2007
51   * 
52   * @example
53   * <pre name="testErottelija">
54   * Erottelija erottaja = new Erottelija("12;3.5:kissa,,,istuu puussa,3.4",
55   *                                          ";:,");
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   *                                          ";:,");
79   * erottaja.nextToken() === $s; erottaja.hasMoreTokens() === $tokens;
80   * erottaja.countRemainingTokens() === $n; erottaja.rest() === $rest;   
81   * 
82   *   $i  |  $s             | $tokens | $n | $rest
83   *  --------------------------------------------------------------------------  
84   *   0   | ---             | true    | 9  | "12;3.5:kissa,,,istuu puussa,3.4;;"
85   *   1   | "12"            | true    | 8  | "3.5:kissa,,,istuu puussa,3.4;;"
86   *   2   | "3.5"           | true    | 7  | "kissa,,,istuu puussa,3.4;;"
87   *   3   | "kissa"         | true    | 6  | ",,istuu puussa,3.4;;"
88   *   4   | ""              | true    | 5  | ",istuu puussa,3.4;;"
89   *   5   | ""              | true    | 4  | "istuu puussa,3.4;;"
90   *   6   | "istuu puussa"  | true    | 3  | "3.4;;"
91   *   7   | "3.4"           | true    | 2  | ";"
92   *   8   | ""              | true    | 1  | ""
93   *   9   | ""              | false   | 0  | ""
94   *  10   | ""              | false   | 0  | ""
95   *   
96   *   erottaja.nextToken(";","kissa") === "kissa";
97   * </pre>
98   */
99  @SuppressWarnings("unchecked")
100 public class Erottelija implements Enumeration { // NOPMD - enum ok
101 
102   /**
103    * Etsitään mistä kohti jonosta str löytyy ensimmäinen erotinmerkki 
104    * joukosta delim.  Etsintä aloitetaan paikasta pos.
105    * @param str    mistä jonosta etsitään
106    * @param delim  joukko erotinmerkkejä
107    * @param pos    paikka josta aloitetaan 
108    * @return       palauttaa ensimmäisen esiintymän tai -1 jos ei löydy
109    * @example
110    * <pre name="test">
111    *   indexOfAny("a;, b",",; ",0) === 1
112    *   indexOfAny("a;, b",",; ",2) === 2
113    *   indexOfAny("a;, b"," ",0)   === 3
114    *   indexOfAny("a;, b",".",0)   === -1
115    *   indexOfAny(null,",; ",0)    === -1
116    *   indexOfAny("a b",",; ",-1)  === 1
117    * </pre>
118    */
119   public static int indexOfAny(String str, String delim, int pos) {
120     int i=pos; if ( i<0 ) i = 0;
121     if ( str == null || delim == null ) return -1;
122     for ( ; i<str.length(); i++ ) {
123       char c = str.charAt(i);
124       if ( delim.indexOf(c) >= 0 ) return i;
125     }
126     return -1;
127   }
128 
129   /**
130    * Etsitään mistä kohti jonosta str löytyy ensimmäinen erotinmerkki 
131    * joukosta delim.  Etsintä aloitetaan alusta.
132    * @param str    mistä jonosta etsitään
133    * @param delim  joukko erotinmerkkejä
134    * @return       palauttaa ensimmäisen esiintymän tai -1 jos ei löydy
135    * @example
136    * <pre name="test">
137    *   indexOfAny("a;, b",",; ") === 1
138    * </pre>
139    */
140   public static int indexOfAny(String str, String delim) {
141     return indexOfAny(str,delim,0);
142   }
143 
144 
145   private String str;
146   private final String delim;
147   private int pos=0;
148 
149   /**
150    * Luodaan erottelija, joka erottelee jonosta str palasia minkä tahansa
151    * joukosta delim löytyvän merkin kohdalta. 
152    * @param str    jono josta erotellaan
153    * @param delim  erottavien merkkien joukko
154    */
155   public Erottelija(String str, String delim) {
156     this.str = str;
157     this.delim = delim;
158   }
159 
160   /**
161    * Luodaan erottelija, joka erottelee jonosta str palasia välilyönnin 
162    * kohdalta.
163    * @param str    jono josta erotellaan
164    * @example
165    * <pre name="test">
166    *                                     //01234 
167    * Erottelija erottaja = new Erottelija("a b ");
168    * erottaja.countTokens() === 3;
169    * </pre>
170    */
171   public Erottelija(String str) {
172     this.str = str; 
173     this.delim = " ";
174   }
175 
176   /**
177    * Palauttaa seuraavan palasen jonosta
178    * @return jonon seuraava palanen
179    * @example
180    * <pre name="test">
181    * Erottelija erottaja = new Erottelija("a;b;",";");
182    * erottaja.nextToken() === $s; erottaja.hasMoreTokens() === $tokens;
183    * erottaja.countRemainingTokens() === $n; erottaja.rest() === $rest;   
184    * 
185    *   $i  |  $s             | $tokens | $n | $rest
186    *  --------------------------------------------------------------------------  
187    *   0   | ---             | true    | 3  | "a;b;"
188    *   1   | "a"             | true    | 2  | "b;"
189    *   2   | "b"             | true    | 1  | ""
190    *   3   | ""              | false   | 0  | ""
191    *   4   | ""              | false   | 0  | ""
192    * </pre>
193    */
194   public String nextToken() {
195     return nextToken(delim);
196   }
197 
198   /**
199    * Tarkistaa onko erotinmerkkiä juuri paikan vasemmalla puolella
200    * @param pos paikka josta tutkitaan
201    * @param delim kelpaavat erotinmerkit
202    * @return true jos on erotin merkki vasemmalla puolella, false muuten
203    * @example
204    * <pre name="test">
205    *                                     //01234 
206    * Erottelija erottaja = new Erottelija("a;b;",";");
207    * erottaja.isDelimBefore(0,";") === false;
208    * erottaja.isDelimBefore(1,";") === false;
209    * erottaja.isDelimBefore(2,";") === true;
210    * erottaja.isDelimBefore(3,";") === false;
211    * erottaja.isDelimBefore(4,";") === true;
212    * erottaja.isDelimBefore(5,";") === false;
213    * </pre>
214    */
215   public boolean isDelimBefore(int pos, String delim) {
216      if ( pos <= 0 ) return false;
217      if ( pos > str.length() ) return false;
218      String usedDelim = delim;
219      if ( delim == null ) usedDelim = this.delim;
220      char c = str.charAt(pos-1);
221      return ( usedDelim.indexOf(c) >= 0 );
222   }
223   
224   /**
225    * Tarkistaa onko erotinmerkkiä juuri paikan vasemmalla puolella
226    * @param pos paikka josta tutkitaan
227    * @return true jos on erotin merkki vasemmalla puolella, false muuten
228    * @example
229    * <pre name="test">
230    *                                     //01234 
231    * Erottelija erottaja = new Erottelija("a;b;",";");
232    * erottaja.isDelimBefore(0) === false;
233    * erottaja.isDelimBefore(1) === false;
234    * erottaja.isDelimBefore(2) === true;
235    * erottaja.isDelimBefore(3) === false;
236    * erottaja.isDelimBefore(4) === true;
237    * erottaja.isDelimBefore(5) === false;
238    * </pre>
239    */
240   public boolean isDelimBefore(int pos) {
241      return isDelimBefore(pos,null);
242   }
243   
244   /**
245    * Tarkistaa onko erotinmerkkiä juuri nykypaikan vasemmalla puolella
246    * @return true jos on erotin merkki vasemmalla puolella, false muuten
247    * @example
248    * <pre name="test">
249    *                                     //01234 
250    * Erottelija erottaja = new Erottelija("a;b;",";");
251    *                               erottaja.isDelimBefore() === false;
252    * erottaja.nextToken() === "a"; erottaja.isDelimBefore() === true;
253    * erottaja.nextToken() === "b"; erottaja.isDelimBefore() === true;
254    * erottaja.nextToken() === "";  erottaja.isDelimBefore() === false;
255    * erottaja.nextToken() === "";  erottaja.isDelimBefore() === false;
256    * </pre>
257    */
258   public boolean isDelimBefore() {
259      return isDelimBefore(pos,null);
260   }
261   
262   
263   /**
264    * Palauttaa seuraavan palasen jonosta.
265    * @param delim erotinjoukko, jonka perusteella perusteella erotetaan
266    * @return jonon seuraava palanen
267    * @example
268    * <pre name="test">
269    *   Erottelija erottaja = new Erottelija("a b;c");
270    *   erottaja.nextToken(" ") === "a";
271    *   erottaja.nextToken(" ") === "b;c";
272    *   erottaja = new Erottelija("a b;c");
273    *   erottaja.nextToken(" ") === "a";
274    *   erottaja.nextToken(";") === "b";
275    *   erottaja.nextToken(" ") === "c";
276    *   erottaja.nextToken(" ") === "";
277    *   erottaja = new Erottelija(null);
278    *   erottaja.nextToken(" ") === "";
279    *   erottaja = new Erottelija("a b");
280    *   erottaja.nextToken(null) === "a";
281    * </pre>
282    */
283   public String nextToken(String delim) {
284     if ( str == null ) return "";
285     int len = str.length();
286     if ( pos > len ) return "";
287     if ( pos == len ) { pos = len+1; return ""; }
288     String usedDelim = delim;
289     if ( delim == null ) usedDelim = this.delim;
290     int nextpos = indexOfAny(str,usedDelim,pos);
291     if ( nextpos < 0 ) nextpos = len;
292     String result = str.substring(pos,nextpos);
293     pos = nextpos;
294     if ( pos < len ) pos++;
295     return result;
296   }
297 
298   /**
299    * Ottaa seuraavan palasen ja jos se on tyhjä, niin palauttaa def-jonon.
300    * @param delim erotinjoukko, jonka perusteella perusteella erotetaan
301    * @param def oletusarvo jos seuraava palanen on tyhjä
302    * @return jonon seuraava palanen tai oletus
303    * @example
304    * <pre name="test">
305    *   Erottelija erottaja = new Erottelija("a b;c");
306    *   erottaja.nextToken(" ","d") === "a";
307    *   erottaja.nextToken(" ","d") === "b;c";
308    *   erottaja.nextToken(" ","d") === "d";
309    * </pre>  
310    */
311   public String nextToken(String delim, String def) {
312     String piece = nextToken(delim);
313     if ( piece.length() > 0 ) return piece;
314     return def;
315   }
316 
317   /**
318    * Palauttaa jonosta seuraavan kokonaisluvun ja oletuksen jos luku ei ole
319    * kunnollinen.
320    * @param delim erotinjoukko, jonka perusteella perusteella erotetaan
321    * @param def oletusarvo jos luku ei ole kunnollinen
322    * @return seuraava kokonaisluku tai oletus
323    * @example
324    * <pre name="test">
325    *   Erottelija erottaja = new Erottelija("1;2");
326    *   erottaja.nextToken(";",3) === 1; 
327    *   erottaja.nextToken(";",3) === 2; 
328    *   erottaja.nextToken(";",3) === 3; 
329    * </pre>
330    */
331   public int nextToken(String delim, int def) {
332     String piece = nextToken(delim);
333     return Mjonot.erotaInt(piece,def);
334   }
335 
336   /**
337    * Palauttaa jonosta seuraavan kokonaisluvun ja oletuksen jos luku ei ole
338    * kunnollinen.
339    * @param def oletusarvo jos luku ei ole kunnollinen
340    * @return seuraava kokonaisluku tai oletus
341    * @example
342    * <pre name="test">
343    *   Erottelija erottaja = new Erottelija("1 2");
344    *   erottaja.nextToken(3) === 1; 
345    *   erottaja.nextToken(3) === 2; 
346    *   erottaja.nextToken(3) === 3; 
347    * </pre>
348    */
349   public int nextToken(int def) {
350     return nextToken(null,def);
351   }
352 
353   
354   /**
355    * Palauttaa jonosta seuraavan kokonaisluvun ja 0 jos luku ei ole
356    * kunnollinen.
357    * @return seuraava kokonaisluku tai 0
358    * @example
359    * <pre name="test">
360    *   Erottelija erottaja = new Erottelija("1 2");
361    *   erottaja.nextInt() === 1; 
362    *   erottaja.nextInt() === 2; 
363    *   erottaja.nextInt() === 0; 
364    * </pre>
365    */
366   public int nextInt() {
367     return nextToken(0);
368   }
369   
370   
371   /**
372    * Palauttaa jonosta seuraavan reaaliluvun ja oletuksen jos luku ei ole
373    * kunnollinen.
374    * @param delim erotinjoukko, jonka perusteella perusteella erotetaan
375    * @param def oletusarvo jos luku ei ole kunnollinen
376    * @return seuraava reaaliluku tai oletus
377    * @example
378    * <pre name="test">
379    *   Erottelija erottaja = new Erottelija("1;2");
380    *   erottaja.nextToken(";",3.1) ~~~ 1.0; 
381    *   erottaja.nextToken(";",3.1) ~~~ 2.0; 
382    *   erottaja.nextToken(";",3.1) ~~~ 3.1; 
383    * </pre>
384    */
385   public double nextToken(String delim, double def) {
386     String piece = nextToken(delim);
387     return Mjonot.erotaDouble(piece,def);
388   }
389 
390   /**
391    * Palauttaa jonosta seuraavan reaaliluvun ja oletuksen jos luku ei ole
392    * kunnollinen.
393    * @param def oletusarvo jos luku ei ole kunnollinen
394    * @return seuraava reaaliluku tai oletus
395    * @example
396    * <pre name="test">
397    *   Erottelija erottaja = new Erottelija("1 2");
398    *   erottaja.nextToken(3.1) ~~~ 1.0; 
399    *   erottaja.nextToken(3.1) ~~~ 2.0; 
400    *   erottaja.nextToken(3.1) ~~~ 3.1; 
401    * </pre>
402    */
403   public double nextToken(double def) {
404     return nextToken(null,def);
405   }
406 
407   /**
408    * Palauttaa jonosta seuraavan reaaliluvun ja 0.0 jos luku ei ole
409    * kunnollinen.
410    * @return seuraava reaaliluku tai 0.0
411    * @example
412    * <pre name="test">
413    *   Erottelija erottaja = new Erottelija("1 2");
414    *   erottaja.nextDouble() ~~~ 1.0; 
415    *   erottaja.nextDouble() ~~~ 2.0; 
416    *   erottaja.nextDouble() ~~~ 0.0; 
417    * </pre>
418    */
419   public double nextDouble() {
420     return nextToken(0.0);
421   }
422   
423   
424   /**
425    * Laskee palasten lukumäärän.
426    * @param pos paikka josta laskeminen aloitetaan
427    * @return palasten lukumäärä.
428    */
429   public int countTokens(int pos) {
430     int n=1;
431     int len=str.length();
432     if ( pos > len ) return 0;
433     if ( pos == len ) return isDelimBefore(pos) ? 1 : 0;
434     for (int i=pos; i<len; i++) {
435       char c = str.charAt(i);
436       if ( delim.indexOf(c) >= 0 ) n++;
437     }
438     return n;
439   }
440 
441   /**
442    * Laskee palasten lukumäärän.
443    * @return palasten lukumäärä.
444    */
445   public int countTokens() {
446     return countTokens(0);
447   }
448    
449   
450   /**
451    * Laskee palasten lukumäärän.
452    * @return palasten lukumäärä.
453    */
454   public int countRemainingTokens() {
455     return countTokens(pos);
456   }
457 
458   /**
459    * Tarkistaa että vieläkö palasia on jäljellä.
460    * @return onko palasia jäljellä
461    */
462   public boolean hasMoreElements() {
463     if ( isDelimBefore() ) return true;  
464     return ( pos < str.length() );
465   }
466 
467   /**
468    * Tarkistaa että vieläkö palasia on jäljellä.
469    * @return onko palasia jäljellä
470    */
471   public boolean hasMoreTokens() {
472     return hasMoreElements();
473   }
474 
475   /**
476    * Palauttaa seuraavan palasen Objectina.
477    * @return seuraava palanen
478    * @example
479    * <pre name="test">
480    *   Erottelija erottaja = new Erottelija("1 2");
481    *   erottaja.nextElement().toString() === "1";
482    * </pre>
483    */
484   public Object nextElement() {
485     return nextToken();
486   }
487 
488   /**
489    * Siivoaa palasteltavan jonon turhista välilyönneistä
490    * @example
491    * <pre name="test">
492    *   Erottelija erottaja = new Erottelija(" 1   2 ");
493    *   erottaja.countTokens() === 6;
494    *   erottaja.trim();
495    *   erottaja.countTokens() === 4;
496    * </pre>
497    */
498   public void trim() {
499     str = Mjonot.poista_2_tyhjat(str);
500     pos = 0;
501   }
502   
503   /**
504    * Palauttaa jäljellä olevan jonon.
505    * @return jäljellä oleva jono.
506    * @example
507    * <pre name="test">
508    *   Erottelija erottaja = new Erottelija(" 1   2 ");
509    *   erottaja.trim();
510    *   erottaja.rest() === " 1 2 ";
511    * </pre>
512    */
513   public String rest() {
514     if ( !hasMoreTokens() ) return "";
515     return str.substring(pos);
516   }
517   
518   /**
519    * Palauttaa paikan erottelijan alkuun.
520    */
521   public void reset() {
522     pos = 0;    
523   }
524 
525   /**
526    * Testataan Erottelijaluokkaa
527    * @param args ei käytössä
528    */
529 /*
530   public static void main(String[] args) {
531     Erottelija erottaja = new Erottelija("12;3.5:kissa,,,istuu puussa,3.4",";:,");
532     System.out.println("Palasia: " + erottaja.countTokens());
533     for (int i=1; erottaja.hasMoreTokens(); i++ )
534       System.out.println(i + ": |" + erottaja.nextToken()+"|");
535     System.out.println("8: |"+erottaja.nextToken()+"|");
536     erottaja.reset();
537     System.out.println(erottaja.nextToken(0));
538     System.out.println(erottaja.countRemainingTokens());
539     System.out.println(erottaja.rest());
540     System.out.println(erottaja.nextToken(0.0));
541     System.out.println(erottaja.nextToken(2));
542     System.out.println(erottaja.nextToken(2.1));
543     System.out.println(erottaja.countRemainingTokens());
544     System.out.println(erottaja.rest());
545   }
546 */
547 }
548