/************************************************************************** * Parts copyright (c) 2001 by Punch Telematix. All rights reserved. * * Parts copyright (c) 2004, 2005, 2008, 2009 by Chris Gray, /k/ Embedded * * Java Solutions. All rights reserved. * * * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * 1. Redistributions of source code must retain the above copyright * * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the following disclaimer in the * * documentation and/or other materials provided with the distribution. * * 3. Neither the name of Punch Telematix or of /k/ Embedded Java Solutions* * nor the names of other contributors may be used to endorse or promote* * products derived from this software without specific prior written * * permission. * * * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * * IN NO EVENT SHALL PUNCH TELEMATIX, /K/ EMBEDDED JAVA SOLUTIONS OR OTHER * * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **************************************************************************/ package wonka.resource; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Enumeration; import java.util.Hashtable; import java.util.MissingResourceException; import java.util.NoSuchElementException; import java.util.ResourceBundle; import java.util.SimpleTimeZone; import java.util.StringTokenizer; import java.util.TimeZone; public class TimeZoneResourceBundle extends ResourceBundle { /** * Mapping of timezone names onto either a SimpleTimeZone object or * a String which is the name of another time zone for which the * current key is an alias. Thus if a given key returns a String value * the query should be repeated with the String value as key. */ private static Hashtable timeZones = new Hashtable(); /** * The name of the default time zone (may be an alias). If no default * is specified in the mika.timezones file then GMT is used. */ private static String defaultTimeZoneName = "GMT"; /** * The static initializer reads in and processes the mika.timezones file. */ static { InputStream tzis = ClassLoader.getSystemResourceAsStream(System.getProperty("mika.timezones", "mika.timezones")); if (tzis == null) { tzis = new ByteArrayInputStream("GMT=0\n".getBytes()); } BufferedReader tzbr = new BufferedReader(new InputStreamReader(tzis)); try { String tzline = tzbr.readLine(); while (tzline != null) { processTimeZoneLine(tzline); tzline = tzbr.readLine(); } } catch (IOException ioe) { System.err.println("Error reading mika.timezones file"); ioe.printStackTrace(); } } /** * Process one line of the mika.timezones file. * <dl><dt>Line starting with '#' * <dd>Comment line, ignore. * </dl> * <p>Ohterwise the line must contain an equals sign; the string to the * left of this we call 'lhs' and the string to the right 'rhs'. * <dl><dt>lhs is "Default" * <dd>Store rhs as <code>defaultTimeZoneName</code>. * <dd>rhs starts with '+', '-', or a digit * <dt>Parse the rhs as a SimpleTimeZone definition; for details see * the <code>mika.timezones</code> file. The SimpleTimeZone * definition may optionally be followed by up to four quotes * strings which are the short and long names for the timezone * without and with daylight saving time. Set a mapping from * lhs to this SimpleTimeZone. * <dt>Otherwise (rhs does not start with sign or digit) * <dd>Treat as an alias: the rhs must correspond to the lhs of a * line which was already processed. Set a mapping from * lhs to this String. * </dl> */ private static void processTimeZoneLine(String tzline) { if (tzline.length() == 0 || tzline.charAt(0) == '#') { return; } int equalssign = tzline.indexOf('='); if (equalssign < 0) { System.err.println("No '=' sign in mika.timezones line: " + tzline); return; } String lhs = tzline.substring(0, equalssign).trim(); String rhs = tzline.substring(equalssign + 1).trim(); if (rhs.length() == 0) { System.err.println("Nothing after '=' sign in mika.timezones line: " + tzline); return; } if ("Default".equalsIgnoreCase(lhs)) { defaultTimeZoneName = rhs; return; } char rhs1stchar = rhs.charAt(0); if (Character.isDigit(rhs1stchar) || rhs1stchar == '-' || rhs1stchar == '+') { // Strip off quoted strings from end String[] names = new String[4]; while (names[3] == null && rhs.charAt(rhs.length() - 1) == '"') { rhs = rhs.substring(0, rhs.length() - 1); int openquote = rhs.lastIndexOf('"'); if (openquote < 0) { System.err.println("Mismatched quotes in mika.timezones line: " + tzline); return; } names[3] = names[2]; names[2] = names[1]; names[1] = names[0]; names[0] = rhs.substring(openquote + 1); rhs = rhs.substring(0, openquote).trim(); } if (names[0] == null) { names[0] = lhs; } if (names[1] == null) { names[1] = names[0]; } if (names[2] == null) { names[2] = names[0]; } if (names[3] == null) { names[3] = names[2]; } TimeZoneDisplayNameResourceBundle.timeZoneNames.put(lhs, names); if (TimeZoneDisplayNameResourceBundle.timeZoneNames.get(names[0]) == null) { TimeZoneDisplayNameResourceBundle.timeZoneNames.put(names[0], names); } int leftparen = rhs.indexOf('('); int rightparen = rhs.indexOf(')'); float basicoffset; try { if (leftparen < 0 && rightparen < 0) { basicoffset = Float.parseFloat(rhs); SimpleTimeZone stz = new SimpleTimeZone((int)(basicoffset * 3600000F), lhs); timeZones.put(lhs, stz); } else if (leftparen < 0 || rightparen < 0 || rightparen < leftparen) { System.err.println("Mismatched parentheses in mika.timezones line: " + tzline); return; } else { basicoffset = Float.parseFloat(rhs.substring(0, leftparen)); String dststring = rhs.substring(leftparen + 1, rightparen); float savings = Float.parseFloat(rhs.substring(rightparen + 1)); StringTokenizer st = new StringTokenizer(dststring, ","); try { int startmonth = Integer.parseInt(st.nextToken()) - 1; int startday = Integer.parseInt(st.nextToken()); int startdayofweek = (Integer.parseInt(st.nextToken()) % 7) + 1; int starttime = (int)(Float.parseFloat(st.nextToken()) * 3600000F); int endmonth = Integer.parseInt(st.nextToken()) - 1; int endday = Integer.parseInt(st.nextToken()); int enddayofweek = (Integer.parseInt(st.nextToken()) % 7) + 1; int endtime = (int)(Float.parseFloat(st.nextToken()) * 3600000F); SimpleTimeZone stz = new SimpleTimeZone((int)(basicoffset * 3600000F), lhs, startmonth, startday, startdayofweek, starttime, endmonth, endday, enddayofweek, endtime/*, savings*/); timeZones.put(lhs, stz); } catch (NoSuchElementException nsee) { System.err.println("Too few DST elements in mika.timezone line: " + tzline); return; } } } catch (NumberFormatException nfe) { System.err.println("NumberFormatException in mika.timezone line: " + tzline); return; } } else { SimpleTimeZone stz = (SimpleTimeZone)timeZones.get(rhs); if (stz == null) { System.err.println("Unknown right-hand side in mika.timezone line: " + tzline); return; } timeZones.put(lhs, rhs); } } //required implementation of abstract methods of ResourceBundle protected Object handleGetObject(String key) throws MissingResourceException { Object o = timeZones.get(key); while ((o != null) && (o instanceof String)) { o = timeZones.get(o); } if (o != null) { return o; } throw new MissingResourceException("Oops, resource not found","TimeZoneResourceBundle","key"); } public Enumeration getKeys() { return timeZones.keys(); } /** */ public String[] getKeysArray() { int length = timeZones.size(); String [] keys = new String[length]; Enumeration e = timeZones.keys(); int i=0; while (e.hasMoreElements()) { keys[i++] = (String) e.nextElement(); } return keys; } /** */ public String[] getKeysArray(int rOffset) { int length = timeZones.size(); String [] keys = new String[length]; Enumeration e = timeZones.keys(); int i=0; while (e.hasMoreElements()) { String s = (String) e.nextElement(); Object o = timeZones.get(s); while ((o != null) && (o instanceof String)) { o = timeZones.get(o); } if (rOffset == ((SimpleTimeZone)o).getRawOffset()){ keys[i++] = s; } } String[] okeys = new String[i]; System.arraycopy(keys , 0 , okeys , 0 , i); return okeys; } /** ** This implementation will create time zones of the form "GMT+hh:mm" or ** "GMT-hh:mm" on demand, adding them to its hashtable. Note that the ** result returned by getAvailableIDs() will only include these zones ** after they have been requested using getTimeZone(), even though in ** a sense they were always available ... */ public TimeZone getTimeZone(String keyID) { Object o = null; String alias = null; String canonical = keyID; SimpleTimeZone z; synchronized(timeZones) { o = timeZones.get(keyID); if ((o != null) && (o instanceof String)) { alias = keyID; while ((o != null) && (o instanceof String)) { canonical = (String)o; o = timeZones.get(o); } } z = (SimpleTimeZone)o; if (alias != null) { z = (SimpleTimeZone)z.clone(); z.setID(alias); TimeZoneDisplayNameResourceBundle.timeZoneNames.put(alias, canonical); } int l = canonical.length(); if (z == null && l >= 7 && canonical.charAt(l - 3) == ':' && (canonical.charAt(l - 6) == '+' || canonical.charAt(l - 6) == '-')) { String prefix = null; TimeZone base = null; Enumeration e = timeZones.keys(); while (e.hasMoreElements()) { String candidate = (String)e.nextElement(); if (candidate.length() == l - 6 && canonical.startsWith(candidate)) { try { base = (SimpleTimeZone)timeZones.get(candidate); prefix = candidate; break; } catch (ClassCastException cce) {} } } if (prefix != null) { char sign_char = canonical.charAt(l - 6); try { int hours = Integer.parseInt(canonical.substring(l - 5, l - 3)); int mins = Integer.parseInt(canonical.substring(l - 2)); if (hours < 0 || hours > 23 || mins < 0 || mins > 59) { throw new NumberFormatException(); } int raw = base.getRawOffset(); z = (SimpleTimeZone)base.clone(); z.setID(canonical); if (sign_char == '+') { z.setRawOffset(base.getRawOffset() + 1000*60*(60 * hours + mins)); } else { z.setRawOffset(base.getRawOffset() - 1000*60*(60 * hours + mins)); } } catch (NumberFormatException nfe) { System.err.println("Malformed relative timezone: " + canonical); } } } } return z; } /** * Get the default time zone, i.e. the one specified in the mika.timezones * file as "Default=FOO", or GMT if no such line is present in the file. * @return The default time zone. */ public TimeZone getDefaultTimeZone() { return getTimeZone(defaultTimeZoneName); } }