001package biweekly.io; 002 003import java.io.FileNotFoundException; 004import java.io.IOException; 005import java.net.URI; 006import java.net.URISyntaxException; 007import java.util.Collections; 008import java.util.HashMap; 009import java.util.Map; 010import java.util.NoSuchElementException; 011import java.util.TimeZone; 012 013import biweekly.component.VTimezone; 014import biweekly.io.text.ICalReader; 015import biweekly.property.TimezoneId; 016import biweekly.util.IOUtils; 017 018/* 019 Copyright (c) 2013-2015, Michael Angstadt 020 All rights reserved. 021 022 Redistribution and use in source and binary forms, with or without 023 modification, are permitted provided that the following conditions are met: 024 025 1. Redistributions of source code must retain the above copyright notice, this 026 list of conditions and the following disclaimer. 027 2. Redistributions in binary form must reproduce the above copyright notice, 028 this list of conditions and the following disclaimer in the documentation 029 and/or other materials provided with the distribution. 030 031 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 032 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 033 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 034 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 035 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 036 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 037 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 038 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 039 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 040 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 041 */ 042 043/** 044 * Downloads {@link VTimezone} components from <a 045 * href="http://www.tzurl.org">tzurl.org</a>. 046 * @author Michael Angstadt 047 */ 048public class TzUrlDotOrgGenerator implements VTimezoneGenerator { 049 private static final Map<URI, VTimezone> cache = Collections.synchronizedMap(new HashMap<URI, VTimezone>()); 050 private final String baseUrl; 051 052 /** 053 * Creates a new tzurl.org translator. 054 * @param outlook true to generate Outlook-compatible {@link VTimezone} 055 * components, false to use standards-based ones 056 */ 057 public TzUrlDotOrgGenerator(boolean outlook) { 058 baseUrl = "http://www.tzurl.org/zoneinfo" + (outlook ? "-outlook" : "") + "/"; 059 } 060 061 public VTimezone generate(TimeZone timezone) throws IllegalArgumentException { 062 URI uri; 063 try { 064 uri = new URI(baseUrl + timezone.getID()); 065 } catch (URISyntaxException e) { 066 throw new IllegalArgumentException(e); 067 } 068 069 VTimezone component = cache.get(uri); 070 if (component != null) { 071 return component; 072 } 073 074 ICalReader reader = null; 075 try { 076 reader = new ICalReader(uri.toURL().openStream()); 077 reader.readNext(); 078 079 TimezoneInfo tzinfo = reader.getTimezoneInfo(); 080 component = tzinfo.getComponents().iterator().next(); 081 082 TimezoneId componentId = component.getTimezoneId(); 083 if (componentId == null) { 084 /* 085 * There should always be a TZID property, but just in case 086 * there there isn't one, create one. 087 */ 088 component.setTimezoneId(timezone.getID()); 089 } else if (!timezone.getID().equals(componentId.getValue())) { 090 /* 091 * Ensure that the value of the TZID property is identical to 092 * the ID of the Java TimeZone object. This is to ensure that 093 * the values of the TZID parameters throughout the iCal match 094 * the value of the VTIMEZONE component's TZID property. 095 * 096 * For example, if tzurl.org is queried for the "PRC" timezone, 097 * then a VTIMEZONE component with a TZID of "Asia/Shanghai" is 098 * *actually* returned. This is a problem because iCal 099 * properties use the value of the Java TimeZone object to get 100 * the value of the TZID parameter, so the values of the TZID 101 * parameters and the VTIMEZONE component's TZID property will 102 * not be the same. 103 */ 104 componentId.setValue(timezone.getID()); 105 } 106 107 cache.put(uri, component); 108 return component; 109 } catch (FileNotFoundException e) { 110 throw notFound(e); 111 } catch (NoSuchElementException e) { 112 throw notFound(e); 113 } catch (IOException e) { 114 throw new RuntimeException(e); 115 } finally { 116 IOUtils.closeQuietly(reader); 117 } 118 } 119 120 private IllegalArgumentException notFound(Exception e) { 121 return new IllegalArgumentException("Timezone ID not recognized.", e); 122 } 123}