001package fi.jyu.mit.ohj2;
002import java.io.*;
003import java.util.*;
004
005/**
006 * Luokka avustusten tulostamiseksi.  Avustustiedoston muoto:
007 * <pre>
008 * [SISÄLLYS] - aina etsitään sisällystä tällä aihe- (topic) nimellä.
009 * Eka aihe - kerrotaan ekasta aiheesta
010 * Toka aihe - kerrotaan tokasta aiheesta
011 *
012 * [Eka aihe]
013 * Ekassa aiheessa voidaan kuvata mitä vaan ekaan aiheeseen liittyvää
014 * ja miksei muutakin.
015 *
016 * [Toka aihe] - aiheen otsikkorivillä saa olla kommentti
017 * Tokassa aiheessa sitten tokan aiheen sisällöstä.
018 * Tietysti aiheita voidaan kirjoittaa niin monta kuin halutaan
019 * eikä niiden kaikkien tarvitse olla sisällysluettelossa.
020 * Jos on kamalan pitkä aihe, jonka tulostus halutaan keskeyttää,
021 * voidaan avustustiedoston rivi aloittaa
022 * #
023 * risuaitamerkillä, joka pysäyttää tulostuksen.
024 * muutenkin tulostetaan vain korkeintaan 23 riviä kerrallaan.
025 * ; puolipisteellä alkava rivi on kommenttia ja sitä ei tulosteta
026 * </pre>
027 * Käyttöesimerkkejä:<br>
028 * Selaillaan koko avustusta.  Tulostetaan ensin sisällysluettelo
029 * <pre>
030 *   try {
031 *     Help h = new Help("kerho.hlp");
032 *     h.browse();
033 *   }
034 *   catch (IOException ioe) {
035 *     System.err.println(ioe);
036 *   }
037 * </pre>
038 * Tulostetaan valitun aiheen kohdalta:
039 * <pre>
040 *   h.printMatchingTopics("Li*");  // tulostaa kaikki Li-alkavat aiheet
041 *   h.printTopic("Lisäys");        // tulostaa aiheen Lisäys
042 *   h.helpTopic("Li*");            // tulostaa kaikki Li-alkavat aiheet
043 * </pre>
044 * @author Vesa Lappalainen, Markku Vire
045 * @version 1.0, 24.03.2003
046 */
047public class Help {
048  private static final char COMMENT = ';';
049  private static final char TOPIC_START = '[';
050  private static final char TOPIC_END = ']';
051  private static final char HELP_PAUSE = '#';
052  private static final char QUIT_CHAR = 'q';
053  private static final int  DEFAULT_LINES = 23;
054  private static final String INDEX = "SIS*LLYS";
055
056  private final Map<String,Collection<String>> topics = new HashMap<String,Collection<String>>();
057  private int lines = DEFAULT_LINES;
058  private int printedLineCounter = 0;
059  private final BufferedReader consoleIn = new BufferedReader(new InputStreamReader(System.in));
060  
061  private PrintStream out = System.out;
062  private boolean stdout = true;
063
064  /**
065   * @return kuinka monta riviä tulostetaan korkeintaan pysähtymättä
066   */
067  public int getLines() { return lines; }
068
069  /**
070   * @param lines pysähtymättä tulostettavien rivien lukumäärä
071   */
072  public void setLines(int lines) {
073    if ( lines > 0 )
074      this.lines = lines;
075  }
076
077  /**
078   * @param input tutkittava rivi
079   * @return alkaako rivi lopetusmerkillä
080   */
081  private static boolean quit(String input) {
082    return firstIs(input, QUIT_CHAR);
083  }
084
085  /**
086   * @param s tutkittava rivi
087   * @param c merkki jota etsitään
088   * @return onko c rivin s ensimmäinen merkki (true) vai ei (false)
089   */
090  private static boolean firstIs(String s, char c) {
091    if ( s == null || s.length() == 0 )
092      return false;
093
094    return (Character.toUpperCase(s.charAt(0)) == Character.toUpperCase(c));
095  }
096
097  /**
098   * Pysähtyy odottamaan returnin panamista jos on tulostettuja rivejä
099   * 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}