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}