001/* 002 * Copyright 2011-2012 Stephen Connolly. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.jszip.pseudo.io; 018 019import org.apache.commons.lang3.StringUtils; 020import org.codehaus.plexus.archiver.zip.ZipEntry; 021import org.codehaus.plexus.archiver.zip.ZipFile; 022import org.mozilla.javascript.Context; 023 024import java.io.File; 025import java.io.IOException; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Collections; 029import java.util.Enumeration; 030import java.util.LinkedHashSet; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034import java.util.TreeMap; 035 036public class PseudoFileSystem { 037 /** 038 * Secret key used to hold the reference to the pseudo filesystem. 039 */ 040 private static final Object KEY = new Object(); 041 042 private final PseudoFile root = new VirtualDirectoryPseudoFile(null, ""); 043 044 private final Layer[] layers; 045 046 public PseudoFileSystem(Layer... layers) { 047 this.layers = layers; 048 } 049 050 public PseudoFileSystem(List<Layer> layers) { 051 this(layers.toArray(new Layer[layers.size()])); 052 } 053 054 public String getPathSeparator() { 055 return "/"; 056 } 057 058 public PseudoFile[] listChildren(PseudoFile dir, PseudoFileFilter filter) { 059 TreeMap<String, Layer> names = new TreeMap<String, Layer>(); 060 final String path = dir.getAbsolutePath(this); 061 for (int i = layers.length - 1; i >= 0; i--) { 062 for (String name : layers[i].listChildren(path)) { 063 names.put(name, layers[i]); 064 } 065 } 066 List<PseudoFile> result = new ArrayList<PseudoFile>(names.size()); 067 for (Map.Entry<String, Layer> entry : names.entrySet()) { 068 if (filter.accept(entry.getKey())) { 069 result.add(entry.getValue().makeChild(this, dir, entry.getKey())); 070 } 071 } 072 return result.toArray(new PseudoFile[result.size()]); 073 } 074 075 public PseudoFile root() { 076 return root; 077 } 078 079 public PseudoFile getPseudoFile(String filename) { 080 filename = StringUtils.removeEnd(filename, getPathSeparator()); 081 if (filename.isEmpty()) { 082 return root(); 083 } 084 int index = filename.lastIndexOf(getPathSeparator()); 085 if (index != -1) { 086 return getPseudoFile(getPseudoFile(filename.substring(0, index)), filename.substring(index + 1)); 087 } 088 return getPseudoFile(root(), filename); 089 } 090 091 public PseudoFile getPseudoFile(PseudoFile parent, String name) { 092 if (name.equals(".")) { 093 return parent; 094 } 095 if (name.equals("..")) { 096 return parent.getParentFile(); 097 } 098 String parentPath = parent.getAbsolutePath(this); 099 for (Layer layer : layers) { 100 if (layer.listChildren(parentPath).contains(name)) { 101 return layer.makeChild(this, parent, name); 102 } 103 } 104 if (layers.length == 0) { 105 return new VirtualDirectoryPseudoFile(parent, name); 106 } 107 return layers[0].makeChild(this, parent, name); 108 } 109 110 public synchronized void installInContext() { 111 Context.getCurrentContext().putThreadLocal(KEY, this); 112 } 113 114 public synchronized void removeFromContext() { 115 final Context context = Context.getCurrentContext(); 116 if (context != null) { 117 context.putThreadLocal(KEY, null); 118 } 119 } 120 121 public static PseudoFileSystem current() { 122 final Context currentContext = Context.getCurrentContext(); 123 return currentContext != null ? (PseudoFileSystem) currentContext.getThreadLocal(KEY) : null; 124 } 125 126 public abstract static class Layer { 127 128 public abstract List<String> listChildren(String relativePath); 129 130 public PseudoFile makeChild(PseudoFile parent, String name) { 131 return makeChild(PseudoFileSystem.current(), parent, name); 132 } 133 134 public abstract PseudoFile makeChild(PseudoFileSystem fs, PseudoFile parent, String name); 135 136 } 137 138 public static class FileLayer extends Layer { 139 private final String prefix; 140 private final File root; 141 142 public FileLayer(File root) { 143 this("", root); 144 } 145 146 public FileLayer(String prefix, File root) { 147 this.prefix = StringUtils.isEmpty(prefix) ? "/" : "/" + StringUtils.removeEnd( 148 StringUtils.removeStart(prefix, "/"), "/") + "/"; 149 this.root = root; 150 } 151 152 @Override 153 public List<String> listChildren(String relativePath) { 154 relativePath = StringUtils.removeEnd(relativePath, "/") + "/"; 155 if (relativePath.startsWith(prefix)) { 156 final String pathFragment = relativePath.substring(prefix.length()); 157 final String[] list = new File(root, pathFragment).list(); 158 return list == null ? Collections.<String>emptyList() : Arrays.asList(list); 159 } 160 if (prefix.startsWith(relativePath)) { 161 int index = prefix.indexOf('/', relativePath.length() + 1); 162 if (index != -1) { 163 return Collections.singletonList(prefix.substring(relativePath.length(), index)); 164 } 165 } 166 return Collections.emptyList(); 167 } 168 169 @Override 170 public PseudoFile makeChild(PseudoFileSystem fs, PseudoFile parent, String name) { 171 String relativePath = StringUtils.removeEnd(parent.getAbsolutePath(fs), "/") + "/" + name; 172 if (relativePath.startsWith(prefix)) { 173 return new FilePseudoFile(parent, new File(root, relativePath.substring(prefix.length()))); 174 } 175 if (prefix.equals(relativePath + "/")) { 176 int lastIndex = prefix.lastIndexOf('/'); 177 int index = prefix.lastIndexOf('/', lastIndex - 1); 178 return new AliasFilePseudoFile(parent, root, prefix.substring(index + 1, lastIndex)); 179 } 180 if (!StringUtils.isEmpty(prefix) && prefix.startsWith(relativePath)) { 181 return new VirtualDirectoryPseudoFile(parent, name); 182 } 183 return new NotExistingPseudoFile(parent, name); 184 } 185 186 @Override 187 public String toString() { 188 final StringBuilder sb = new StringBuilder(); 189 sb.append("FileLayer"); 190 sb.append("{prefix='").append(prefix).append('\''); 191 sb.append(", root=").append(root); 192 sb.append('}'); 193 return sb.toString(); 194 } 195 } 196 197 public static class ZipLayer extends PseudoFileSystem.Layer { 198 private final String prefix; 199 private final File zipFile; 200 private final Map<String, ZipEntry> contents; 201 202 public ZipLayer(String prefix, File zipFile) throws IOException { 203 this.prefix = StringUtils.isEmpty(prefix) ? "/" : "/" + StringUtils.removeEnd( 204 StringUtils.removeStart(prefix, "/"), "/") + "/"; 205 this.zipFile = zipFile; 206 ZipFile file = new ZipFile(zipFile); 207 Map<String, ZipEntry> contents = new TreeMap<String, ZipEntry>(); 208 Enumeration<ZipEntry> entries = file.getEntries(); 209 while (entries.hasMoreElements()) { 210 ZipEntry entry = entries.nextElement(); 211 contents.put(this.prefix + StringUtils.removeStart(entry.getName(), "/"), entry); 212 } 213 214 this.contents = contents; 215 } 216 217 @Override 218 public List<String> listChildren(String relativePath) { 219 relativePath = StringUtils.removeEnd(relativePath, "/") + "/"; 220 if (relativePath.startsWith(prefix) || prefix.equals(relativePath + "/")) { 221 final String pathFragment = StringUtils.removeEnd(relativePath, "/") + "/"; 222 Set<String> result = new LinkedHashSet<String>(); 223 for (String path : contents.keySet()) { 224 if (path.startsWith(pathFragment)) { 225 int index = path.indexOf('/', pathFragment.length()); 226 if (index == -1) { 227 result.add(path.substring(pathFragment.length())); 228 } else { 229 result.add(path.substring(pathFragment.length(), index)); 230 } 231 } 232 } 233 return new ArrayList<String>(result); 234 } 235 if (prefix.startsWith(relativePath)) { 236 int index = prefix.indexOf('/', relativePath.length()); 237 if (index != -1) { 238 return Collections.singletonList(prefix.substring(relativePath.length(), index)); 239 } 240 } 241 return Collections.emptyList(); 242 } 243 244 @Override 245 public PseudoFile makeChild(PseudoFileSystem fs, PseudoFile parent, String name) { 246 String relativePath = StringUtils.removeEnd(parent.getAbsolutePath(fs), "/") + "/" + name; 247 final ZipEntry entry = contents.get(relativePath); 248 if (entry != null) { 249 return new ZipPseudoFile(parent, zipFile, entry); 250 } 251 if (prefix.equals(relativePath + "/")) { 252 return new VirtualDirectoryPseudoFile(parent, name); 253 } 254 if (!StringUtils.isEmpty(prefix) && prefix.startsWith(relativePath)) { 255 return new VirtualDirectoryPseudoFile(parent, name); 256 } 257 for (String childPath : contents.keySet()) { 258 if (!StringUtils.isEmpty(childPath) && childPath.startsWith(relativePath)) { 259 return new VirtualDirectoryPseudoFile(parent, name); 260 } 261 } 262 return new NotExistingPseudoFile(parent, name); 263 } 264 265 @Override 266 public String toString() { 267 final StringBuilder sb = new StringBuilder(); 268 sb.append("ZipLayer"); 269 sb.append("{prefix='").append(prefix).append('\''); 270 sb.append(", zipFile=").append(zipFile); 271 sb.append('}'); 272 return sb.toString(); 273 } 274 } 275 276}