• 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           cachedMap.put(jarEntry.getName(), jarEntry);
115         }
116         synchronized (CACHE) {
117           CACHE.put(file.getAbsoluteFile(), cachedMap);
118         }
119       }
120 
121       jarEntryMap = cachedMap;
122     }
123 
join(String folderBaseName)124     @Override public FsFile join(String folderBaseName) {
125       return new JarFsFile(folderBaseName);
126     }
127 
128     class JarFsFile implements FsFile {
129       private final String path;
130 
JarFsFile(String path)131       public JarFsFile(String path) {
132         this.path = path.replaceAll("^/+", "");
133       }
134 
exists()135       @Override public boolean exists() {
136         return isFile() || isDirectory();
137       }
138 
isDirectory()139       @Override public boolean isDirectory() {
140         return jarEntryMap.containsKey(path + "/");
141       }
142 
isFile()143       @Override public boolean isFile() {
144         return jarEntryMap.containsKey(path);
145       }
146 
listFiles()147       @Override public FsFile[] listFiles() {
148         return listFiles(fsFile -> true);
149       }
150 
listFiles(Filter filter)151       @Override public FsFile[] listFiles(Filter filter) {
152         NavigableSet<String> strings = jarEntryMap.navigableKeySet();
153         int startOfFilename = 0;
154 
155         if (!path.equals("")) {
156           if (!isDirectory()) {
157             return null;
158           }
159 
160           strings = strings.subSet(path + "/", false, path + "0", false);
161           startOfFilename = path.length() + 2;
162         }
163 
164         List<FsFile> fsFiles = new ArrayList<>();
165         for (String string : strings) {
166           int nextSlash = string.indexOf('/', startOfFilename);
167           FsFile fsFile;
168           if (nextSlash == string.length() - 1) {
169             // directory entry
170             fsFile = new JarFsFile(string.substring(0, string.length() - 1));
171           } else if (nextSlash == -1) {
172             // file entry
173             fsFile = new JarFsFile(string);
174           } else {
175             // file within a nested directory, ignore
176             fsFile = null;
177           }
178 
179           if (fsFile != null && filter.accept(fsFile)) {
180             fsFiles.add(fsFile);
181           }
182         }
183         return fsFiles.toArray(new FsFile[fsFiles.size()]);
184       }
185 
listFileNames()186       @Override public String[] listFileNames() {
187         List<String> fileNames = new ArrayList<>();
188         for (FsFile fsFile : listFiles()) {
189           fileNames.add(fsFile.getName());
190         }
191         return fileNames.toArray(new String[fileNames.size()]);
192       }
193 
getParent()194       @Override public FsFile getParent() {
195         int index = path.lastIndexOf('/');
196         String parent = index != -1 ? path.substring(0, index) : "";
197         return new JarFsFile(parent);
198       }
199 
getName()200       @Override public String getName() {
201         int index = path.lastIndexOf('/');
202         return index != -1 ? path.substring(index + 1, path.length()) : path;
203       }
204 
getInputStream()205       @Override public InputStream getInputStream() throws IOException {
206         return new BufferedInputStream(jarFile.getInputStream(jarEntryMap.get(path)));
207       }
208 
getBytes()209       @Override public byte[] getBytes() throws IOException {
210         return Util.readBytes(jarFile.getInputStream(jarEntryMap.get(path)));
211       }
212 
join(String... pathParts)213       @Override public FsFile join(String... pathParts) {
214         return new JarFsFile(path + "/" + Join.join("/", asList(pathParts)));
215       }
216 
getBaseName()217       @Override public String getBaseName() {
218         String name = getName();
219         int dotIndex = name.indexOf(".");
220         return dotIndex >= 0 ? name.substring(0, dotIndex) : name;
221       }
222 
getPath()223       @Override public String getPath() {
224         return "jar:file:" + getJarFileName() + "!/" + path;
225       }
226 
227       @Override
length()228       public long length() {
229         return jarFile.getEntry(path).getSize();
230       }
231 
232       @Override
equals(Object o)233       public boolean equals(Object o) {
234         if (this == o) return true;
235         if (o == null || getClass() != o.getClass()) return false;
236 
237         JarFsFile jarFsFile = (JarFsFile) o;
238 
239         if (!getJarFileName().equals(jarFsFile.getJarFileName())) return false;
240         if (!path.equals(jarFsFile.path)) return false;
241 
242         return true;
243       }
244 
getJarFileName()245       private String getJarFileName() {
246         return jarFile.getName();
247       }
248 
249       @Override
hashCode()250       public int hashCode() {
251         return getJarFileName().hashCode() * 31 + path.hashCode();
252       }
253 
toString()254       @Override public String toString() {
255         return getPath();
256       }
257     }
258   }
259 
join(String folderBaseName)260   abstract public FsFile join(String folderBaseName);
261 }
262