1 /* 2 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * Copyright (c) 2012, Stephen Colebourne & Michael Nascimento Santos 28 * 29 * All rights reserved. 30 * 31 * Redistribution and use in source and binary forms, with or without 32 * modification, are permitted provided that the following conditions are met: 33 * 34 * * Redistributions of source code must retain the above copyright notice, 35 * this list of conditions and the following disclaimer. 36 * 37 * * Redistributions in binary form must reproduce the above copyright notice, 38 * this list of conditions and the following disclaimer in the documentation 39 * and/or other materials provided with the distribution. 40 * 41 * * Neither the name of JSR-310 nor the names of its contributors 42 * may be used to endorse or promote products derived from this software 43 * without specific prior written permission. 44 * 45 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 46 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 47 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 48 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 49 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 50 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 51 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 52 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 53 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 54 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 55 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 56 */ 57 package java.time.chrono; 58 59 60 import java.io.BufferedReader; 61 import java.io.File; 62 import java.io.FileInputStream; 63 import java.io.IOException; 64 import java.io.InputStream; 65 import java.io.InputStreamReader; 66 import java.nio.file.FileSystems; 67 import java.security.AccessController; 68 import java.text.ParseException; 69 import java.time.LocalDate; 70 import java.time.format.DateTimeFormatter; 71 import java.time.temporal.ChronoField; 72 import java.util.Arrays; 73 import java.util.function.Consumer; 74 75 /** 76 * A reader for Hijrah Deviation files. 77 * <p> 78 * For each Hijrah calendar a deviation file is used 79 * to modify the initial Hijrah calendar by changing the length of 80 * individual months. 81 * <p> 82 * The default location of the deviation files is 83 * {@code <java.home> + FILE_SEP + "lib"}. 84 * The deviation files are named {@code "hijrah_" + ID}. 85 * <p> 86 * The deviation file for a calendar can be overridden by defining the 87 * property {@code java.time.chrono.HijrahChronology.File.hijrah_<ID>} 88 * with the full pathname of the deviation file. 89 * <p> 90 * The deviation file is read line by line: 91 * <ul> 92 * <li>The "#" character begins a comment - 93 * all characters including and after the "#" on the line are ignored</li> 94 * <li>Valid lines contain two fields, separated by whitespace, whitespace 95 * is otherwise ignored.</li> 96 * <li>The first field is a LocalDate using the format "MMM-dd-yyyy"; 97 * The LocalDate must be converted to a HijrahDate using 98 * the {@code islamic} calendar.</li> 99 * <li>The second field is the offset, +2, +1, -1, -2 as parsed 100 * by Integer.valueOf to modify the length of the Hijrah month.</li> 101 * <li>Empty lines are ignore.</li> 102 * <li>Exceptions are throw for invalid formatted dates and offset, 103 * and other I/O errors on the file. 104 * </ul> 105 * <p>Example:</p> 106 * <pre># Deviation data for islamicc calendar 107 * Mar-23-2012 -1 108 * Apr-22-2012 +1 109 * May-21-2012 -1 110 * Dec-14-2012 +1 111 * </pre> 112 * 113 * @since 1.8 114 */ 115 final class HijrahDeviationReader { 116 117 /** 118 * Default prefix for name of deviation file; suffix is typeId. 119 */ 120 private static final String DEFAULT_CONFIG_FILE_PREFIX = "hijrah_"; 121 122 /** 123 * Read Hijrah_deviation.cfg file. The config file contains the deviation 124 * data with format defined in the class javadoc. 125 * 126 * @param typeId the name of the calendar 127 * @param calendarType the calendar type 128 * @return {@code true} if the file was read and each entry accepted by the 129 * Consumer; else {@code false} no configuration was done 130 * 131 * @throws IOException for zip/jar file handling exception. 132 * @throws ParseException if the format of the configuration file is wrong. 133 */ 134 static boolean readDeviation(String typeId, String calendarType, 135 Consumer<HijrahChronology.Deviation> consumer) throws IOException, ParseException { 136 InputStream is = getConfigFileInputStream(typeId); 137 if (is != null) { 138 try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { 139 String line = ""; 140 int num = 0; 141 while ((line = br.readLine()) != null) { 142 num++; 143 HijrahChronology.Deviation entry = parseLine(line, num); 144 if (entry != null) { 145 consumer.accept(entry); 146 } 147 } 148 } 149 return true; 150 } 151 return false; 152 } 153 154 /** 155 * Parse each deviation element. 156 * 157 * @param line a line to parse 158 * @param num line number 159 * @return an Entry or null if the line is empty. 160 * @throws ParseException if line has incorrect format. 161 */ 162 private static HijrahChronology.Deviation parseLine(final String line, final int num) throws ParseException { 163 int hash = line.indexOf("#"); 164 String nocomment = (hash < 0) ? line : line.substring(0, hash); 165 String[] split = nocomment.split("\\s"); 166 if (split.length == 0 || split[0].isEmpty()) { 167 return null; // Nothing to parse 168 } 169 if (split.length != 2) { 170 throw new ParseException("Less than 2 tokens on line : " + line + Arrays.toString(split) + ", split.length: " + split.length, num); 171 } 172 173 //element [0] is a date 174 //element [1] is the offset 175 176 LocalDate isoDate = DateTimeFormatter.ofPattern("MMM-dd-yyyy").parse(split[0], LocalDate::from); 177 int offset = Integer.valueOf(split[1]); 178 179 // Convert date to HijrahDate using the default Islamic Calendar 180 181 HijrahDate hijrahDate = HijrahChronology.INSTANCE.date(isoDate); 182 183 int year = hijrahDate.get(ChronoField.YEAR); 184 int month = hijrahDate.get(ChronoField.MONTH_OF_YEAR); 185 return new HijrahChronology.Deviation(year, month, year, month, offset); 186 } 187 188 189 /** 190 * Return InputStream for deviation configuration file. The default location 191 * of the deviation file is: 192 * <pre> 193 * $CLASSPATH/java/time/calendar 194 * </pre> And the default file name is: 195 * <pre> 196 * hijrah_ + typeId + .cfg 197 * </pre> The default location and file name can be overridden by setting 198 * following two Java system properties. 199 * <pre> 200 * Location: java.time.chrono.HijrahDate.deviationConfigDir 201 * File name: java.time.chrono.HijrahDate.File. + typeid 202 * </pre> Regarding the file format, see readDeviationConfig() method for 203 * details. 204 * 205 * @param typeId the name of the calendar deviation data 206 * @return InputStream for file reading. 207 * @throws IOException for zip/jar file handling exception. 208 */ 209 private static InputStream getConfigFileInputStream(final String typeId) throws IOException { 210 try { 211 InputStream stream = AccessController 212 .doPrivileged((java.security.PrivilegedExceptionAction<InputStream>) () -> { 213 String propFilename = "java.time.chrono.HijrahChronology.File." + typeId; 214 String filename = System.getProperty(propFilename); 215 File file = null; 216 if (filename != null) { 217 file = new File(filename); 218 } else { 219 String libDir = System.getProperty("java.home") + File.separator + "lib"; 220 try { 221 libDir = FileSystems.getDefault().getPath(libDir).toRealPath().toString(); 222 } catch(Exception e) {} 223 filename = DEFAULT_CONFIG_FILE_PREFIX + typeId + ".cfg"; 224 file = new File(libDir, filename); 225 } 226 227 if (file.exists()) { 228 try { 229 return new FileInputStream(file); 230 } catch (IOException ioe) { 231 throw ioe; 232 } 233 } else { 234 return null; 235 } 236 }); 237 return stream; 238 } catch (Exception ex) { 239 ex.printStackTrace(); 240 // Not working 241 return null; 242 } 243 } 244 }