1   package fi.jyu.mit.ohj2;
2   import java.io.*;
3   import java.util.*;
4   
5   /**
6    * Luokka avustusten tulostamiseksi.  Avustustiedoston muoto:
7    * <pre>
8    * [SISÄLLYS] - aina etsitään sisällystä tällä aihe- (topic) nimellä.
9    * Eka aihe - kerrotaan ekasta aiheesta
10   * Toka aihe - kerrotaan tokasta aiheesta
11   *
12   * [Eka aihe]
13   * Ekassa aiheessa voidaan kuvata mitä vaan ekaan aiheeseen liittyvää
14   * ja miksei muutakin.
15   *
16   * [Toka aihe] - aiheen otsikkorivillä saa olla kommentti
17   * Tokassa aiheessa sitten tokan aiheen sisällöstä.
18   * Tietysti aiheita voidaan kirjoittaa niin monta kuin halutaan
19   * eikä niiden kaikkien tarvitse olla sisällysluettelossa.
20   * Jos on kamalan pitkä aihe, jonka tulostus halutaan keskeyttää,
21   * voidaan avustustiedoston rivi aloittaa
22   * #
23   * risuaitamerkillä, joka pysäyttää tulostuksen.
24   * muutenkin tulostetaan vain korkeintaan 23 riviä kerrallaan.
25   * ; puolipisteellä alkava rivi on kommenttia ja sitä ei tulosteta
26   * </pre>
27   * Käyttöesimerkkejä:<br>
28   * Selaillaan koko avustusta.  Tulostetaan ensin sisällysluettelo
29   * <pre>
30   *   try {
31   *     Help h = new Help("kerho.hlp");
32   *     h.browse();
33   *   }
34   *   catch (IOException ioe) {
35   *     System.err.println(ioe);
36   *   }
37   * </pre>
38   * Tulostetaan valitun aiheen kohdalta:
39   * <pre>
40   *   h.printMatchingTopics("Li*");  // tulostaa kaikki Li-alkavat aiheet
41   *   h.printTopic("Lisäys");        // tulostaa aiheen Lisäys
42   *   h.helpTopic("Li*");            // tulostaa kaikki Li-alkavat aiheet
43   * </pre>
44   * @author Vesa Lappalainen, Markku Vire
45   * @version 1.0, 24.03.2003
46   */
47  public class Help {
48    private static final char COMMENT = ';';
49    private static final char TOPIC_START = '[';
50    private static final char TOPIC_END = ']';
51    private static final char HELP_PAUSE = '#';
52    private static final char QUIT_CHAR = 'q';
53    private static final int  DEFAULT_LINES = 23;
54    private static final String INDEX = "SISÄLLYS";
55  
56    private final Map<String,Collection<String>> topics = new HashMap<String,Collection<String>>();
57    private int lines = DEFAULT_LINES;
58    private int printedLineCounter = 0;
59    private final BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
60  
61    /**
62     * @return kuinka monta riviä tulostetaan korkeintaan pysähtymättä
63     */
64    public int getLines() { return lines; }
65  
66    /**
67     * @param lines pysähtymättä tulostettavien rivien lukumäärä
68     */
69    public void setLines(int lines) {
70      if ( lines > 0 )
71        this.lines = lines;
72    }
73  
74    /**
75     * @param input tutkittava rivi
76     * @return alkaako rivi lopetusmerkillä
77     */
78    private static boolean quit(String input) {
79      return firstIs(input, QUIT_CHAR);
80    }
81  
82    /**
83     * @param s tutkittava rivi
84     * @param c merkki jota etsitään
85     * @return onko c rivin s ensimmäinen merkki (true) vai ei (false)
86     */
87    private static boolean firstIs(String s, char c) {
88      if ( s == null || s.length() == 0 )
89        return false;
90  
91      return (Character.toUpperCase(s.charAt(0)) == Character.toUpperCase(c));
92    }
93  
94    /**
95     * Pyähtyy odottamaan reurnin panamista jos on tulostettuja rivejä
96     * edellisen pysähtymisen jälkeen.
97     * @return onko painettu postumista (true) vai ei (false)
98     */
99    private boolean helpStop() {
100     String input = null;
101 
102     if (printedLineCounter != 0) {
103       printedLineCounter = 0;
104       System.out.print("Jatka ENTER > ");
105       try {
106         input = in.readLine();
107       } catch (IOException ioe) {
108         System.out.println(ioe);
109       }
110     }
111 
112     return quit(input);
113   }
114 
115   /**
116    * Lisätään uusi aihe-otsikko avustukseen.
117    * Tämän jälkeen rivejä voi lisätä otsikon alle
118    * <pre>
119    * Collection topic = h.addTopic("Uusi aihe");
120    * topic.add("Eka rivi");
121    * topic.add("Toka rivi");
122    * </pre>
123    * Mikäli aihe on jo olemassa, palautetaan viite vanhaan aiheeseen.
124    * @param topic lisättävän aiheen otsikko
125    * @return tietorakenne, johon voi lisätä rivejä aiheen alle
126    */
127   @SuppressWarnings("unchecked")
128 public final Collection<String> addTopic(String topic) {
129     String uptopic = topic.toUpperCase(); // NOPMD
130     Collection<String> newtopic = topics.get(uptopic);
131     if ( newtopic != null ) return newtopic;
132 
133     newtopic = new ArrayList();
134     topics.put(uptopic,newtopic);
135     return newtopic;
136   }
137 
138   /**
139    * Lisätään yksi rivi avustukseen aiheen topic alle.  Mikäli aihetta
140    * ei vielä ole, se luodaan.
141    * @param topic lisättävän aiheen otsikko
142    * @param line lisättävä rivi
143    */
144   @SuppressWarnings("unchecked")
145   public void addLine(String topic, String line) {
146     Collection topicLines = addTopic(topic);
147     topicLines.add(line);
148   }
149 
150   /**
151    * Alustetaan tyhjä avustus, johon voi lisätä aiheita
152    * metodeilla: addTopic, addLine, readFile.
153    */
154   public Help()  { 
155       // ei tarvii tehdä mitään toistaiseksi
156   }
157 
158   /**
159    * Lukee avustuksen tiedostosta.  Voidaan kutsua useita kertoja
160    * jolloin voidaan yhdistää monia avustustiedostoja.
161    *
162    * Tiedot kerätään map-tauluun, jossa aiheiden mukaiset merkkijonot
163    * avaimina (isoksi muutettuna).  Aiheen alaiset rivit on
164    * sitten vektorina "avaimen oliona".
165    * @param fileName tiedosto,josta avustukset luetaan
166    * @throws IOException jos jokin menee pieleen tiedoston lukemisessa
167    */
168   @SuppressWarnings("unchecked")
169   public final void readFile(String fileName) throws IOException {
170     BufferedReader in = new BufferedReader(new FileReader(fileName));
171     Collection topic = null;
172     String line;
173     try {
174 
175       while ( (line = in.readLine()) != null ) {
176         int comment = line.indexOf(COMMENT);
177 
178         if ( comment >= 0 ) line = line.substring(0, comment);
179 
180         if ( firstIs(line, TOPIC_START) ) {
181           int topicEnd = line.indexOf(TOPIC_END);
182 
183           if (topicEnd >= 0) {
184             String topicName = line.substring(1, topicEnd);
185 
186             topic = addTopic(topicName);
187           }
188         } else {
189           if (topic != null)
190             topic.add(line);
191         }
192       }
193     } finally {
194       in.close();
195     }
196   }
197 
198   /**
199    * Alustaa avustuksen lukemalla avustukset tiedostosta.
200    * Tiedot kerätään map-tauluun, jossa aiheiden mukaiset merkkijonot
201    * avaimina (isoksi muutettuna).  Aiheen alaiset rivit on
202    * sitten vektorina "avaimen oliona".
203    * @param fileName tiedosto,josta avustukset luetaan
204    * @throws IOException jos jokin menee pieleen tiedoston lukemisessa
205    */
206   public Help(String fileName) throws IOException {
207     readFile(fileName);
208   }
209 
210   /**
211    * Tulostaa seuraavan rivin Help-tekstiä.  Jos rivimäärä ylittyy,
212    * niin pysähtyy.
213    * @param text tulostettava rivi
214    * @return painettiinko poistumista (true) vai ei (false) tulostuksen aikana
215    */
216   private boolean helpPrint(String text) {
217     if ( firstIs(text, HELP_PAUSE) ) return helpStop();
218 
219     System.out.println(text);
220     printedLineCounter++;
221 
222     if ( printedLineCounter >= lines ) return helpStop();
223 
224     return false;
225   }
226 
227   /**
228    * Tulostaa valitun lohkon avustuksesta.
229    * @param topic tulostettava lohko.  Ei saa sisältää jokereita
230    * @return painettiinko poistumista (true) vai ei (false) tulostuksen aikana
231    */
232   public boolean printTopic(String topic) {
233     Collection<String> topicText = topics.get(topic.toUpperCase()); // NOPMD
234 
235     if ( topicText == null )
236       return helpPrint("Aihetta " + topic + " ei löydy");
237 
238     for (Iterator<String> i = topicText.iterator(); i.hasNext(); )
239       if ( helpPrint(i.next()) )
240         return true;
241 
242     return false;
243   }
244 
245   /**
246    * Tulostaa ne avustuksen lohkot jotka täsmäävät hakuehtoon.
247    * @param topic mahdolisesti jokereita * ja ? sisältävä ehto
248    * @return painettiinko poistumista (true) vai ei (false) tulostuksen aikana
249    */
250   public boolean printMatchingTopics(String topic) {
251     printedLineCounter = 0;
252 
253     if ( WildChars.containsWildChars(topic) ) {
254       for ( Iterator<String> i = topics.keySet().iterator(); i.hasNext() ; ) {
255         String s = i.next();
256         if ( WildChars.onkoSamat(s, topic) && printTopic(s) )
257           return true;
258       }
259       return false;
260     }
261     return printTopic(topic);
262   }
263 
264   /**
265    * Selailee avustusta valitun lohkon kohdalta.  Jos lohko == null
266    * niin näytetään kohta [SISÄLLYS]
267    * @param topic tulostettava avustuksen kohta
268    */
269   public void browse(String topic)  {
270     String newtopic = topic;  
271     while (true) {
272       try {
273         if (newtopic == null) newtopic = INDEX;
274 
275         if ( printMatchingTopics(newtopic) ) return;
276 
277         System.out.print("Valitse aihe (voi olla myös *) > ");
278         newtopic = in.readLine();
279         if ( newtopic.length() == 0 ) break;
280         if ( quit(newtopic) ) break;
281       }
282       catch (IOException ioe) {
283         System.out.println(ioe);
284       }
285     }
286   }
287 
288   /**
289    * Selailee avustusta aloittaen kohdasta [SISÄLLYS]
290    */
291   public void browse()  { browse(null); }
292 
293   /**
294    * Tulostaa topic:in mukaisen lohkon avustuksesta.
295    * @param topic tulostetavan lohkon otsikko tai null jolloin selailee avustusta
296    */
297   public void helpTopic(String topic)  {
298     if ( topic == null ) browse();
299     else printMatchingTopics(topic);
300   }
301 
302 
303   /**
304    * Testataa Help-luokkaa
305    * @param args ei käytössä
306    */
307   public static void main(String[] args) {
308     try {
309       Help h = new Help("kerho.txt");
310       h.helpPrint("Terve!");
311       h.helpTopic("Lisäys");
312       h.helpPrint("#");
313       h.browse();
314       h.addLine("Uusi otsikko","Eka rivi");
315       h.addLine("Uusi otsikko","Toka rivi");
316       h.printMatchingTopics("Uusi*");
317     }
318     catch (IOException ioe) {
319       System.err.println(ioe);
320     }
321   }
322 }
323