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    }