1   package kanta;
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.*;
8   
9   import kanta.Tietue;
10  import fi.jyu.mit.ohj2.Tiedosto;
11  import fi.jyu.mit.ohj2.WildChars;
12  
13  /**
14   * Rakenne tietueita varten. 
15   * Osaa lukea ja kirjoittaa tiedoston.
16   * Osaa poistaa ja etsiä tietyn viitteen tietueet
17   *
18   * @author Vesa Lappalainen
19   * @version 1.0, 22.02.2003
20   * @version 2.0, 24.03.2012 - geneerinen versio
21   * @param <TYPE> Minkä tyyppisiä tietueita talletetaan
22   * @example
23   * <pre name="testJAVA">
24   * public static class Harrastus extends PerusTietue {
25   *     private Kentta kentat[] = { 
26   *        new IntKentta("id"),
27   *        new IntKentta("jäsenId"),
28   *        new JonoKentta("ala"),
29   *        new IntKentta("aloitusvuosi"),
30   *        new IntKentta("h/vko")
31   *     };
32   *     private static int seuraavaNro = 1;
33   *     public Harrastus() { }
34   *     public Harrastus(int jnro) { ((IntKentta)(kentat[1])).setValue(jnro); }
35   *     @Override public int ekaKentta() { return 2; }
36   *     @Override public Kentta[] getKentat() { return kentat; }
37   *     @Override public int getSeuraavaNro() { return seuraavaNro; }
38   *     @Override protected void setSeuraavaNro(int i) { seuraavaNro = i; }
39   *     @Override protected void setKentat(Kentta[] kentat) { this.kentat = kentat; }
40   *     @Override public Harrastus clone() throws CloneNotSupportedException {return (Harrastus)super.clone(); }
41   *     public int getJasenNro() { return ((IntKentta)getKentta(1)).getValue(); }
42   *     public void vastaaPitsinNyplays(int nro) {
43   *       aseta(1,""+nro); aseta(2,"Pitsin nypläys"); aseta(3,"1952"); aseta(4,"30");
44   *     }
45   * }
46   */
47  public class PerusTietueet<TYPE extends Tietue> implements Tietueet<TYPE> {
48      private boolean                     muutettu           = false;
49      private String                      tiedostonPerusNimi = "";
50      private TYPE malli;
51      private String tark = ".dat";
52      private String baktark = ".bak";
53  
54      /** Taulukko tietueista */
55      private final List<TYPE> alkiot = new ArrayList<TYPE>();
56  
57  
58      /**
59       * Rakenteen alustaminen.
60       * @param malli malliolio, jonka kloonilla voi luoda uusia
61       */
62      public PerusTietueet(TYPE malli) {
63          this.malli = malli;
64      }
65  
66      
67      /**
68       * Rakenteen alustaminen.
69       * @param malli malliolio, jonka kloonilla voi luoda uusia
70       * @param tark tiedoston tarkennin
71       * @param baktark backuptiedoston tarkennin, jos null, ei tehdä
72       */
73      public PerusTietueet(TYPE malli,String tark, String baktark) {
74          this(malli);
75          this.tark = tark;
76          this.baktark = baktark;
77      }
78  
79      
80      /**
81       * Luodaan uusi talletettavan kaltainen olio.
82       * Tarvitaan tiedoston lukemisessa.
83       * @return uusi talletettavan tyyppinen alkio
84       */
85      @SuppressWarnings("unchecked")
86      @Override
87      public TYPE luoUusi() {
88          try {
89              Tietue uusi = malli.clone();
90              return (TYPE)uusi;
91          } catch (CloneNotSupportedException e) {
92              return null;
93          }
94      }
95      
96  
97      /**
98       * Lisää uuden tietueen tietorakenteeseen.  Ottaa tietueen omistukseensa.
99       * @param har lisättävä harrastus.  Huom tietorakenne muuttuu omistajaksi
100      */
101     @Override
102     public void lisaa(TYPE har) {
103         alkiot.add(har);
104         muutettu = true;
105     }
106 
107 
108     /**
109      * Korvaa tietueen tietorakenteessa.  Ottaa tietueen omistukseensa.
110      * Etsitään samalla tunnusnumerolla oleva tietue.  Jos ei löydy,
111      * niin lisätään uutena tietueena.
112      * @param tietue lisätäävän tietueen viite.  Huom tietorakenne muuttuu omistajaksi
113      * @example
114      * <pre name="test">
115      * #THROWS CloneNotSupportedException
116      * #PACKAGEIMPORT
117      * PerusTietueet<Harrastus> harrastukset = new PerusTietueet(new Harrastus());
118      * Harrastus har1 = new Harrastus(), har2 = new Harrastus();
119      * har1.rekisteroi(); har2.rekisteroi();
120      * harrastukset.getLkm() === 0;
121      * harrastukset.korvaaTaiLisaa(har1); harrastukset.getLkm() === 1;
122      * harrastukset.korvaaTaiLisaa(har2); harrastukset.getLkm() === 2;
123      * Harrastus har3 = har1.clone();
124      * har3.aseta(2,"kkk");
125      * Iterator<Harrastus> i2=harrastukset.iterator();
126      * i2.next() === har1;
127      * harrastukset.korvaaTaiLisaa(har3); harrastukset.getLkm() === 2;
128      * i2=harrastukset.iterator();
129      * Harrastus h = i2.next();
130      * h === har3;
131      * h == har3 === true;
132      * h == har1 === false;
133      * </pre>
134      */
135     @Override
136     public void korvaaTaiLisaa(TYPE tietue) {
137         int id = tietue.getTunnusNro();
138         for (int i = 0; i < getLkm(); i++) {
139             if (alkiot.get(i).getTunnusNro() == id) {
140                 alkiot.set(i, tietue);
141                 muutettu = true;
142                 return;
143             }
144         }
145         lisaa(tietue);
146     }
147     
148     
149     /**
150      * Poistaa valitun tietueen
151      * @param tietue poistettava tietue
152      * @return tosi jos löytyi poistettava tietue 
153      * @example
154      * <pre name="test">
155      * #THROWS SailoException 
156      * #import java.io.File;
157      *  PerusTietueet<Harrastus> harrasteet = new PerusTietueet(new Harrastus());
158      *  Harrastus pitsi21 = new Harrastus(); pitsi21.vastaaPitsinNyplays(2);
159      *  Harrastus pitsi11 = new Harrastus(); pitsi11.vastaaPitsinNyplays(1);
160      *  Harrastus pitsi22 = new Harrastus(); pitsi22.vastaaPitsinNyplays(2); 
161      *  Harrastus pitsi12 = new Harrastus(); pitsi12.vastaaPitsinNyplays(1); 
162      *  Harrastus pitsi23 = new Harrastus(); pitsi23.vastaaPitsinNyplays(2); 
163      *  harrasteet.lisaa(pitsi21);
164      *  harrasteet.lisaa(pitsi11);
165      *  harrasteet.lisaa(pitsi22);
166      *  harrasteet.lisaa(pitsi12);
167      *  harrasteet.poista(pitsi23) === false ; harrasteet.getLkm() === 4;
168      *  harrasteet.poista(pitsi11) === true;   harrasteet.getLkm() === 3;
169      *  List<Harrastus> h = harrasteet.annaTietueet(1,1);
170      *  h.size() === 1; 
171      *  h.get(0) === pitsi12;
172      * </pre>
173      */
174     @Override
175     public boolean poista(TYPE tietue) {
176         boolean ret = alkiot.remove(tietue);
177         if (ret) muutettu = true;
178         return ret;
179     }
180 
181 
182     /**
183      * Poistaa tietueen jolla on valittu tunnusnumero 
184      * @param id poistettavan tietueen tunnusnumero
185      * @return 1 jos poistettiin, 0 jos ei löydy
186      * @example
187      * <pre name="test">
188      * PerusTietueet<Harrastus> harrastukset = new PerusTietueet(new Harrastus());
189      * Harrastus har1 = new Harrastus(), har2 = new Harrastus(), har3 = new Harrastus();
190      * har1.rekisteroi(); har2.rekisteroi(); har3.rekisteroi();
191      * int id1 = har1.getTunnusNro();
192      * harrastukset.lisaa(har1); harrastukset.lisaa(har2); harrastukset.lisaa(har3);
193      * harrastukset.poista(id1+1) === 1;
194      * harrastukset.poista(id1+1) === 0; harrastukset.getLkm() === 2;
195      * harrastukset.poista(id1) === 1; harrastukset.getLkm() === 1;
196      * harrastukset.poista(id1+3) === 0; harrastukset.getLkm() === 1;
197      * </pre>
198      */
199     @Override
200     public int poista(int id) {
201         return poista(id,0);
202     }
203 
204 
205     /**
206      * Poistaa kaikki tietyn viitteen tietueet PerusTietueet
207      * @param viiteNro viite siihen, mihin liittyvät tietueet poistetaan
208      * @return montako poistettiin 
209      * @example
210      * <pre name="test">
211      * #THROWS SailoException 
212      * #import java.io.File;
213      *  PerusTietueet<Harrastus> harrasteet = new PerusTietueet(new Harrastus());
214      *  Harrastus pitsi21 = new Harrastus(); pitsi21.vastaaPitsinNyplays(2);
215      *  Harrastus pitsi11 = new Harrastus(); pitsi11.vastaaPitsinNyplays(1);
216      *  Harrastus pitsi22 = new Harrastus(); pitsi22.vastaaPitsinNyplays(2); 
217      *  Harrastus pitsi12 = new Harrastus(); pitsi12.vastaaPitsinNyplays(1); 
218      *  Harrastus pitsi23 = new Harrastus(); pitsi23.vastaaPitsinNyplays(2); 
219      *  harrasteet.lisaa(pitsi21);
220      *  harrasteet.lisaa(pitsi11);
221      *  harrasteet.lisaa(pitsi22);
222      *  harrasteet.lisaa(pitsi12);
223      *  harrasteet.lisaa(pitsi23);
224      *  harrasteet.poista(2,1) === 3;  harrasteet.getLkm() === 2;
225      *  harrasteet.poista(3,1) === 0;  harrasteet.getLkm() === 2;
226      *  List<Harrastus> h = harrasteet.annaTietueet(2,1);
227      *  h.size() === 0; 
228      *  h = harrasteet.annaTietueet(1,1);
229      *  h.get(0) === pitsi11;
230      *  h.get(1) === pitsi12;
231      * </pre>
232      */
233     @Override
234     public int poista(int viiteNro, int k) {
235         int n = 0;
236         for (Iterator<TYPE> it = alkiot.iterator(); it.hasNext();) {
237             TYPE tietue = it.next();
238             if ( tietue.annaInt(k) == viiteNro) {
239                 it.remove();
240                 n++;
241             }
242         }
243         if (n > 0) muutettu = true;
244         return n;
245     }
246 
247 
248     /**
249      * Lukee PerusTietueet tiedostosta.
250      * @param tied tiedoston nimen alkuosa
251      * @throws SailoException jos lukeminen epäonnistuu
252      * 
253      * @example
254      * <pre name="test">
255      * #THROWS SailoException 
256      * #import java.io.File;
257      *  PerusTietueet<Harrastus> harrasteet = new PerusTietueet(new Harrastus());
258      *  Harrastus pitsi21 = new Harrastus(); pitsi21.vastaaPitsinNyplays(2);
259      *  Harrastus pitsi11 = new Harrastus(); pitsi11.vastaaPitsinNyplays(1);
260      *  Harrastus pitsi22 = new Harrastus(); pitsi22.vastaaPitsinNyplays(2); 
261      *  Harrastus pitsi12 = new Harrastus(); pitsi12.vastaaPitsinNyplays(1); 
262      *  Harrastus pitsi23 = new Harrastus(); pitsi23.vastaaPitsinNyplays(2); 
263      *  String tiedNimi = "testikelmit";
264      *  File ftied = new File(tiedNimi+".dat");
265      *  ftied.delete();
266      *  harrasteet.lueTiedostosta(tiedNimi); #THROWS SailoException
267      *  harrasteet.lisaa(pitsi21);
268      *  harrasteet.lisaa(pitsi11);
269      *  harrasteet.lisaa(pitsi22);
270      *  harrasteet.lisaa(pitsi12);
271      *  harrasteet.lisaa(pitsi23);
272      *  harrasteet.talleta();
273      *  harrasteet = new PerusTietueet(new Harrastus());
274      *  harrasteet.lueTiedostosta(tiedNimi);
275      *  Iterator<Harrastus> i = harrasteet.iterator();
276      *  i.next().toString() === pitsi21.toString();
277      *  i.next().toString() === pitsi11.toString();
278      *  i.next().toString() === pitsi22.toString();
279      *  i.next().toString() === pitsi12.toString();
280      *  i.next().toString() === pitsi23.toString();
281      *  i.hasNext() === false;
282      *  harrasteet.lisaa(pitsi23);
283      *  harrasteet.talleta();
284      *  ftied.delete() === true;
285      *  File fbak = new File(tiedNimi+".bak");
286      *  fbak.delete() === true;
287      * </pre>
288      */
289     @Override
290     public void lueTiedostosta(String tied) throws SailoException {
291         muutettu = true;
292         setTiedostonPerusNimi(tied);
293         BufferedReader fi = Tiedosto.avaa_lukemista_varten(getTiedostonNimi());
294         if (fi == null) throw new SailoException("Tiedosto " + getTiedostonNimi() + " ei aukea");
295 
296         String rivi;
297         try {
298             lueAlkurivit(fi);
299             while ((rivi = fi.readLine()) != null) { 
300                 rivi = rivi.trim();
301                 if ("".equals(rivi) || rivi.charAt(0) == ';') continue;
302                 TYPE tietue = luoUusi();
303                 /// TYPE tietue = new TYPE(); /// nyyh 
304                 tietue.parse(rivi); // voisi olla virhekäsittely
305                 lisaa(tietue);
306             }
307             muutettu = false;
308 
309         } catch (IOException e) {
310             throw new SailoException("Ongelmia tiedoston kanssa: " + e.getMessage());
311         } finally {
312             try {
313                 fi.close();
314             } catch (IOException e) {
315                 throw new SailoException("Tiedoston sulkeminen ei onnistu: " + e.getMessage()); 
316             }
317         }
318     }
319 
320 
321     /**
322      * Lukee rivit tiedoston alusta
323      * @param fi virta josta luetaan
324      * @throws IOException jos jokin menee vikaan
325      * @throws SailoException jos tieto virheellistä
326      */
327     protected void lueAlkurivit(BufferedReader fi) throws IOException, SailoException {
328        //        
329     }
330 
331 
332     /**
333      * Tiedoston alkuun tulevat rivit
334      * @param fo virta johon talletetaan
335      */
336     protected void teeAlkutalletus(PrintWriter fo) {
337         //
338     }
339 
340 
341     /**
342      * Tallentaa PerusTietueet tiedostoon.
343      * @throws SailoException jos talletus epäonnistuu
344      */
345     @Override
346     public void talleta() throws SailoException {
347         if (!muutettu) return;
348 
349         File ftied = new File(getTiedostonNimi());
350         if ( baktark != null ) {
351             File fbak = new File(getBakNimi());
352             fbak.delete(); //  if ... System.err.println("Ei voi tuhota");
353             ftied.renameTo(fbak); //  if ... System.err.println("Ei voi nimetä");
354         }
355 
356         PrintWriter fo = Tiedosto.avaa_kirjoittamista_varten(ftied.getName());
357         if (fo == null) throw new SailoException("Tiedosto " + ftied.getName() + " ei aukea");
358         
359         try {
360             teeAlkutalletus(fo);
361             for (TYPE tietue : this) {
362                 fo.println(tietue.toString());
363             }
364         } finally {
365             fo.close();
366         }
367 
368         muutettu = false;
369     }
370 
371 
372     /**
373      * Palauttaa tietueiden lukumäärän
374      * @return harrastusten lukumäärä
375      */
376     @Override
377     public int getLkm() {
378         return alkiot.size();
379     }
380     
381     
382     /**
383      * Asettaa tiedoston perusnimen ilman tarkenninta
384      * @param tied tallennustiedoston perusnimi
385      */
386     @Override
387     public void setTiedostonPerusNimi(String tied) {
388         tiedostonPerusNimi = tied;
389     }
390 
391     
392     /**
393      * Palauttaa tiedoston nimen, jota käytetään tallennukseen
394      * @return tallennustiedoston nimi
395      */
396     @Override
397     public String getTiedostonPerusNimi() {
398         return tiedostonPerusNimi;
399     }
400 
401 
402     /**
403      * Palauttaa tiedoston nimen, jota käytetään tallennukseen
404      * @return tallennustiedoston nimi
405      */
406     @Override
407     public String getTiedostonNimi() {
408         return getTiedostonPerusNimi() + getTark();
409     }
410 
411 
412     /**
413      * Palauttaa varakopiotiedoston nimen
414      * @return varakopiotiedoston nimi
415      */
416     @Override
417     public String getBakNimi() {
418         return getTiedostonPerusNimi() + getBaktark();
419     }
420 
421 
422     /**
423      * Iteraattori kaikkien harrastusten läpikäymiseen
424      * @return harrastusiteraattori
425      * 
426      * @example
427      * <pre name="test">
428      * #PACKAGEIMPORT
429      * #import java.util.*;
430      * 
431      *  PerusTietueet<Harrastus> harrasteet = new PerusTietueet(new Harrastus());
432      *  Harrastus pitsi21 = new Harrastus(2); harrasteet.lisaa(pitsi21);
433      *  Harrastus pitsi11 = new Harrastus(1); harrasteet.lisaa(pitsi11);
434      *  Harrastus pitsi22 = new Harrastus(2); harrasteet.lisaa(pitsi22);
435      *  Harrastus pitsi12 = new Harrastus(1); harrasteet.lisaa(pitsi12);
436      *  Harrastus pitsi23 = new Harrastus(2); harrasteet.lisaa(pitsi23);
437      * 
438      *  Iterator<Harrastus> i2=harrasteet.iterator();
439      *  i2.next() === pitsi21;
440      *  i2.next() === pitsi11;
441      *  i2.next() === pitsi22;
442      *  i2.next() === pitsi12;
443      *  i2.next() === pitsi23;
444      *  i2.next() === pitsi12;  #THROWS NoSuchElementException  
445      *  
446      *  int n = 0;
447      *  int jnrot[] = {2,1,2,1,2};
448      *  
449      *  for ( Harrastus har:harrasteet ) { 
450      *    har.getJasenNro() === jnrot[n]; n++;  
451      *  }
452      *  
453      *  n === 5;
454      *  
455      * </pre>
456      */
457     @Override
458     public Iterator<TYPE> iterator() {
459         return alkiot.iterator();
460     }
461 
462 
463     /**
464      * Haetaan kaikki tietyn viitteen tietueet
465      * @param viiteNro etsittävän viitteen tunnusnumero jolle tietueita haetaan
466      * @param k etsittävän kentän numero
467      * @return tietorakenne jossa viiteet löydetteyihin tietueisiin
468      * @example
469      * <pre name="test">
470      * #import java.util.*;
471      * 
472      *  PerusTietueet<Harrastus> harrasteet = new PerusTietueet(new Harrastus());
473      *  Harrastus pitsi21 = new Harrastus(2); harrasteet.lisaa(pitsi21);
474      *  Harrastus pitsi11 = new Harrastus(1); harrasteet.lisaa(pitsi11);
475      *  Harrastus pitsi22 = new Harrastus(2); harrasteet.lisaa(pitsi22);
476      *  Harrastus pitsi12 = new Harrastus(1); harrasteet.lisaa(pitsi12);
477      *  Harrastus pitsi23 = new Harrastus(2); harrasteet.lisaa(pitsi23);
478      *  Harrastus pitsi51 = new Harrastus(5); harrasteet.lisaa(pitsi51);
479      *  
480      *  List<Harrastus> loytyneet;
481      *  loytyneet = harrasteet.annaTietueet(3,1);
482      *  loytyneet.size() === 0; 
483      *  loytyneet = harrasteet.annaTietueet(1,1);
484      *  loytyneet.size() === 2; 
485      *  loytyneet.get(0) == pitsi11 === true;
486      *  loytyneet.get(1) == pitsi12 === true;
487      *  loytyneet = harrasteet.annaTietueet(5,1);
488      *  loytyneet.size() === 1; 
489      *  loytyneet.get(0) == pitsi51 === true;
490      * </pre> 
491      */
492     @Override
493     public List<TYPE> annaTietueet(int viiteNro, int k) {
494         List<TYPE> loydetyt = new ArrayList<TYPE>();
495         for (TYPE tietue : this)
496             if ( tietue.annaInt(k) == viiteNro) loydetyt.add(tietue);
497         return loydetyt;
498     }
499 
500 
501     /**
502      * Palauttaa "taulukossa" hakuehtoon vastaavien tietueiden viitteet
503      * @param hakuehto hakuehto
504      * @param k etsittävän kentän indeksi 
505      * @return tietorakenteen löytyneistä tietueista
506      * @example
507      * <pre name="test">
508      * #THROWS SailoException 
509      *  PerusTietueet<Harrastus> harrastukset = new PerusTietueet(new Harrastus());
510      *  Harrastus har1 = new Harrastus(); har1.parse("1|2|Possujen jahtaaminen|1940|22");
511      *  Harrastus har2 = new Harrastus(); har2.parse("2|1|Poikien hoitaminen|1953|1");
512      *  Harrastus har3 = new Harrastus(); har3.parse("3|2|Kelmien kerho|1948|20");
513      *  Harrastus har4 = new Harrastus(); har4.parse("4|1|Kalastus|1947|20");
514      *  Harrastus har5 = new Harrastus(); har5.parse("5|3|Viulu|1952|15");
515      *  harrastukset.lisaa(har1); harrastukset.lisaa(har2); harrastukset.lisaa(har3); 
516      *  harrastukset.lisaa(har4); harrastukset.lisaa(har5);
517      *  List<Harrastus> loytyneet;
518      *  loytyneet = harrastukset.etsi("*s*",2);
519      *  loytyneet.size() === 2;
520      *  loytyneet.get(0) == har4 === true;
521      *  loytyneet.get(1) == har1 === true;
522      *   
523      *  loytyneet = harrastukset.etsi("*5*",3);
524      *  loytyneet.size() === 2;
525      *  loytyneet.get(0) == har5 === true;
526      *  loytyneet.get(1) == har2 === true;
527      * </pre>
528      */
529     @Override
530     public List<TYPE> etsi(String hakuehto, int k) {
531         List<TYPE> loytyneet = new ArrayList<TYPE>();
532         for (TYPE jasen : this) {
533             if (WildChars.onkoSamat(jasen.anna(k), hakuehto)) loytyneet.add(jasen); 
534         }
535         Collections.sort(loytyneet, new TietueVertailija(k));
536         return loytyneet;
537     }
538     
539     
540     /**
541      * Etsii tietueen id:n perusteella
542      * @param id tunnusnumero, jonka mukaan etsitään
543      * @return tietue jolla etsittävä id tai null
544      * @example
545      * <pre name="test">
546      *  PerusTietueet<Harrastus> harrastukset = new PerusTietueet(new Harrastus());
547      *  Harrastus har1 = new Harrastus(); har1.parse("1|2|Possujen jahtaaminen|1940|22");
548      *  Harrastus har2 = new Harrastus(); har2.parse("2|1|Poikien hoitaminen|1953|1");
549      *  Harrastus har3 = new Harrastus(); har3.parse("3|2|Kelmien kerho|1948|20");
550      *  harrastukset.lisaa(har1); harrastukset.lisaa(har2); harrastukset.lisaa(har3); 
551      *  harrastukset.annaId(1) == har1 === true;
552      *  harrastukset.annaId(2) == har2 === true;
553      *  harrastukset.annaId(3) == har3 === true;
554      * </pre>
555      */
556     @Override
557     public TYPE annaId(int id) {
558         for (TYPE tietue : this) {
559             if (id == tietue.getTunnusNro()) return tietue;
560         }
561         return null;
562     }
563     
564     
565     /**
566      * Laitetaan muutos, jolloin pakotetaan tallentamaan.  
567      */
568     @Override
569     public void setMuutos() {
570         muutettu = true;
571     }
572 
573 
574     /**
575      * @return tiedoston tarkennin
576      */
577     public String getTark() {
578         return tark;
579     }
580 
581 
582     /**
583      * @param tark tiedoston tarkennin
584      */
585     public void setTark(String tark) {
586         this.tark = tark;
587     }
588 
589 
590     /**
591      * @return backup tiedostojen tarkennin
592      */
593     public String getBaktark() {
594         return baktark;
595     }
596 
597 
598     /**
599      * @param baktark backup tiedostojen tarkennin
600      */
601     public void setBaktark(String baktark) {
602         this.baktark = baktark;
603     }
604 }
605