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