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