1 /**
2  *******************************************************************************
3  * Copyright (C) 2001-2015, International Business Machines Corporation and
4  * others. All Rights Reserved.
5  *******************************************************************************
6  */
7 
8 package com.ibm.icu.impl.data;
9 
10 import java.io.BufferedReader;
11 import java.io.Closeable;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.io.InputStreamReader;
15 import java.io.UnsupportedEncodingException;
16 
17 import com.ibm.icu.impl.ICUData;
18 import com.ibm.icu.impl.PatternProps;
19 
20 /**
21  * A reader for text resource data in the current package or the package
22  * of a given class object.  The
23  * resource data is loaded through the class loader, so it will
24  * typically be a file in the same directory as the *.class files, or
25  * a file within a JAR file in the corresponding subdirectory.  The
26  * file must be a text file in one of the supported encodings; when the
27  * resource is opened by constructing a <code>ResourceReader</code>
28  * object the encoding is specified.
29  *
30  * @author Alan Liu
31  */
32 public class ResourceReader implements Closeable {
33     private BufferedReader reader = null;
34     private String resourceName;
35     private String encoding; // null for default encoding
36     private Class<?> root;
37 
38     /**
39      * The one-based line number. Has the special value -1 before the
40      * object is initialized. Has the special value 0 after initialization
41      * but before the first line is read.
42      */
43     private int lineNo;
44 
45     /**
46      * Construct a reader object for the text file of the given name
47      * in this package, using the given encoding.
48      * @param resourceName the name of the text file located in this
49      * package's ".data" subpackage.
50      * @param encoding the encoding of the text file; if unsupported
51      * an exception is thrown
52      * @exception UnsupportedEncodingException if
53      * <code>encoding</code> is not supported by the JDK.
54      */
ResourceReader(String resourceName, String encoding)55     public ResourceReader(String resourceName, String encoding)
56         throws UnsupportedEncodingException {
57         this(ICUData.class, "data/" + resourceName, encoding);
58     }
59 
60     /**
61      * Construct a reader object for the text file of the given name
62      * in this package, using the default encoding.
63      * @param resourceName the name of the text file located in this
64      * package's ".data" subpackage.
65      */
ResourceReader(String resourceName)66     public ResourceReader(String resourceName) {
67         this(ICUData.class, "data/" + resourceName);
68     }
69 
70     /**
71      * Construct a reader object for the text file of the given name
72      * in the given class's package, using the given encoding.
73      * @param resourceName the name of the text file located in the
74      * given class's package.
75      * @param encoding the encoding of the text file; if unsupported
76      * an exception is thrown
77      * @exception UnsupportedEncodingException if
78      * <code>encoding</code> is not supported by the JDK.
79      */
ResourceReader(Class<?> rootClass, String resourceName, String encoding)80     public ResourceReader(Class<?> rootClass, String resourceName, String encoding)
81         throws UnsupportedEncodingException {
82         this.root = rootClass;
83         this.resourceName = resourceName;
84         this.encoding = encoding;
85         lineNo = -1;
86         _reset();
87     }
88 
89          /**
90           * Construct a reader object for the input stream associated with
91           * the given resource name.
92           * @param is the input stream of the resource
93           * @param resourceName the name of the resource
94           */
ResourceReader(InputStream is, String resourceName, String encoding)95           public ResourceReader(InputStream is, String resourceName, String encoding) {
96                    this.root = null;
97          this.resourceName = resourceName;
98          this.encoding = encoding;
99 
100          this.lineNo = -1;
101          try {
102              InputStreamReader isr = (encoding == null)
103                  ? new InputStreamReader(is)
104                  : new InputStreamReader(is, encoding);
105 
106              this.reader = new BufferedReader(isr);
107              this.lineNo= 0;
108          }
109          catch (UnsupportedEncodingException e) {
110          }
111      }
112 
113           /**
114            * Construct a reader object for the input stream associated with
115            * the given resource name.
116            * @param is the input stream of the resource
117            * @param resourceName the name of the resource
118            */
ResourceReader(InputStream is, String resourceName)119           public ResourceReader(InputStream is, String resourceName) {
120               this(is, resourceName, null);
121           }
122 
123     /**
124      * Construct a reader object for the text file of the given name
125      * in the given class's package, using the default encoding.
126      * @param resourceName the name of the text file located in the
127      * given class's package.
128      */
ResourceReader(Class<?> rootClass, String resourceName)129     public ResourceReader(Class<?> rootClass, String resourceName) {
130         this.root = rootClass;
131         this.resourceName = resourceName;
132         this.encoding = null;
133         lineNo = -1;
134         try {
135             _reset();
136         } catch (UnsupportedEncodingException e) {}
137     }
138 
139     /**
140      * Read and return the next line of the file or <code>null</code>
141      * if the end of the file has been reached.
142      */
readLine()143     public String readLine() throws IOException {
144         if (lineNo == 0) {
145             // Remove BOMs
146             ++lineNo;
147             String line = reader.readLine();
148             if (line != null && (line.charAt(0) == '\uFFEF' ||
149                                  line.charAt(0) == '\uFEFF')) {
150                 line = line.substring(1);
151             }
152             return line;
153         }
154         ++lineNo;
155         return reader.readLine();
156     }
157 
158     /**
159      * Read a line, ignoring blank lines and lines that start with
160      * '#'.
161      * @param trim if true then trim leading Pattern_White_Space.
162      */
readLineSkippingComments(boolean trim)163     public String readLineSkippingComments(boolean trim) throws IOException {
164         for (;;) {
165             String line = readLine();
166             if (line == null) {
167                 return line;
168             }
169             // Skip over white space
170             int pos = PatternProps.skipWhiteSpace(line, 0);
171             // Ignore blank lines and comment lines
172             if (pos == line.length() || line.charAt(pos) == '#') {
173                 continue;
174             }
175             // Process line
176             if (trim) line = line.substring(pos);
177             return line;
178         }
179     }
180 
181 
182     /**
183      * Read a line, ignoring blank lines and lines that start with
184      * '#'. Do not trim leading Pattern_White_Space.
185      */
readLineSkippingComments()186     public String readLineSkippingComments() throws IOException {
187         return readLineSkippingComments(false);
188     }
189 
190     /**
191      * Return the one-based line number of the last line returned by
192      * readLine() or readLineSkippingComments(). Should only be called
193      * after a call to one of these methods; otherwise the return
194      * value is undefined.
195      */
getLineNumber()196     public int getLineNumber() {
197         return lineNo;
198     }
199 
200     /**
201      * Return a string description of the position of the last line
202      * returned by readLine() or readLineSkippingComments().
203      */
describePosition()204     public String describePosition() {
205         return resourceName + ':' + lineNo;
206     }
207 
208     /**
209      * Reset this reader so that the next call to
210      * <code>readLine()</code> returns the first line of the file
211      * again.  This is a somewhat expensive call, however, calling
212      * <code>reset()</code> after calling it the first time does
213      * nothing if <code>readLine()</code> has not been called in
214      * between.
215      */
reset()216     public void reset() {
217         try {
218             _reset();
219         } catch (UnsupportedEncodingException e) {}
220         // We swallow this exception, if there is one.  If the encoding is
221         // invalid, the constructor will have thrown this exception already and
222         // the caller shouldn't use the object afterwards.
223     }
224 
225     /**
226      * Reset to the start by reconstructing the stream and readers.
227      * We could also use mark() and reset() on the stream or reader,
228      * but that would cause them to keep the stream data around in
229      * memory.  We don't want that because some of the resource files
230      * are large, e.g., 400k.
231      */
_reset()232     private void _reset() throws UnsupportedEncodingException {
233         try {
234             close();
235         } catch (IOException e) {}
236         if (lineNo == 0) {
237             return;
238         }
239         InputStream is = ICUData.getStream(root, resourceName);
240         if (is == null) {
241             throw new IllegalArgumentException("Can't open " + resourceName);
242         }
243 
244         InputStreamReader isr =
245             (encoding == null) ? new InputStreamReader(is) :
246                                  new InputStreamReader(is, encoding);
247         reader = new BufferedReader(isr);
248         lineNo = 0;
249     }
250 
251     /**
252      * Closes the underlying reader and releases any system resources
253      * associated with it. If the stream is already closed then invoking
254      * this method has no effect.
255      */
close()256     public void close() throws IOException {
257         if (reader != null) {
258             reader.close();
259             reader = null;
260         }
261     }
262 }
263