• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.res;
2 
3 import static java.util.Arrays.asList;
4 
5 import java.io.BufferedInputStream;
6 import java.io.File;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.net.MalformedURLException;
10 import java.net.URI;
11 import java.net.URL;
12 import java.util.ArrayList;
13 import java.util.Enumeration;
14 import java.util.LinkedHashMap;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.NavigableMap;
18 import java.util.NavigableSet;
19 import java.util.TreeMap;
20 import java.util.jar.JarEntry;
21 import java.util.jar.JarFile;
22 import org.robolectric.util.Join;
23 import org.robolectric.util.Util;
24 
25 abstract public class Fs {
fromJar(URL url)26   public static Fs fromJar(URL url) {
27     return new JarFs(new File(fixFileURL(url).getPath()));
28   }
29 
fixFileURL(URL u)30   private static URI fixFileURL(URL u) {
31     if (!"file".equals(u.getProtocol())) {
32       throw new IllegalArgumentException();
33     }
34     return new File(u.getPath()).toURI();
35   }
36 
37   /**
38    * @deprecated Use {@link #fromURL(URL)} instead.
39    */
40   @Deprecated
fileFromPath(String urlString)41   public static FsFile fileFromPath(String urlString) {
42     if (urlString.startsWith("jar:")) {
43       String[] parts = urlString.replaceFirst("jar:", "").split("!", 0);
44       Fs fs = new JarFs(new File(parts[0]));
45       return fs.join(parts[1].substring(1));
46     } else {
47       return new FileFsFile(new File(urlString));
48     }
49   }
50 
fromURL(URL url)51   public static FsFile fromURL(URL url) {
52     switch (url.getProtocol()) {
53       case "file":
54         return new FileFsFile(new File(url.getPath()));
55       case "jar":
56         String[] parts = url.getPath().split("!", 0);
57         try {
58           Fs fs = fromJar(new URL(parts[0]));
59           return fs.join(parts[1].substring(1));
60         } catch (MalformedURLException e) {
61           throw new IllegalArgumentException(e);
62         }
63       default:
64         throw new IllegalArgumentException("unsupported fs type for '" + url + "'");
65     }
66   }
67 
newFile(File file)68   public static FsFile newFile(File file) {
69     return new FileFsFile(file);
70   }
71 
newJarFile(File file)72   public static FsFile newJarFile(File file) {
73     JarFs jarFs = new JarFs(file);
74     return jarFs.new JarFsFile("");
75   }
76 
newFile(String filePath)77   public static FsFile newFile(String filePath) {
78     return new FileFsFile(filePath);
79   }
80 
currentDirectory()81   public static FsFile currentDirectory() {
82     return newFile(new File("."));
83   }
84 
85   static class JarFs extends Fs {
86     private static final Map<File, NavigableMap<String, JarEntry>> CACHE =
87         new LinkedHashMap<File, NavigableMap<String, JarEntry>>() {
88           @Override
89           protected boolean removeEldestEntry(Map.Entry<File, NavigableMap<String, JarEntry>> fileNavigableMapEntry) {
90             return size() > 10;
91           }
92         };
93 
94     private final JarFile jarFile;
95     private final NavigableMap<String, JarEntry> jarEntryMap;
96 
JarFs(File file)97     public JarFs(File file) {
98       try {
99         jarFile = new JarFile(file);
100       } catch (IOException e) {
101         throw new RuntimeException(e);
102       }
103 
104       NavigableMap<String, JarEntry> cachedMap;
105       synchronized (CACHE) {
106         cachedMap = CACHE.get(file.getAbsoluteFile());
107       }
108 
109       if (cachedMap == null) {
110         cachedMap = new TreeMap<>();
111         Enumeration<JarEntry> entries = jarFile.entries();
112         while (entries.hasMoreElements()) {
113           JarEntry jarEntry = entries.nextElement();
114 
115           // Add entries for any parent directories that did not have
116           // a JarEntry in the jar file.
117           String name = jarEntry.getName();
118           int index = name.length();
119           while ((index = name.lastIndexOf('/', index - 1)) != -1) {
120             String dir = name.substring(0, index+1);
121             if (!cachedMap.containsKey(dir)) {
122               cachedMap.put(dir, new JarEntry(dir));
123             }
124           }
125 
126           cachedMap.put(jarEntry.getName(), jarEntry);
127         }
128         synchronized (CACHE) {
129           CACHE.put(file.getAbsoluteFile(), cachedMap);
130         }
131       }
132 
133       jarEntryMap = cachedMap;
134     }
135 
join(String folderBaseName)136     @Override public FsFile join(String folderBaseName) {
137       return new JarFsFile(folderBaseName);
138     }
139 
140     class JarFsFile implements FsFile {
141       private final String path;
142 
JarFsFile(String path)143       public JarFsFile(String path) {
144         this.path = path.replaceAll("^/+", "");
145       }
146 
exists()147       @Override public boolean exists() {
148         return isFile() || isDirectory();
149       }
150 
isDirectory()151       @Override public boolean isDirectory() {
152         return jarEntryMap.containsKey(path + "/");
153       }
154 
isFile()155       @Override public boolean isFile() {
156         return jarEntryMap.containsKey(path);
157       }
158 
listFiles()159       @Override public FsFile[] listFiles() {
160         return listFiles(fsFile -> true);
161       }
162 
listFiles(Filter filter)163       @Override public FsFile[] listFiles(Filter filter) {
164         NavigableSet<String> strings = jarEntryMap.navigableKeySet();
165         int startOfFilename = 0;
166 
167         if (!path.equals("")) {
168           if (!isDirectory()) {
169             return null;
170           }
171 
172           strings = strings.subSet(path + "/", false, path + "0", false);
173           startOfFilename = path.length() + 2;
174         }
175 
176         List<FsFile> fsFiles = new ArrayList<>();
177         for (String string : strings) {
178           int nextSlash = string.indexOf('/', startOfFilename);
179           FsFile fsFile;
180           if (nextSlash == string.length() - 1) {
181             // directory entry
182             fsFile = new JarFsFile(string.substring(0, string.length() - 1));
183           } else if (nextSlash == -1) {
184             // file entry
185             fsFile = new JarFsFile(string);
186           } else {
187             // file within a nested directory, ignore
188             fsFile = null;
189           }
190 
191           if (fsFile != null && filter.accept(fsFile)) {
192             fsFiles.add(fsFile);
193           }
194         }
195         return fsFiles.toArray(new FsFile[fsFiles.size()]);
196       }
197 
listFileNames()198       @Override public String[] listFileNames() {
199         List<String> fileNames = new ArrayList<>();
200         for (FsFile fsFile : listFiles()) {
201           fileNames.add(fsFile.getName());
202         }
203         return fileNames.toArray(new String[fileNames.size()]);
204       }
205 
getParent()206       @Override public FsFile getParent() {
207         int index = path.lastIndexOf('/');
208         String parent = index != -1 ? path.substring(0, index) : "";
209         return new JarFsFile(parent);
210       }
211 
getName()212       @Override public String getName() {
213         int index = path.lastIndexOf('/');
214         return index != -1 ? path.substring(index + 1, path.length()) : path;
215       }
216 
getInputStream()217       @Override public InputStream getInputStream() throws IOException {
218         return new BufferedInputStream(jarFile.getInputStream(jarEntryMap.get(path)));
219       }
220 
getBytes()221       @Override public byte[] getBytes() throws IOException {
222         return Util.readBytes(jarFile.getInputStream(jarEntryMap.get(path)));
223       }
224 
join(String... pathParts)225       @Override public FsFile join(String... pathParts) {
226         return new JarFsFile(path + "/" + Join.join("/", asList(pathParts)));
227       }
228 
getBaseName()229       @Override public String getBaseName() {
230         String name = getName();
231         int dotIndex = name.indexOf(".");
232         return dotIndex >= 0 ? name.substring(0, dotIndex) : name;
233       }
234 
getPath()235       @Override public String getPath() {
236         return "jar:file:" + getJarFileName() + "!/" + path;
237       }
238 
239       @Override
length()240       public long length() {
241         return jarFile.getEntry(path).getSize();
242       }
243 
244       @Override
equals(Object o)245       public boolean equals(Object o) {
246         if (this == o) return true;
247         if (o == null || getClass() != o.getClass()) return false;
248 
249         JarFsFile jarFsFile = (JarFsFile) o;
250 
251         if (!getJarFileName().equals(jarFsFile.getJarFileName())) return false;
252         if (!path.equals(jarFsFile.path)) return false;
253 
254         return true;
255       }
256 
getJarFileName()257       private String getJarFileName() {
258         return jarFile.getName();
259       }
260 
261       @Override
hashCode()262       public int hashCode() {
263         return getJarFileName().hashCode() * 31 + path.hashCode();
264       }
265 
toString()266       @Override public String toString() {
267         return getPath();
268       }
269     }
270   }
271 
join(String folderBaseName)272   abstract public FsFile join(String folderBaseName);
273 }
274