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 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * 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   * Copyright: 2005
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: SwordBookMetaData.java 2232 2012-02-20 01:21:42Z dmsmith $
21   */
22  package org.crosswire.jsword.book.sword;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.net.URI;
27  import java.util.List;
28  import java.util.Locale;
29  
30  import org.crosswire.common.util.Language;
31  import org.crosswire.common.util.NetUtil;
32  import org.crosswire.common.util.PropertyMap;
33  import org.crosswire.jsword.book.BookCategory;
34  import org.crosswire.jsword.book.FeatureType;
35  import org.crosswire.jsword.book.KeyType;
36  import org.crosswire.jsword.book.basic.AbstractBookMetaData;
37  import org.crosswire.jsword.book.filter.Filter;
38  import org.crosswire.jsword.book.filter.FilterFactory;
39  import org.jdom.Document;
40  
41  /**
42   * A utility class for loading and representing Sword book configs.
43   * 
44   * <p>
45   * Config file format. See also: <a href=
46   * "http://sword.sourceforge.net/cgi-bin/twiki/view/Swordapi/ConfFileLayout">
47   * http://sword.sourceforge.net/cgi-bin/twiki/view/Swordapi/ConfFileLayout</a>
48   * 
49   * <p>
50   * The contents of the About field are in rtf.
51   * <p>
52   * \ is used as a continuation line.
53   * 
54   * @see gnu.lgpl.License for license details.<br>
55   *      The copyright to this program is held by it's authors.
56   * @author Mark Goodwin [mark at thorubio dot org]
57   * @author Joe Walker [joe at eireneh dot com]
58   * @author Jacky Cheung
59   * @author DM Smith [dmsmith555 at yahoo dot com]
60   */
61  /**
62   *
63   *
64   * @see gnu.lgpl.License for license details.<br>
65   *      The copyright to this program is held by it's authors.
66   * @author DM Smith [dmsmith555 at yahoo dot com]
67   */
68  public final class SwordBookMetaData extends AbstractBookMetaData {
69      /**
70       * Loads a sword config from a given File.
71       * 
72       * @param file
73       * @param internal
74       * @throws IOException
75       */
76      public SwordBookMetaData(File file, String internal, URI bookRootPath) throws IOException {
77          cet = new ConfigEntryTable(internal);
78          cet.load(file);
79  
80          setLibrary(bookRootPath);
81          buildProperties();
82      }
83  
84      /**
85       * Loads a sword config from a buffer.
86       * 
87       * @param buffer
88       * @param internal
89       * @throws IOException
90       */
91      public SwordBookMetaData(byte[] buffer, String internal) throws IOException {
92          cet = new ConfigEntryTable(internal);
93          cet.load(buffer);
94          buildProperties();
95      }
96  
97      /* (non-Javadoc)
98       * @see org.crosswire.jsword.book.BookMetaData#isQuestionable()
99       */
100     @Override
101     public boolean isQuestionable() {
102         return cet.isQuestionable();
103     }
104 
105     /* (non-Javadoc)
106      * @see org.crosswire.jsword.book.basic.AbstractBookMetaData#isSupported()
107      */
108     @Override
109     public boolean isSupported() {
110         return cet.isSupported() && cet.getBookType().isSupported(this);
111     }
112 
113     /* (non-Javadoc)
114      * @see org.crosswire.jsword.book.basic.AbstractBookMetaData#isEnciphered()
115      */
116     @Override
117     public boolean isEnciphered() {
118         return cet.isEnciphered();
119     }
120 
121     /* (non-Javadoc)
122      * @see org.crosswire.jsword.book.basic.AbstractBookMetaData#isLocked()
123      */
124     @Override
125     public boolean isLocked() {
126         return cet.isLocked();
127     }
128 
129     /* (non-Javadoc)
130      * @see org.crosswire.jsword.book.basic.AbstractBookMetaData#unlock(java.lang.String)
131      */
132     @Override
133     public boolean unlock(String unlockKey) {
134         return cet.unlock(unlockKey);
135     }
136 
137     /* (non-Javadoc)
138      * @see org.crosswire.jsword.book.basic.AbstractBookMetaData#getUnlockKey()
139      */
140     @Override
141     public String getUnlockKey() {
142         return cet.getUnlockKey();
143     }
144 
145     /* (non-Javadoc)
146      * @see org.crosswire.jsword.book.BookMetaData#getName()
147      */
148     public String getName() {
149         return (String) getProperty(ConfigEntryType.DESCRIPTION);
150     }
151 
152     /**
153      * Returns the Charset of the book based on the encoding attribute
154      * 
155      * @return the charset of the book.
156      */
157     public String getBookCharset() {
158         return ENCODING_JAVA.get(getProperty(ConfigEntryType.ENCODING));
159     }
160 
161     /* (non-Javadoc)
162      * @see org.crosswire.jsword.book.basic.AbstractBookMetaData#getKeyType()
163      */
164     @Override
165     public KeyType getKeyType() {
166         BookType bookType = getBookType();
167         if (bookType == null) {
168             return null;
169         }
170         return bookType.getKeyType();
171     }
172 
173     /**
174      * Returns the Book Type.
175      */
176     public BookType getBookType() {
177         return cet.getBookType();
178     }
179 
180     /**
181      * Returns the sourceType.
182      */
183     public Filter getFilter() {
184         String sourcetype = (String) getProperty(ConfigEntryType.SOURCE_TYPE);
185         return FilterFactory.getFilter(sourcetype);
186     }
187 
188     /**
189      * @return Returns the relative path of the book's conf.
190      */
191     public String getConfPath() {
192         return SwordConstants.DIR_CONF + '/' + getInitials().toLowerCase(Locale.ENGLISH) + SwordConstants.EXTENSION_CONF;
193     }
194 
195     /* (non-Javadoc)
196      * @see org.crosswire.jsword.book.basic.AbstractBookMetaData#setLibrary(java.net.URI)
197      */
198     @Override
199     public void setLibrary(URI library) {
200         // Ignore it if it is not supported.
201         if (!isSupported()) {
202             return;
203         }
204 
205         cet.add(ConfigEntryType.LIBRARY_URL, library.toString());
206         super.setLibrary(library);
207 
208         // Previously, all DATA_PATH entries end in / to indicate dirs
209         // or not to indicate file prefixes.
210         // This is no longer true.
211         // Now we need to test the file/url to see if it exists and is a directory.
212         String datapath = (String) getProperty(ConfigEntryType.DATA_PATH);
213         int lastSlash = datapath.lastIndexOf('/');
214 
215         // There were modules that did not have a valid datapath.
216         // This should not be necessary
217         if (lastSlash == -1) {
218             return;
219         }
220 
221         // DataPath typically ends in a '/' to indicate a directory.
222         // If so remove it.
223         if (lastSlash == datapath.length() - 1) {
224             datapath = datapath.substring(0, lastSlash);
225         }
226 
227         URI location = NetUtil.lengthenURI(library, datapath);
228         File bookDir = new File(location.getPath());
229         // For some modules, the last element of the DataPath
230         // is a prefix for file names.
231         if (!bookDir.isDirectory()) {
232             // Shorten it by one segment and test again.
233             lastSlash = datapath.lastIndexOf('/');
234             datapath = datapath.substring(0, lastSlash);
235             location = NetUtil.lengthenURI(library, datapath);
236             bookDir = new File(location.getPath());
237             if (!bookDir.isDirectory()) {
238                 return;
239             }
240         }
241 
242         cet.add(ConfigEntryType.LOCATION_URL, location.toString());
243         super.setLocation(location);
244     }
245 
246     /* (non-Javadoc)
247      * @see org.crosswire.jsword.book.BookMetaData#getBookCategory()
248      */
249     public BookCategory getBookCategory() {
250         if (type == null) {
251             type = (BookCategory) getProperty(ConfigEntryType.CATEGORY);
252             if (type == BookCategory.OTHER) {
253                 type = getBookType().getBookCategory();
254             }
255         }
256         return type;
257     }
258 
259     /* (non-Javadoc)
260      * @see org.crosswire.jsword.book.basic.AbstractBookMetaData#toOSIS()
261      */
262     @Override
263     public Document toOSIS() {
264         return new Document(cet.toOSIS());
265     }
266 
267     /* (non-Javadoc)
268      * @see org.crosswire.jsword.book.BookMetaData#getInitials()
269      */
270     public String getInitials() {
271         return (String) getProperty(ConfigEntryType.INITIALS);
272     }
273 
274     /**
275      * Get the string value for the property or null if it is not defined. It is
276      * assumed that all properties gotten with this method are single line.
277      * 
278      * @param entry
279      *            the ConfigEntryType
280      * @return the property or null
281      */
282     public Object getProperty(ConfigEntryType entry) {
283         return cet.getValue(entry);
284     }
285 
286     /* (non-Javadoc)
287      * @see org.crosswire.jsword.book.BookMetaData#isLeftToRight()
288      */
289     public boolean isLeftToRight() {
290         // This should return the dominate direction of the text, if it is BiDi,
291         // then we have to guess.
292         String dir = (String) getProperty(ConfigEntryType.DIRECTION);
293         if (ConfigEntryType.DIRECTION_BIDI.equalsIgnoreCase(dir)) {
294             // When BiDi, return the dominate direction based upon the Book's
295             // Language not Direction
296             Language lang = (Language) getProperty(ConfigEntryType.LANG);
297             return lang.isLeftToRight();
298         }
299 
300         return ConfigEntryType.DIRECTION_LTOR.equalsIgnoreCase(dir);
301     }
302 
303     /* (non-Javadoc)
304      * @see org.crosswire.jsword.book.basic.AbstractBookMetaData#hasFeature(org.crosswire.jsword.book.FeatureType)
305      */
306     @Override
307     public boolean hasFeature(FeatureType feature) {
308         if (cet.match(ConfigEntryType.FEATURE, feature.toString())) {
309             return true;
310         }
311         // Many "features" are GlobalOptionFilters, which in the Sword C++ API
312         // indicate a class to use for filtering.
313         // These mostly have the source type prepended to the feature
314         StringBuilder buffer = new StringBuilder((String) getProperty(ConfigEntryType.SOURCE_TYPE));
315         buffer.append(feature);
316         if (cet.match(ConfigEntryType.GLOBAL_OPTION_FILTER, buffer.toString())) {
317             return true;
318         }
319         // But some do not
320         return cet.match(ConfigEntryType.GLOBAL_OPTION_FILTER, feature.toString());
321     }
322 
323     private void buildProperties() {
324         // merge entries into properties file
325         for (ConfigEntryType key : cet.getKeys()) {
326             Object value = cet.getValue(key);
327             // value is null if the config entry was rejected.
328             if (value == null) {
329                 continue;
330             }
331             if (value instanceof List<?>) {
332                 List<String> list = (List<String>) value;
333                 StringBuilder combined = new StringBuilder();
334                 boolean appendSeparator = false;
335                 for (String element : list) {
336                     if (appendSeparator) {
337                         combined.append('\n');
338                     }
339                     combined.append(element);
340                     appendSeparator = true;
341                 }
342 
343                 value = combined.toString();
344             }
345 
346             putProperty(key.toString(), value);
347         }
348     }
349 
350     /**
351      * Sword only recognizes two encodings for its modules: UTF-8 and LATIN1
352      * Sword uses MS Windows cp1252 for Latin 1 not the standard. Arrgh! The
353      * language strings need to be converted to Java charsets
354      */
355     private static final PropertyMap ENCODING_JAVA = new PropertyMap();
356     static {
357         ENCODING_JAVA.put("Latin-1", "WINDOWS-1252");
358         ENCODING_JAVA.put("UTF-8", "UTF-8");
359     }
360 
361     private ConfigEntryTable cet;
362     private BookCategory type;
363 }
364