1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *      http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * © CrossWire Bible Society, 2005 - 2016
18   *
19   */
20  package org.crosswire.jsword.book.readings;
21  
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.Date;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Locale;
28  import java.util.Map;
29  import java.util.MissingResourceException;
30  import java.util.ResourceBundle;
31  import java.util.TreeMap;
32  
33  import org.crosswire.common.util.CWClassLoader;
34  import org.crosswire.common.util.Language;
35  import org.crosswire.jsword.JSMsg;
36  import org.crosswire.jsword.JSOtherMsg;
37  import org.crosswire.jsword.book.BookCategory;
38  import org.crosswire.jsword.book.BookException;
39  import org.crosswire.jsword.book.FeatureType;
40  import org.crosswire.jsword.book.OSISUtil;
41  import org.crosswire.jsword.book.basic.AbstractBook;
42  import org.crosswire.jsword.book.basic.DefaultBookMetaData;
43  import org.crosswire.jsword.book.sword.processing.RawTextToXmlProcessor;
44  import org.crosswire.jsword.internationalisation.LocaleProviderManager;
45  import org.crosswire.jsword.passage.DefaultKeyList;
46  import org.crosswire.jsword.passage.Key;
47  import org.crosswire.jsword.passage.NoSuchKeyException;
48  import org.crosswire.jsword.passage.Passage;
49  import org.crosswire.jsword.passage.PassageKeyFactory;
50  import org.crosswire.jsword.passage.PreferredKey;
51  import org.crosswire.jsword.passage.RestrictionType;
52  import org.crosswire.jsword.passage.SetKeyList;
53  import org.crosswire.jsword.passage.VerseRange;
54  import org.crosswire.jsword.versification.system.Versifications;
55  import org.jdom2.Content;
56  import org.jdom2.Element;
57  import org.slf4j.Logger;
58  import org.slf4j.LoggerFactory;
59  
60  /**
61   * A Dictionary that displays daily Readings.
62   * 
63   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
64   * @author Joe Walker
65   */
66  public class ReadingsBook extends AbstractBook implements PreferredKey {
67      /**
68       * Constructor for ReadingsBook.
69       * @param driver the driver for this book
70       * @param setname the name of this book
71       * @param type the type of book
72       */
73      public ReadingsBook(ReadingsBookDriver driver, String setname, BookCategory type) {
74          super(null, null); // set the book metadata later and no backend
75  
76          hash = new TreeMap<Key, String>();
77  
78          //although we're getting this from the LocaleProviderManager, we're still setting 
79          //the language on the metadata once, so won't cope for dynamic changes
80          Locale defaultLocale = LocaleProviderManager.getLocale();
81          ResourceBundle prop = ResourceBundle.getBundle(setname, defaultLocale, CWClassLoader.instance(ReadingsBookDriver.class));
82  
83          // TRANSLATOR: The default name for JSword's Reading plan.
84          String name = JSMsg.gettext("Readings");
85          try {
86              name = prop.getString("title");
87          } catch (MissingResourceException e) {
88              log.warn("Missing resource: 'title' while parsing: {}", setname);
89          }
90  
91          DefaultBookMetaData bmd = new DefaultBookMetaData(driver, name, type);
92          bmd.setInitials(setname);
93          bmd.setLanguage(new Language(defaultLocale.getLanguage()));
94          setBookMetaData(bmd);
95  
96          // Go through the current year
97          java.util.Calendar greg = new java.util.GregorianCalendar();
98          greg.set(java.util.Calendar.DAY_OF_MONTH, 1);
99          greg.set(java.util.Calendar.MONDAY, java.util.Calendar.JANUARY);
100         int currentYear = greg.get(java.util.Calendar.YEAR);
101 
102         while (greg.get(java.util.Calendar.YEAR) == currentYear) {
103             String internalKey = ReadingsKey.external2internal(greg);
104             String readings = "";
105 
106             try {
107                 readings = prop.getString(internalKey);
108                 hash.put(new ReadingsKey(greg.getTime()), readings);
109             } catch (MissingResourceException e) {
110                 log.warn("Missing resource: {} while parsing: {}", internalKey, setname);
111             }
112 
113             greg.add(java.util.Calendar.DATE, 1);
114         }
115 
116         global = new SetKeyList(hash.keySet(), getName());
117     }
118 
119     /* (non-Javadoc)
120      * @see org.crosswire.jsword.passage.PreferredKey#getPreferred()
121      */
122     public Key getPreferred() {
123         return new ReadingsKey(new Date());
124     }
125 
126     public Iterator<Content> getOsisIterator(final Key key, final boolean allowEmpty, final boolean allowGenTitles) throws BookException {
127         if (!(key instanceof ReadingsKey)) {
128             // TRANSLATOR: Error condition: Indicates that something could not be found in the book. {0} is a placeholder for the unknown key.
129             throw new BookException(JSMsg.gettext("Key not found {0}", key.getName()));
130         }
131 
132         // TODO(DMS): make the iterator be demand driven
133         List<Content> content = new ArrayList<Content>();
134 
135         Element title = OSISUtil.factory().createTitle();
136         title.addContent(key.getName());
137         content.add(title);
138 
139         String readings = hash.get(key);
140         if (readings == null) {
141             // TRANSLATOR: Error condition: Indicates that something could not be found in the book. {0} is a placeholder for the unknown key.
142             throw new BookException(JSMsg.gettext("Key not found {0}", key.getName()));
143         }
144 
145         try {
146             // AV11N(DMS): Is this right?
147             // At this point and time, ReadingsBook is a verse list from the KJV.
148             // Should store the v11n in the ReadingsBook
149             PassageKeyFactory keyf = PassageKeyFactory.instance();
150             Passage ref = keyf.getKey(Versifications.instance().getVersification("KJV"), readings);
151 
152             Element list = OSISUtil.factory().createList();
153             content.add(list);
154 
155             Iterator<VerseRange> it = ref.rangeIterator(RestrictionType.NONE);
156             while (it.hasNext()) {
157                 Key range = it.next();
158 
159                 Element reading = OSISUtil.factory().createReference();
160                 reading.setAttribute(OSISUtil.OSIS_ATTR_REF, range.getOsisRef());
161                 reading.addContent(range.getName());
162 
163                 Element item = OSISUtil.factory().createItem();
164                 item.addContent(reading);
165                 list.addContent(item);
166             }
167         } catch (NoSuchKeyException ex) {
168             content.add(OSISUtil.factory().createText(JSOtherMsg.lookupText("Failed to parse {0}", readings)));
169         }
170 
171         return content.iterator();
172     }
173 
174     /* (non-Javadoc)
175      * @see org.crosswire.jsword.book.Book#contains(org.crosswire.jsword.passage.Key)
176      */
177     public boolean contains(Key key) {
178         return false;
179     }
180 
181     /** Returns an empty string
182      * (non-Javadoc)
183      * @see org.crosswire.jsword.book.Book#getRawText(org.crosswire.jsword.passage.Key)
184      */
185     public String getRawText(Key key) throws BookException {
186         return "";
187     }
188 
189     /**
190      * Returns an empty list
191      */
192     @Override
193     public List<Content> getOsis(Key key, RawTextToXmlProcessor processor) throws BookException {
194         return Collections.emptyList();
195     }
196 
197     /* (non-Javadoc)
198      * @see org.crosswire.jsword.book.Book#isWritable()
199      */
200     public boolean isWritable() {
201         return false;
202     }
203 
204     /* (non-Javadoc)
205      * @see org.crosswire.jsword.book.Book#setRawText(org.crosswire.jsword.passage.Key, java.lang.String)
206      */
207     public void setRawText(Key key, String rawData) throws BookException {
208         throw new BookException(JSOtherMsg.lookupText("This Book is read-only."));
209     }
210 
211     /* (non-Javadoc)
212      * @see org.crosswire.jsword.book.Book#setAliasKey(org.crosswire.jsword.passage.Key, org.crosswire.jsword.passage.Key)
213      */
214     public void setAliasKey(Key alias, Key source) throws BookException {
215         throw new BookException(JSOtherMsg.lookupText("This Book is read-only."));
216     }
217 
218     /* (non-Javadoc)
219      * @see org.crosswire.jsword.book.Book#getValidKey(java.lang.String)
220      */
221     public Key getValidKey(String name) {
222         try {
223             return getKey(name);
224         } catch (NoSuchKeyException e) {
225             return createEmptyKeyList();
226         }
227     }
228 
229     /* (non-Javadoc)
230      * @see org.crosswire.jsword.book.Book#getKey(java.lang.String)
231      */
232     public Key getKey(String name) throws NoSuchKeyException {
233         DefaultKeyList reply = new DefaultKeyList();
234         reply.addAll(new ReadingsKey(name, name, global));
235         return reply;
236     }
237 
238     /* (non-Javadoc)
239      * @see org.crosswire.jsword.book.Book#getGlobalKeyList()
240      */
241     public Key getGlobalKeyList() {
242         return global;
243     }
244 
245     /* (non-Javadoc)
246      * @see org.crosswire.jsword.book.Book#createEmptyKeyList()
247      */
248     public Key createEmptyKeyList() {
249         return new DefaultKeyList();
250     }
251 
252     @Override
253     public boolean hasFeature(FeatureType feature) {
254         return feature == FeatureType.DAILY_DEVOTIONS;
255     }
256 
257     /**
258      * The global key list
259      */
260     private Key global;
261 
262     /**
263      * The store of keys and data
264      */
265     private Map<Key, String> hash;
266 
267     /**
268      * The log stream
269      */
270     private static final Logger log = LoggerFactory.getLogger(ReadingsBook.class);
271 }
272