    001    // Copyright 2006, 2008, 2010, 2011, 2012 The Apache Software Foundation
    002    //
    003    // Licensed under the Apache License, Version 2.0 (the "License");
    004    // you may not use this file except in compliance with the License.
    005    // You may obtain a copy of the License at
    006    //
    007    // http://www.apache.org/licenses/LICENSE-2.0
    008    //
    009    // Unless required by applicable law or agreed to in writing, software
    010    // distributed under the License is distributed on an "AS IS" BASIS,
    011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012    // See the License for the specific language governing permissions and
    013    // limitations under the License.
    014
    015    package org.apache.tapestry5.ioc.internal.util;
    016
    017    import org.apache.tapestry5.ioc.Resource;
    018    import org.apache.tapestry5.ioc.util.LocalizedNameGenerator;
    019
    020    import java.io.BufferedInputStream;
    021    import java.io.IOException;
    022    import java.io.InputStream;
    023    import java.net.URL;
    024    import java.util.Locale;
    025
    026    /**
    027     * Abstract implementation of {@link Resource}. Subclasses must implement the abstract methods {@link Resource#toURL()}
    028     * and {@link #newResource(String)} as well as toString(), hashCode() and equals().
    029     */
    030    public abstract class AbstractResource extends LockSupport implements Resource
    031    {
    032        private class Localization
    033        {
    034            final Locale locale;
    035
    036            final Resource resource;
    037
    038            final Localization next;
    039
    040            private Localization(Locale locale, Resource resource, Localization next)
    041            {
    042                this.locale = locale;
    043                this.resource = resource;
    044                this.next = next;
    045            }
    046        }
    047
    048        private final String path;
    049
    050        // Guarded by Lock
    051        private boolean exists, existsComputed;
    052
    053        // Guarded by lock
    054        private Localization firstLocalization;
    055
    056        protected AbstractResource(String path)
    057        {
    058            assert path != null;
    059            this.path = path;
    060        }
    061
    062        public final String getPath()
    063        {
    064            return path;
    065        }
    066
    067        public final String getFile()
    068        {
    069            int slashx = path.lastIndexOf('/');
    070
    071            return path.substring(slashx + 1);
    072        }
    073
    074        public final String getFolder()
    075        {
    076            int slashx = path.lastIndexOf('/');
    077
    078            return (slashx < 0) ? "" : path.substring(0, slashx);
    079        }
    080
    081        public final Resource forFile(String relativePath)
    082        {
    083            assert relativePath != null;
    084            StringBuilder builder = new StringBuilder(getFolder());
    085
    086            for (String term : relativePath.split("/"))
    087            {
    088                // This will occur if the relative path contains sequential slashes
    089
    090                if (term.equals(""))
    091                    continue;
    092
    093                if (term.equals("."))
    094                    continue;
    095
    096                if (term.equals(".."))
    097                {
    098                    int slashx = builder.lastIndexOf("/");
    099
    100                    // TODO: slashx < 0 (i.e., no slash)
    101
    102                    // Trim path to content before the slash
    103
    104                    builder.setLength(slashx);
    105                    continue;
    106                }
    107
    108                // TODO: term blank or otherwise invalid?
    109                // TODO: final term should not be "." or "..", or for that matter, the
    110                // name of a folder, since a Resource should be a file within
    111                // a folder.
    112
    113                if (builder.length() > 0)
    114                    builder.append("/");
    115
    116                builder.append(term);
    117            }
    118
    119            return createResource(builder.toString());
    120        }
    121
    122        public final Resource forLocale(Locale locale)
    123        {
    124            try
    125            {
    126                acquireReadLock();
    127
    128                for (Localization l = firstLocalization; l != null; l = l.next)
    129                {
    130                    if (l.locale.equals(locale))
    131                    {
    132                        return l.resource;
    133                    }
    134                }
    135
    136                return populateLocalizationCache(locale);
    137            } finally
    138            {
    139                releaseReadLock();
    140            }
    141        }
    142
    143        private Resource populateLocalizationCache(Locale locale)
    144        {
    145            try
    146            {
    147                upgradeReadLockToWriteLock();
    148
    149                // Race condition: another thread may have beaten us to it:
    150
    151                for (Localization l = firstLocalization; l != null; l = l.next)
    152                {
    153                    if (l.locale.equals(locale))
    154                    {
    155                        return l.resource;
    156                    }
    157                }
    158
    159                Resource result = findLocalizedResource(locale);
    160
    161                firstLocalization = new Localization(locale, result, firstLocalization);
    162
    163                return result;
    164
    165            } finally
    166            {
    167                downgradeWriteLockToReadLock();
    168            }
    169        }
    170
    171        private Resource findLocalizedResource(Locale locale)
    172        {
    173            for (String path : new LocalizedNameGenerator(this.path, locale))
    174            {
    175                Resource potential = createResource(path);
    176
    177                if (potential.exists())
    178                    return potential;
    179            }
    180
    181            return null;
    182        }
    183
    184        public final Resource withExtension(String extension)
    185        {
    186            assert InternalUtils.isNonBlank(extension);
    187            int dotx = path.lastIndexOf('.');
    188
    189            if (dotx < 0)
    190                return createResource(path + "." + extension);
    191
    192            return createResource(path.substring(0, dotx + 1) + extension);
    193        }
    194
    195        /**
    196         * Creates a new resource, unless the path matches the current Resource's path (in which case, this resource is
    197         * returned).
    198         */
    199        private Resource createResource(String path)
    200        {
    201            if (this.path.equals(path))
    202                return this;
    203
    204            return newResource(path);
    205        }
    206
    207        /**
    208         * Simple check for whether {@link #toURL()} returns null or not.
    209         */
    210        public boolean exists()
    211        {
    212            try
    213            {
    214                acquireReadLock();
    215
    216                if (!existsComputed)
    217                {
    218                    computeExists();
    219                }
    220
    221                return exists;
    222            } finally
    223            {
    224                releaseReadLock();
    225            }
    226        }
    227
    228        private void computeExists()
    229        {
    230            try
    231            {
    232                upgradeReadLockToWriteLock();
    233
    234                if (!existsComputed)
    235                {
    236                    exists = toURL() != null;
    237                    existsComputed = true;
    238                }
    239            } finally
    240            {
    241                downgradeWriteLockToReadLock();
    242            }
    243        }
    244
    245        /**
    246         * Obtains the URL for the Resource and opens the stream, wrapped by a BufferedInputStream.
    247         */
    248        public InputStream openStream() throws IOException
    249        {
    250            URL url = toURL();
    251
    252            if (url == null)
    253                return null;
    254
    255            return new BufferedInputStream(url.openStream());
    256        }
    257
    258        /**
    259         * Factory method provided by subclasses.
    260         */
    261        protected abstract Resource newResource(String path);
    262    }