Fri, 13 Feb 2015 23:45:37 +0100
Give up after a longwinded and unfruitful attempt to patch ical4j,
made difficult by the neverending graph of method calls leading up
to the NPE caused in getResource*() of the ResourceLoader class.
1 /**
2 * Copyright (c) 2012, Ben Fortuna
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * o Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * o Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * o Neither the name of Ben Fortuna nor the names of any other contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 package net.fortuna.ical4j.model;
34 import java.io.IOException;
35 import java.net.URL;
36 import java.util.Map;
37 import java.util.Properties;
38 import java.util.regex.Matcher;
39 import java.util.regex.Pattern;
41 import net.fortuna.ical4j.data.CalendarBuilder;
42 import net.fortuna.ical4j.data.ParserException;
43 import net.fortuna.ical4j.model.component.VTimeZone;
44 import net.fortuna.ical4j.model.property.TzUrl;
45 import net.fortuna.ical4j.util.CompatibilityHints;
46 import net.fortuna.ical4j.util.Configurator;
47 import net.fortuna.ical4j.util.ResourceLoader;
49 import android.util.Log;
51 import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
53 /**
54 * $Id$
55 *
56 * Created on 18/09/2005
57 *
58 * The default implementation of a <code>TimeZoneRegistry</code>. This implementation will search the classpath for
59 * applicable VTimeZone definitions used to back the provided TimeZone instances.
60 * @author Ben Fortuna
61 */
62 public class TimeZoneRegistryImpl implements TimeZoneRegistry {
64 private static final String DEFAULT_RESOURCE_PREFIX = "zoneinfo/";
66 private static final Pattern TZ_ID_SUFFIX = Pattern.compile("(?<=/)[^/]*/[^/]*$");
68 private static final String UPDATE_ENABLED = "net.fortuna.ical4j.timezone.update.enabled";
70 private static final Map DEFAULT_TIMEZONES = new ConcurrentHashMap();
72 private static final Properties ALIASES = new Properties();
73 static {
74 try {
75 ALIASES.load(ResourceLoader.getResourceAsStream("tz.alias"));
76 }
77 catch (IOException ioe) {
78 Log.w("MSvB-Hack", "Error loading timezone aliases: " + ioe.getMessage());
79 }
80 try {
81 ALIASES.load(ResourceLoader.getResourceAsStream("/tz.alias"));
82 }
83 catch (Exception e) {
84 Log.d("MSvB-Hack", "Error loading custom timezone aliases: " + e.getMessage());
85 }
86 }
88 private Map timezones;
90 private String resourcePrefix;
92 /**
93 * Default constructor.
94 */
95 public TimeZoneRegistryImpl() {
96 this(DEFAULT_RESOURCE_PREFIX);
97 }
99 /**
100 * Creates a new instance using the specified resource prefix.
101 * @param resourcePrefix a prefix prepended to classpath resource lookups for default timezones
102 */
103 public TimeZoneRegistryImpl(final String resourcePrefix) {
104 this.resourcePrefix = resourcePrefix;
105 timezones = new ConcurrentHashMap();
106 }
108 /**
109 * {@inheritDoc}
110 */
111 public final void register(final TimeZone timezone) {
112 // for now we only apply updates to included definitions by default..
113 register(timezone, false);
114 }
116 /**
117 * {@inheritDoc}
118 */
119 public final void register(final TimeZone timezone, boolean update) {
120 if (update) {
121 // load any available updates for the timezone..
122 timezones.put(timezone.getID(), new TimeZone(updateDefinition(timezone.getVTimeZone())));
123 }
124 else {
125 timezones.put(timezone.getID(), timezone);
126 }
127 }
129 /**
130 * {@inheritDoc}
131 */
132 public final void clear() {
133 timezones.clear();
134 }
136 /**
137 * {@inheritDoc}
138 */
139 public final TimeZone getTimeZone(final String id) {
140 TimeZone timezone = (TimeZone) timezones.get(id);
141 if (timezone == null) {
142 timezone = (TimeZone) DEFAULT_TIMEZONES.get(id);
143 if (timezone == null) {
144 // if timezone not found with identifier, try loading an alias..
145 final String alias = ALIASES.getProperty(id);
146 if (alias != null) {
147 return getTimeZone(alias);
148 }
149 else {
150 synchronized (DEFAULT_TIMEZONES) {
151 // check again as it may be loaded now..
152 timezone = (TimeZone) DEFAULT_TIMEZONES.get(id);
153 if (timezone == null) {
154 try {
155 final VTimeZone vTimeZone = loadVTimeZone(id);
156 if (vTimeZone != null) {
157 // XXX: temporary kludge..
158 // ((TzId) vTimeZone.getProperties().getProperty(Property.TZID)).setValue(id);
159 timezone = new TimeZone(vTimeZone);
160 DEFAULT_TIMEZONES.put(timezone.getID(), timezone);
161 }
162 else if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING)) {
163 // strip global part of id and match on default tz..
164 Matcher matcher = TZ_ID_SUFFIX.matcher(id);
165 if (matcher.find()) {
166 return getTimeZone(matcher.group());
167 }
168 }
169 }
170 catch (Exception e) {
171 Log.w("MSvB-Hack", "Error occurred loading VTimeZone", e);
172 }
173 }
174 }
175 }
176 }
177 }
178 return timezone;
179 }
181 /**
182 * Loads an existing VTimeZone from the classpath corresponding to the specified Java timezone.
183 */
184 private VTimeZone loadVTimeZone(final String id) throws IOException, ParserException {
185 final URL resource = ResourceLoader.getResource(resourcePrefix + id + ".ics");
186 if (resource != null) {
187 final CalendarBuilder builder = new CalendarBuilder();
188 final Calendar calendar = builder.build(resource.openStream());
189 final VTimeZone vTimeZone = (VTimeZone) calendar.getComponent(Component.VTIMEZONE);
190 // load any available updates for the timezone.. can be explicility disabled via configuration
191 if (!"false".equals(Configurator.getProperty(UPDATE_ENABLED))) {
192 return updateDefinition(vTimeZone);
193 }
194 return vTimeZone;
195 }
196 return null;
197 }
199 /**
200 * @param vTimeZone
201 * @return
202 */
203 private VTimeZone updateDefinition(VTimeZone vTimeZone) {
204 final TzUrl tzUrl = vTimeZone.getTimeZoneUrl();
205 if (tzUrl != null) {
206 try {
207 final CalendarBuilder builder = new CalendarBuilder();
208 final Calendar calendar = builder.build(tzUrl.getUri().toURL().openStream());
209 final VTimeZone updatedVTimeZone = (VTimeZone) calendar.getComponent(Component.VTIMEZONE);
210 if (updatedVTimeZone != null) {
211 return updatedVTimeZone;
212 }
213 }
214 catch (Exception e) {
215 Log.w("MSvB-Hack", "Unable to retrieve updates for timezone: " + vTimeZone.getTimeZoneId().getValue(), e);
216 }
217 }
218 return vTimeZone;
219 }
220 }