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 }