1   package kerho;
2   
3   import java.io.BufferedReader;
4   import java.io.File;
5   import java.io.IOException;
6   import java.io.PrintWriter;
7   import java.util.ArrayList;
8   import java.util.Arrays;
9   import java.util.Collection;
10  import java.util.Collections;
11  import java.util.Iterator;
12  import java.util.List;
13  import java.util.NoSuchElementException;
14  
15  import fi.jyu.mit.ohj2.Tiedosto;
16  import fi.jyu.mit.ohj2.WildChars;
17  
18  /**
19   * Kerhon jäsenistö joka osaa mm. lisätä uuden jäsenen
20   *
21   * @author Vesa Lappalainen
22   * @version 1.0, 22.02.2003
23   * @version 1.1, 19.02.2012 
24   * @version 1.2, 15.03.2012 
25   */
26  public class Jasenet implements Iterable<Jasen> {
27      private boolean muutettu = false;
28      private int MAX_JASENIA = Integer.MAX_VALUE;
29      private String kokoNimi = "";
30      private String tiedostonPerusNimi = "";
31      private int lkm = 0;
32      private Jasen alkiot[] = new Jasen[5];
33  
34  
35      /**
36       * Oletusmuodostaja jolla tulee täysin dynaaminen koko.
37       */
38      public Jasenet() {
39          // Attribuuttien oma alustus riittää
40      }
41  
42  
43      /**
44       * Muodostaja jolla maxkoko voidaan asettaa.
45       * Tällöin ei koskaan lisätä enempää kuin maksimi.
46       * @param koko jäsenistön maxkoko 
47       * 
48       */
49      public Jasenet(int koko) {
50          MAX_JASENIA = koko;
51          alkiot = new Jasen[MAX_JASENIA];
52      }
53  
54      
55      /**
56       * Lisää uuden jäsenen tietorakenteeseen.  Ottaa jäsenen omistukseensa.
57       * @param jasen lisättävän jäsenen viite.  Huom tietorakenne muuttuu omistajaksi
58       * @throws SailoException jos määrä ylittää pyydetyn maksimirajan
59       * @example
60       * <pre name="test">
61       * #THROWS SailoException 
62       * #PACKAGEIMPORT
63       * Jasenet jasenet = new Jasenet(5);
64       * Jasen aku1 = new Jasen(), aku2 = new Jasen();
65       * jasenet.getLkm() === 0;
66       * jasenet.lisaa(aku1); jasenet.getLkm() === 1;
67       * jasenet.lisaa(aku2); jasenet.getLkm() === 2;
68       * jasenet.lisaa(aku1); jasenet.getLkm() === 3;
69       * jasenet.anna(0) === aku1;
70       * jasenet.anna(1) === aku2;
71       * jasenet.anna(2) === aku1;
72       * jasenet.anna(1) == aku1 === false;
73       * jasenet.anna(1) == aku2 === true;
74       * jasenet.anna(3) === aku1; #THROWS IndexOutOfBoundsException 
75       * jasenet.lisaa(aku1); jasenet.getLkm() === 4;
76       * jasenet.lisaa(aku1); jasenet.getLkm() === 5;
77       * jasenet.lisaa(aku1);  #THROWS SailoException
78       * 
79       * jasenet = new Jasenet();
80       * for (int i=0; i<200; i++) {
81       *    jasenet.lisaa(aku1); jasenet.getLkm() === 2*i+1;
82       *    jasenet.lisaa(aku2); jasenet.getLkm() === 2*i+2;
83       * }  
84       * </pre>
85       */
86      public void lisaa(Jasen jasen) throws SailoException {
87          if (lkm >= MAX_JASENIA) throw new SailoException("Liikaa alkioita");
88          if (lkm >= alkiot.length) alkiot = Arrays.copyOf(alkiot, lkm+20);
89          alkiot[lkm] = jasen;
90          lkm++;
91          muutettu = true;
92      }
93  
94  
95      /**
96       * Poistaa jäsenen jolla on valittu tunnusnumero 
97       * @param id poistettavan jäsenen tunnusnumero
98       * @return 1 jos poistettiin, 0 jos ei löydy
99       * @example
100      * <pre name="test">
101      * #THROWS SailoException 
102      * Jasenet jasenet = new Jasenet(5);
103      * Jasen aku1 = new Jasen(), aku2 = new Jasen(), aku3 = new Jasen();
104      * aku1.rekisteroi(); aku2.rekisteroi(); aku3.rekisteroi();
105      * int id1 = aku1.getTunnusNro();
106      * jasenet.lisaa(aku1); jasenet.lisaa(aku2); jasenet.lisaa(aku3);
107      * jasenet.poista(id1+1) === 1;
108      * jasenet.etsiId(id1+1) === -1; jasenet.getLkm() === 2;
109      * jasenet.poista(id1) === 1; jasenet.getLkm() === 1;
110      * jasenet.poista(id1+3) === 0; jasenet.getLkm() === 1;
111      * </pre>
112      * 
113      */
114     public int poista(int id) {
115         int ind = etsiId(id);
116         if (ind < 0) return 0;
117         lkm--;
118         for (int i = ind; i < lkm; i++)
119             alkiot[i] = alkiot[i + 1];
120         alkiot[lkm] = null;
121         muutettu = true;
122         return 1;
123     }
124 
125 
126     /**
127      * Korvaa jäsenen tietorakenteessa.  Ottaa jäsenen omistukseensa.
128      * Etsitään samalla tunnusnumerolla oleva jäsen.  Jos ei läydy,
129      * niin lisätään uutena jäsenenä.
130      * @param jasen lisätäävän jäsenen viite.  Huom tietorakenne muuttuu omistajaksi
131      * @throws SailoException jos tietorakennen on jo täynnä
132      * <pre name="test">
133      * #THROWS SailoException,CloneNotSupportedException
134      * #PACKAGEIMPORT
135      * Jasenet jasenet = new Jasenet();
136      * Jasen aku1 = new Jasen(), aku2 = new Jasen();
137      * aku1.rekisteroi(); aku2.rekisteroi();
138      * jasenet.getLkm() === 0;
139      * jasenet.korvaaTaiLisaa(aku1); jasenet.getLkm() === 1;
140      * jasenet.korvaaTaiLisaa(aku2); jasenet.getLkm() === 2;
141      * Jasen aku3 = aku1.clone();
142      * aku3.aseta(3,"kkk");
143      * jasenet.anna(0) == aku1 === true;
144      * jasenet.korvaaTaiLisaa(aku3); jasenet.getLkm() === 2;
145      * jasenet.anna(0) === aku3;
146      * jasenet.anna(0) == aku3 === true;
147      * jasenet.anna(0) == aku1 === false;
148      * </pre>
149      */
150     public void korvaaTaiLisaa(Jasen jasen) throws SailoException {
151         int id = jasen.getTunnusNro();
152         for (int i = 0; i < lkm; i++) {
153             if (alkiot[i].getTunnusNro() == id) {
154                 alkiot[i] = jasen;
155                 muutettu = true;
156                 return;
157             }
158         }
159         lisaa(jasen);
160     }
161 
162 
163     /**
164      * Palauttaa viitteen i:teen jäseneen.
165      * @param i monennenko jäsenen viite halutaan
166      * @return viite jäseneen, jonka indeksi on i
167      * @throws IndexOutOfBoundsException jos i ei ole sallitulla alueella  
168      */
169     public Jasen anna(int i) throws IndexOutOfBoundsException {
170         if (i < 0 || lkm <= i) throw new IndexOutOfBoundsException("Laiton indeksi: " + i);
171         return alkiot[i];
172     }
173 
174 
175     /**
176      * Lukee jäsenistön tiedostosta. 
177      * @param tied tiedoston nimen alkuosa
178      * @throws SailoException jos lukeminen epäonnistuu
179      * 
180      * @example
181      * <pre name="test">
182      * #THROWS SailoException 
183      * #import java.io.File;
184      * 
185      *  Jasenet jasenet = new Jasenet();
186      *  Jasen aku1 = new Jasen(), aku2 = new Jasen();
187      *  aku1.vastaaAkuAnkka();
188      *  aku2.vastaaAkuAnkka();
189      *  String tiedNimi = "testikelmit";
190      *  File ftied = new File(tiedNimi+".dat");
191      *  ftied.delete();
192      *  jasenet.lueTiedostosta(tiedNimi); #THROWS SailoException
193      *  jasenet.lisaa(aku1);
194      *  jasenet.lisaa(aku2);
195      *  jasenet.talleta();
196      *  jasenet = new Jasenet();           // Poistetaan vanhat luomalla uusi
197      *  jasenet.lueTiedostosta(tiedNimi);  // johon ladataan tiedot tiedostosta.
198      *  Iterator<Jasen> i = jasenet.iterator();
199      *  i.next().toString() === aku1.toString();
200      *  i.next().toString() === aku2.toString();
201      *  i.hasNext() === false;
202      *  jasenet.lisaa(aku2);
203      *  jasenet.talleta();
204      *  ftied.delete() === true;
205      *  File fbak = new File(tiedNimi+".bak");
206      *  fbak.delete() === true;
207      * </pre>
208      */
209     public void lueTiedostosta(String tied) throws SailoException {
210         setTiedostonPerusNimi(tied);
211         BufferedReader fi = Tiedosto.avaa_lukemista_varten(getTiedostonNimi());
212         if (fi == null) throw new SailoException("Tiedosto " + getTiedostonNimi() + " ei aukea");
213 
214         try {
215             kokoNimi = fi.readLine();
216             if (kokoNimi == null) throw new SailoException("Kerhon nimi puuttuu");
217             String rivi = fi.readLine();
218             if (rivi == null) throw new SailoException("Maksimikoko puuttuu");
219             // int maxKoko = Mjonot.erotaInt(rivi,10); // tehdään jotakin
220 
221             while ((rivi = fi.readLine()) != null) { 
222                 rivi = rivi.trim();
223                 if ("".equals(rivi) || rivi.charAt(0) == ';') continue;
224                 Jasen jasen = new Jasen(); 
225                 jasen.parse(rivi); // voisi olla virhekäsittely
226                 lisaa(jasen);
227             }
228             muutettu = false;
229 
230         } catch (IOException e) {
231             throw new SailoException("Ongelmia tiedoston kanssa: " + e.getMessage());
232         } finally {
233             try {
234                 fi.close();
235             } catch (IOException e) {
236                 throw new SailoException("Tiedoston sulkeminen ei onnistu: " + e.getMessage()); 
237             }
238         }
239     }
240 
241 
242     /**
243      * Tallentaa jäsenistän tiedostoon.  
244      * Tiedoston muoto:
245      * <pre>
246      * Kelmien kerho
247      * 20
248      * ; kommenttirivi
249      * 2|Ankka Aku|121103-706Y|Ankkakuja 6|12345|ANKKALINNA|12-1234|||1996|50.0|30.0|Velkaa Roopelle
250      * 3|Ankka Tupu|121153-706Y|Ankkakuja 6|12345|ANKKALINNA|12-1234|||1996|50.0|30.0|Velkaa Roopelle
251      * </pre>
252      * @throws SailoException jos talletus epäonnistuu
253      */
254     public void talleta() throws SailoException {
255         if (!muutettu) return;
256 
257         File fbak = new File(getBakNimi());
258         File ftied = new File(getTiedostonNimi());
259         fbak.delete(); // if .. System.err.println("Ei voi tuhota");
260         ftied.renameTo(fbak); // if .. System.err.println("Ei voi nimetä");
261 
262         PrintWriter fo = Tiedosto.avaa_kirjoittamista_varten(ftied.getName());
263         if (fo == null) throw new SailoException("Tiedosto " + ftied.getName() + " ei aukea");
264         try {
265             fo.println(getKokoNimi());
266             fo.println(alkiot.length);
267             for (Jasen jasen : this) {
268                 fo.println(jasen.toString());
269             }
270             //} catch ( IOException e ) { // ei heitä poikkeusta
271             //  throw new SailoException("Tallettamisessa ongelmia: " + e.getMessage());
272         } finally {
273             fo.close();
274         }
275 
276         muutettu = false;
277     }
278     
279     
280     /**
281      * Palauttaa Kerhon koko nimen
282      * @return Kerhon koko nimi merkkijononna
283      */
284     public String getKokoNimi() {
285         return kokoNimi;
286     }
287 
288 
289      /**
290      * Palauttaa kerhon jäsenten lukumäärän
291      * @return jäsenten lukumäärä
292      */
293     public int getLkm() {
294         return lkm;
295     }
296 
297 
298     /**
299      * Palauttaa tiedoston nimen, jota käytetään tallennukseen
300      * @return tallennustiedoston nimi
301      */
302     public String getTiedostonPerusNimi() {
303         return tiedostonPerusNimi;
304     }
305 
306 
307     /**
308      * Asettaa tiedoston perusnimen ilan tarkenninta
309      * @param tied tallennustiedoston perusnimi
310      */
311     public void setTiedostonPerusNimi(String tied) {
312         tiedostonPerusNimi = tied;
313     }
314 
315 
316     /**
317      * Palauttaa tiedoston nimen, jota käytetään tallennukseen
318      * @return tallennustiedoston nimi
319      */
320     public String getTiedostonNimi() {
321         return getTiedostonPerusNimi() + ".dat";
322     }
323 
324 
325     /**
326      * Palauttaa varakopiotiedoston nimen
327      * @return varakopiotiedoston nimi
328      */
329     public String getBakNimi() {
330         return tiedostonPerusNimi + ".bak";
331     }
332 
333 
334     /**
335      * Luokka jäsenten iteroimiseksi.
336      * @example
337      * <pre name="test">
338      * #THROWS SailoException 
339      * #PACKAGEIMPORT
340      * #import java.util.*;
341      * 
342      * Jasenet jasenet = new Jasenet();
343      * Jasen aku1 = new Jasen(), aku2 = new Jasen();
344      * aku1.rekisteroi(); aku2.rekisteroi();
345      *
346      * jasenet.lisaa(aku1); 
347      * jasenet.lisaa(aku2); 
348      * jasenet.lisaa(aku1); 
349      * 
350      * StringBuffer ids = new StringBuffer(30);
351      * for (Jasen jasen:jasenet)   // Kokeillaan for-silmukan toimintaa
352      *   ids.append(" "+jasen.getTunnusNro());           
353      * 
354      * String tulos = " " + aku1.getTunnusNro() + " " + aku2.getTunnusNro() + " " + aku1.getTunnusNro();
355      * 
356      * ids.toString() === tulos; 
357      * 
358      * ids = new StringBuffer(30);
359      * for (Iterator<Jasen>  i=jasenet.iterator(); i.hasNext(); ) { // ja iteraattorin toimintaa
360      *   Jasen jasen = i.next();
361      *   ids.append(" "+jasen.getTunnusNro());           
362      * }
363      * 
364      * ids.toString() === tulos;
365      * 
366      * Iterator<Jasen>  i=jasenet.iterator();
367      * i.next() == aku1  === true;
368      * i.next() == aku2  === true;
369      * i.next() == aku1  === true;
370      * 
371      * i.next();  #THROWS NoSuchElementException
372      *  
373      * </pre>
374      */
375     public class JasenetIterator implements Iterator<Jasen> {
376         private int kohdalla = -1;
377 
378 
379         /**
380          * Onko olemassa vielä seuraavaa jäsentä
381          * @see java.util.Iterator#hasNext()
382          * @return true jos on vielä jäseniä
383          */
384         @Override
385         public boolean hasNext() {
386             //       if ( kohdalla + 1 >= lkm )  return false;
387             //       return true;
388             return kohdalla + 1 < getLkm();
389         }
390 
391 
392         /**
393          * Annetaan seuraava jäsen
394          * @return seuraava jäsen
395          * @throws NoSuchElementException jos seuraava alkiota ei enää ole
396          * @see java.util.Iterator#next()
397          */
398         @Override
399         public Jasen next() throws NoSuchElementException {
400             if (!hasNext()) throw new NoSuchElementException("Ei oo");
401             kohdalla++;
402             return anna(kohdalla);
403         }
404 
405 
406         /**
407          * Tuhoamista ei ole toteutettu
408          * @throws UnsupportedOperationException aina
409          * @see java.util.Iterator#remove()
410          */
411         @Override
412         public void remove() throws UnsupportedOperationException {
413             throw new UnsupportedOperationException("Me ei poisteta");
414         }
415     }
416 
417 
418     /**
419      * Palautetaan iteraattori jäsenistään.
420      * @return jäsen iteraattori
421      */
422     @Override
423     public Iterator<Jasen> iterator() {
424         return new JasenetIterator();
425     }
426 
427 
428     /**
429      * Palauttaa "taulukossa" hakuehtoon vastaavien jäsenten viitteet
430      * @param hakuehto hakuehto
431      * @param k etsittävän kentän indeksi 
432      * @return tietorakenteen löytyneistä jäsenistä
433      * @example
434      * <pre name="test">
435      * #THROWS SailoException 
436      *   Jasenet jasenet = new Jasenet();
437      *   Jasen jasen1 = new Jasen(); jasen1.parse("1|Ankka Aku|030201-115H|Ankkakuja 6|");
438      *   Jasen jasen2 = new Jasen(); jasen2.parse("2|Ankka Tupu||030552-123B|");
439      *   Jasen jasen3 = new Jasen(); jasen3.parse("3|Susi Sepe|121237-121V||131313|Perämetsä");
440      *   Jasen jasen4 = new Jasen(); jasen4.parse("4|Ankka Iines|030245-115V|Ankkakuja 9");
441      *   Jasen jasen5 = new Jasen(); jasen5.parse("5|Ankka Roope|091007-408U|Ankkakuja 12");
442      *   jasenet.lisaa(jasen1); jasenet.lisaa(jasen2); jasenet.lisaa(jasen3); jasenet.lisaa(jasen4); jasenet.lisaa(jasen5);
443      *   List<Jasen> loytyneet;
444      *   loytyneet = (List<Jasen>)jasenet.etsi("*s*",1);
445      *   loytyneet.size() === 2;
446      *   loytyneet.get(0) == jasen4 === true;
447      *   loytyneet.get(1) == jasen3 === true;
448      *   
449      *   loytyneet = (List<Jasen>)jasenet.etsi("*7-*",2);
450      *   loytyneet.size() === 2;
451      *   loytyneet.get(0) == jasen5 === true;
452      *   loytyneet.get(1) == jasen3 === true;
453      * </pre>
454      */
455     public Collection<Jasen> etsi(String hakuehto, int k) {
456         List<Jasen> loytyneet = new ArrayList<Jasen>();
457         for (Jasen jasen : this) {
458             //        if ( jasen.anna(k).toUpperCase().startsWith(hakuehto.toUpperCase()) ) loytyneet.add(jasen);  // NOPMD
459             if (WildChars.onkoSamat(jasen.anna(k), hakuehto)) loytyneet.add(jasen); // NOPMD
460         }
461         Collections.sort(loytyneet, new Jasen.Vertailija(k));
462         return loytyneet;
463     }
464 
465 
466     /**
467      * Etsii jäsenen id:n perusteella
468      * @param id tunnusnumero, jonka mukaan etsitään
469      * @return jäsen jolla etsittävä id tai null
470      * <pre name="test">
471      * #THROWS SailoException 
472      * Jasenet jasenet = new Jasenet(5);
473      * Jasen aku1 = new Jasen(), aku2 = new Jasen(), aku3 = new Jasen();
474      * aku1.rekisteroi(); aku2.rekisteroi(); aku3.rekisteroi();
475      * int id1 = aku1.getTunnusNro();
476      * jasenet.lisaa(aku1); jasenet.lisaa(aku2); jasenet.lisaa(aku3);
477      * jasenet.annaId(id1  ) == aku1 === true;
478      * jasenet.annaId(id1+1) == aku2 === true;
479      * jasenet.annaId(id1+2) == aku3 === true;
480      * </pre>
481      */
482     public Jasen annaId(int id) {
483         for (Jasen jasen : this) {
484             if (id == jasen.getTunnusNro()) return jasen;
485         }
486         return null;
487     }
488 
489 
490     /**
491      * Etsii jäsenen id:n perusteella
492      * @param id tunnusnumero, jonka mukaan etsitään
493      * @return löytyneen jäsenen indeksi tai -1 jos ei löydy
494      * <pre name="test">
495      * #THROWS SailoException 
496      * Jasenet jasenet = new Jasenet(5);
497      * Jasen aku1 = new Jasen(), aku2 = new Jasen(), aku3 = new Jasen();
498      * aku1.rekisteroi(); aku2.rekisteroi(); aku3.rekisteroi();
499      * int id1 = aku1.getTunnusNro();
500      * jasenet.lisaa(aku1); jasenet.lisaa(aku2); jasenet.lisaa(aku3);
501      * jasenet.etsiId(id1+1) === 1;
502      * jasenet.etsiId(id1+2) === 2;
503      * </pre>
504      */
505     public int etsiId(int id) {
506         for (int i = 0; i < lkm; i++)
507             if (id == alkiot[i].getTunnusNro()) return i;
508         return -1;
509     }
510 
511 
512     /**
513      * Testiohjelma jäsenistölle
514      * @param args ei käytössä
515      */
516     public static void main(String args[]) {
517         Jasenet jasenet = new Jasenet();
518 
519         Jasen aku = new Jasen(), aku2 = new Jasen();
520         aku.rekisteroi();
521         aku.vastaaAkuAnkka();
522         aku2.rekisteroi();
523         aku2.vastaaAkuAnkka();
524 
525         try {
526             jasenet.lisaa(aku);
527             jasenet.lisaa(aku2);
528 
529             System.out.println("============= Jäsenet testi =================");
530 
531             for (int i = 0; i < jasenet.getLkm(); i++) {
532                 Jasen jasen = jasenet.anna(i);
533                 System.out.println("Jäsen nro: " + i);
534                 jasen.tulosta(System.out);
535             }
536 
537         } catch (SailoException ex) {
538             System.out.println(ex.getMessage());
539         }
540     }
541 
542 }
543