# HG changeset patch # User Michael Schloh von Bennewitz # Date 1423867537 -3600 # Node ID 6dcaece8ec41f2c41ad51b60258e565213b6bdc1 # Parent cc93757aeca3525f72a617678fd69b9810e0e862 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. diff -r cc93757aeca3 -r 6dcaece8ec41 build.gradle --- a/build.gradle Thu Feb 12 20:16:00 2015 +0100 +++ b/build.gradle Fri Feb 13 23:45:37 2015 +0100 @@ -39,6 +39,38 @@ } productFlavors { } + //dexOptions { + // preDexLibraries = false + //} +} + +// Kludgy attempt to patch ical4j +//task fixDeps(type: zip) { +// zip -d $cache/ical4j-*.jar net/fortuna/ical4j/model/TimeZoneRegistryImpl.class +// zip -d $cache/ical4j-*.jar net/fortuna/ical4j/util/ResourceLoader.class +//} +// Hack contains patched ical4j files +task patchIcal(type: Copy) { + from 'hack' + include 'model/' + include 'util/' + into 'src/net/fortuna/ical4j' +} +configure(patchIcal) { + group = BasePlugin.ASSEMBLE_TASK_NAME // Or BUILD_GROUP + description = 'Patch embedded ical4j dependency in a kludgy way' +} +task patchWipe(type: Delete) { + delete 'src/net' +} +configure(patchWipe) { + group = BasePlugin.ASSEMBLE_TASK_NAME // Or BUILD_GROUP + description = 'Deletes patched dependencies in a kludgy way' +} +project.afterEvaluate{ + clean.dependsOn(patchWipe) + compileDebugJava.dependsOn(patchIcal) + compileReleaseJava.dependsOn(patchIcal) } dependencies { diff -r cc93757aeca3 -r 6dcaece8ec41 hack/model/TimeZoneRegistryImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hack/model/TimeZoneRegistryImpl.java Fri Feb 13 23:45:37 2015 +0100 @@ -0,0 +1,220 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o 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. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 THE COPYRIGHT OWNER OR + * 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 net.fortuna.ical4j.model; + +import java.io.IOException; +import java.net.URL; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.fortuna.ical4j.data.CalendarBuilder; +import net.fortuna.ical4j.data.ParserException; +import net.fortuna.ical4j.model.component.VTimeZone; +import net.fortuna.ical4j.model.property.TzUrl; +import net.fortuna.ical4j.util.CompatibilityHints; +import net.fortuna.ical4j.util.Configurator; +import net.fortuna.ical4j.util.ResourceLoader; + +import android.util.Log; + +import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap; + +/** + * $Id$ + * + * Created on 18/09/2005 + * + * The default implementation of a TimeZoneRegistry. This implementation will search the classpath for + * applicable VTimeZone definitions used to back the provided TimeZone instances. + * @author Ben Fortuna + */ +public class TimeZoneRegistryImpl implements TimeZoneRegistry { + + private static final String DEFAULT_RESOURCE_PREFIX = "zoneinfo/"; + + private static final Pattern TZ_ID_SUFFIX = Pattern.compile("(?<=/)[^/]*/[^/]*$"); + + private static final String UPDATE_ENABLED = "net.fortuna.ical4j.timezone.update.enabled"; + + private static final Map DEFAULT_TIMEZONES = new ConcurrentHashMap(); + + private static final Properties ALIASES = new Properties(); + static { + try { + ALIASES.load(ResourceLoader.getResourceAsStream("tz.alias")); + } + catch (IOException ioe) { + Log.w("MSvB-Hack", "Error loading timezone aliases: " + ioe.getMessage()); + } + try { + ALIASES.load(ResourceLoader.getResourceAsStream("/tz.alias")); + } + catch (Exception e) { + Log.d("MSvB-Hack", "Error loading custom timezone aliases: " + e.getMessage()); + } + } + + private Map timezones; + + private String resourcePrefix; + + /** + * Default constructor. + */ + public TimeZoneRegistryImpl() { + this(DEFAULT_RESOURCE_PREFIX); + } + + /** + * Creates a new instance using the specified resource prefix. + * @param resourcePrefix a prefix prepended to classpath resource lookups for default timezones + */ + public TimeZoneRegistryImpl(final String resourcePrefix) { + this.resourcePrefix = resourcePrefix; + timezones = new ConcurrentHashMap(); + } + + /** + * {@inheritDoc} + */ + public final void register(final TimeZone timezone) { + // for now we only apply updates to included definitions by default.. + register(timezone, false); + } + + /** + * {@inheritDoc} + */ + public final void register(final TimeZone timezone, boolean update) { + if (update) { + // load any available updates for the timezone.. + timezones.put(timezone.getID(), new TimeZone(updateDefinition(timezone.getVTimeZone()))); + } + else { + timezones.put(timezone.getID(), timezone); + } + } + + /** + * {@inheritDoc} + */ + public final void clear() { + timezones.clear(); + } + + /** + * {@inheritDoc} + */ + public final TimeZone getTimeZone(final String id) { + TimeZone timezone = (TimeZone) timezones.get(id); + if (timezone == null) { + timezone = (TimeZone) DEFAULT_TIMEZONES.get(id); + if (timezone == null) { + // if timezone not found with identifier, try loading an alias.. + final String alias = ALIASES.getProperty(id); + if (alias != null) { + return getTimeZone(alias); + } + else { + synchronized (DEFAULT_TIMEZONES) { + // check again as it may be loaded now.. + timezone = (TimeZone) DEFAULT_TIMEZONES.get(id); + if (timezone == null) { + try { + final VTimeZone vTimeZone = loadVTimeZone(id); + if (vTimeZone != null) { + // XXX: temporary kludge.. + // ((TzId) vTimeZone.getProperties().getProperty(Property.TZID)).setValue(id); + timezone = new TimeZone(vTimeZone); + DEFAULT_TIMEZONES.put(timezone.getID(), timezone); + } + else if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING)) { + // strip global part of id and match on default tz.. + Matcher matcher = TZ_ID_SUFFIX.matcher(id); + if (matcher.find()) { + return getTimeZone(matcher.group()); + } + } + } + catch (Exception e) { + Log.w("MSvB-Hack", "Error occurred loading VTimeZone", e); + } + } + } + } + } + } + return timezone; + } + + /** + * Loads an existing VTimeZone from the classpath corresponding to the specified Java timezone. + */ + private VTimeZone loadVTimeZone(final String id) throws IOException, ParserException { + final URL resource = ResourceLoader.getResource(resourcePrefix + id + ".ics"); + if (resource != null) { + final CalendarBuilder builder = new CalendarBuilder(); + final Calendar calendar = builder.build(resource.openStream()); + final VTimeZone vTimeZone = (VTimeZone) calendar.getComponent(Component.VTIMEZONE); + // load any available updates for the timezone.. can be explicility disabled via configuration + if (!"false".equals(Configurator.getProperty(UPDATE_ENABLED))) { + return updateDefinition(vTimeZone); + } + return vTimeZone; + } + return null; + } + + /** + * @param vTimeZone + * @return + */ + private VTimeZone updateDefinition(VTimeZone vTimeZone) { + final TzUrl tzUrl = vTimeZone.getTimeZoneUrl(); + if (tzUrl != null) { + try { + final CalendarBuilder builder = new CalendarBuilder(); + final Calendar calendar = builder.build(tzUrl.getUri().toURL().openStream()); + final VTimeZone updatedVTimeZone = (VTimeZone) calendar.getComponent(Component.VTIMEZONE); + if (updatedVTimeZone != null) { + return updatedVTimeZone; + } + } + catch (Exception e) { + Log.w("MSvB-Hack", "Unable to retrieve updates for timezone: " + vTimeZone.getTimeZoneId().getValue(), e); + } + } + return vTimeZone; + } +} diff -r cc93757aeca3 -r 6dcaece8ec41 hack/util/ResourceLoader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hack/util/ResourceLoader.java Fri Feb 13 23:45:37 2015 +0100 @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o 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. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 THE COPYRIGHT OWNER OR + * 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 net.fortuna.ical4j.util; + +import java.io.InputStream; +import java.net.URL; + +import android.util.Log; + +/** + * @author fortuna + * + */ +public class ResourceLoader { + + /** + * Load a resource via the thread context classloader. If security permissions don't allow + * this fallback to loading via current classloader. + * @param name a resource name + * @return a {@link URL} or null if resource is not found + */ + public static URL getResource(String name) { + URL resource = null; + try { + // Hack to bootstrap a multithreaded class loader context + if (Thread.currentThread().getContextClassLoader() == null) + Thread.currentThread().setContextClassLoader(ResourceLoader.class.getClassLoader()); + resource = Thread.currentThread().getContextClassLoader().getResource(name); + + if (resource == null) // Flawed build path for assets, try again + resource = Thread.currentThread().getContextClassLoader().getResource("/" + name); + } + catch (SecurityException e) { + Log.i("MSvB-Hack", "Unable to access context classloader, using default. " + e.getMessage()); + } + catch (Exception e) { + Log.i("MSvB-Hack", "General context classloader error, using default. " + e.getMessage()); + } + if (resource == null) { + resource = ResourceLoader.class.getResource("/" + name); + } + return resource; + } + + /** + * Load a resource via the thread context classloader. If security permissions don't allow + * this fallback to loading via current classloader. + * @param name a resource name + * @return an {@link InputStream} or null if resource is not found + */ + public static InputStream getResourceAsStream(String name) { + InputStream stream = null; + try { + // Hack to bootstrap a multithreaded class loader context + if (Thread.currentThread().getContextClassLoader() == null) + Thread.currentThread().setContextClassLoader(ResourceLoader.class.getClassLoader()); + stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(name); + if (stream == null) + stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("/" + name); + } + catch (SecurityException e) { + Log.i("MSvB-Hack", "Unable to access context classloader, using default. " + e.getMessage()); + } + catch (Exception e) { + Log.i("MSvB-Hack", "General context classloader error, using default. " + e.getMessage()); + } + if (stream == null) { + stream = ResourceLoader.class.getResourceAsStream("/" + name); + } + return stream; + } +}