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