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 }