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 consoleIn = new BufferedReader(new InputStreamReader(System.in));
60    
61    private PrintStream out = System.out;
62    private boolean stdout = true;
63  
64    /**
65     * @return kuinka monta riviä tulostetaan korkeintaan pysähtymättä
66     */
67    public int getLines() { return lines; }
68  
69    /**
70     * @param lines pysähtymättä tulostettavien rivien lukumäärä
71     */
72    public void setLines(int lines) {
73      if ( lines > 0 )
74        this.lines = lines;
75    }
76  
77    /**
78     * @param input tutkittava rivi
79     * @return alkaako rivi lopetusmerkillä
80     */
81    private static boolean quit(String input) {
82      return firstIs(input, QUIT_CHAR);
83    }
84  
85    /**
86     * @param s tutkittava rivi
87     * @param c merkki jota etsitään
88     * @return onko c rivin s ensimmäinen merkki (true) vai ei (false)
89     */
90    private static boolean firstIs(String s, char c) {
91      if ( s == null || s.length() == 0 )
92        return false;
93  
94      return (Character.toUpperCase(s.charAt(0)) == Character.toUpperCase(c));
95    }
96  
97    /**
98     * Pysähtyy odottamaan returnin panamista jos on tulostettuja rivejä
99     * edellisen pysähtymisen jälkeen.
100    * @return onko painettu postumista (true) vai ei (false)
101    */
102   private boolean helpStop() {
103     if ( !stdout ) return false;  
104     String input = null;
105 
106     if (printedLineCounter != 0) {
107       printedLineCounter = 0;
108       System.out.print("Jatka ENTER > ");
109       try {
110         input = consoleIn.readLine();
111       } catch (IOException ioe) {
112         System.out.println(ioe);
113       }
114     }
115 
116     return quit(input);
117   }
118 
119   /**
120    * Lisätään uusi aihe-otsikko avustukseen.
121    * Tämän jälkeen rivejä voi lisätä otsikon alle
122    * <pre>
123    * Collection topic = h.addTopic("Uusi aihe");
124    * topic.add("Eka rivi");
125    * topic.add("Toka rivi");
126    * </pre>
127    * Mikäli aihe on jo olemassa, palautetaan viite vanhaan aiheeseen.
128    * @param topic lisättävän aiheen otsikko
129    * @return tietorakenne, johon voi lisätä rivejä aiheen alle
130    */
131   public final Collection<String> addTopic(String topic) {
132     String uptopic = topic.toUpperCase(); // NOPMD
133     Collection<String> newtopic = topics.get(uptopic);
134     if ( newtopic != null ) return newtopic;
135 
136     newtopic = new ArrayList<String>();
137     topics.put(uptopic,newtopic);
138     return newtopic;
139   }
140 
141   /**
142    * Lisätään yksi rivi avustukseen aiheen topic alle.  Mikäli aihetta
143    * ei vielä ole, se luodaan.
144    * @param topic lisättävän aiheen otsikko
145    * @param line lisättävä rivi
146    */
147   public void addLine(String topic, String line) {
148     Collection<String> topicLines = addTopic(topic);
149     topicLines.add(line);
150   }
151 
152   /**
153    * Alustetaan tyhjä avustus, johon voi lisätä aiheita
154    * metodeilla: addTopic, addLine, readFile.
155    */
156   public Help()  { 
157       // ei tarvii tehdä mitään toistaiseksi
158   }
159   
160   
161   /**
162    * Asetetaan tulostusvirta toiseen paikkaan
163    * @param newout mihin tulostus tehdään?
164    */
165   public void setOut(PrintStream newout) {
166       out = newout;
167       stdout = false;
168   }
169   
170   
171 
172   /**
173    * Lukee avustuksen tiedostosta.  Voidaan kutsua useita kertoja
174    * jolloin voidaan yhdistää monia avustustiedostoja.
175    *
176    * Tiedot kerätään map-tauluun, jossa aiheiden mukaiset merkkijonot
177    * avaimina (isoksi muutettuna).  Aiheen alaiset rivit on
178    * sitten vektorina "avaimen oliona".
179    * @param fileName tiedosto,josta avustukset luetaan
180    * @throws IOException jos jokin menee pieleen tiedoston lukemisessa
181    */
182   public final void readFile(String fileName) throws IOException {
183     @SuppressWarnings("resource")
184     BufferedReader in = new BufferedReader(new FileReader(fileName));
185     Collection<String> topic = null;
186     String line;
187     try {
188 
189       while ( (line = in.readLine()) != null ) {
190         int comment = line.indexOf(COMMENT);
191 
192         if ( comment >= 0 ) line = line.substring(0, comment);
193 
194         if ( firstIs(line, TOPIC_START) ) {
195           int topicEnd = line.indexOf(TOPIC_END);
196 
197           if (topicEnd >= 0) {
198             String topicName = line.substring(1, topicEnd);
199 
200             topic = addTopic(topicName);
201           }
202         } else {
203           if (topic != null)
204             topic.add(line);
205         }
206       }
207     } finally {
208       in.close();
209     }
210   }
211 
212   /**
213    * Alustaa avustuksen lukemalla avustukset tiedostosta.
214    * Tiedot kerätään map-tauluun, jossa aiheiden mukaiset merkkijonot
215    * avaimina (isoksi muutettuna).  Aiheen alaiset rivit on
216    * sitten vektorina "avaimen oliona".
217    * @param fileName tiedosto,josta avustukset luetaan
218    * @throws IOException jos jokin menee pieleen tiedoston lukemisessa
219    */
220   public Help(String fileName) throws IOException {
221     readFile(fileName);
222   }
223 
224   /**
225    * Tulostaa seuraavan rivin Help-tekstiä.  Jos rivimäärä ylittyy,
226    * niin pysähtyy.
227    * @param text tulostettava rivi
228    * @return painettiinko poistumista (true) vai ei (false) tulostuksen aikana
229    */
230   private boolean helpPrint(String text) {
231     if ( firstIs(text, HELP_PAUSE) ) return helpStop();
232 
233     out.println(text);
234     printedLineCounter++;
235 
236     if ( printedLineCounter >= lines ) return helpStop();
237 
238     return false;
239   }
240 
241   /**
242    * Tulostaa valitun lohkon avustuksesta.
243    * @param topic tulostettava lohko.  Ei saa sisältää jokereita
244    * @return painettiinko poistumista (true) vai ei (false) tulostuksen aikana
245    */
246   public boolean printTopic(String topic) {
247     Collection<String> topicText = topics.get(topic.toUpperCase()); // NOPMD
248 
249     if ( topicText == null )
250       return helpPrint("Aihetta " + topic + " ei löydy");
251 
252     for (Iterator<String> i = topicText.iterator(); i.hasNext(); )
253       if ( helpPrint(i.next()) )
254         return true;
255 
256     return false;
257   }
258 
259   /**
260    * Tulostaa ne avustuksen lohkot jotka täsmäävät hakuehtoon.
261    * @param topic mahdolisesti jokereita * ja ? sisältävä ehto
262    * @return painettiinko poistumista (true) vai ei (false) tulostuksen aikana
263    */
264   public boolean printMatchingTopics(String topic) {
265     printedLineCounter = 0;
266 
267     if ( WildChars.containsWildChars(topic) ) {
268       for ( Iterator<String> i = topics.keySet().iterator(); i.hasNext() ; ) {
269         String s = i.next();
270         if ( WildChars.onkoSamat(s, topic) && printTopic(s) )
271           return true;
272       }
273       return false;
274     }
275     return printTopic(topic);
276   }
277 
278   /**
279    * Selailee avustusta valitun lohkon kohdalta.  Jos lohko == null
280    * niin näytetään kohta [SISÄLLYS]
281    * @param topic tulostettava avustuksen kohta
282    */
283   public void browse(String topic)  {
284     String newtopic = topic;  
285     while (true) {
286       try {
287         if (newtopic == null) newtopic = INDEX;
288 
289         if ( printMatchingTopics(newtopic) || !stdout ) return;
290 
291         System.out.print("Valitse aihe (voi olla myös *) > ");
292         newtopic = consoleIn.readLine();
293         if ( newtopic.length() == 0 ) break;
294         if ( quit(newtopic) ) break;
295       }
296       catch (IOException ioe) {
297         System.out.println(ioe);
298       }
299     }
300   }
301 
302   /**
303    * Selailee avustusta aloittaen kohdasta [SISÄLLYS]
304    */
305   public void browse()  { browse(null); }
306 
307   /**
308    * Tulostaa topic:in mukaisen lohkon avustuksesta.
309    * @param topic tulostetavan lohkon otsikko tai null jolloin selailee avustusta
310    */
311   public void helpTopic(String topic)  {
312     if ( topic == null ) browse();
313     else printMatchingTopics(topic);
314   }
315 
316 
317   /**
318    * Testataa Help-luokkaa
319    * @param args ei käytössä
320    */
321   public static void main(String[] args) {
322     try {
323       Help h = new Help(); //"kerho.txt");
324       h.helpPrint("Terve!");
325       h.helpTopic("Lisäys");
326       h.helpPrint("#");
327       h.browse();
328       h.addLine("Uusi otsikko","Eka rivi");
329       h.addLine("Uusi otsikko","Toka rivi");
330       h.printMatchingTopics("Uusi*");
331     }
332     catch (Exception ioe) {
333       System.err.println(ioe);
334     }
335   }
336 }
337