1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.store.fs; |
7 | |
8 | import java.io.IOException; |
9 | import java.io.InputStream; |
10 | import java.io.OutputStream; |
11 | import java.nio.channels.FileChannel; |
12 | import java.util.Collections; |
13 | import java.util.List; |
14 | import java.util.Map; |
15 | import org.h2.util.MathUtils; |
16 | import org.h2.util.New; |
17 | |
18 | /** |
19 | * A path to a file. It similar to the Java 7 <code>java.nio.file.Path</code>, |
20 | * but simpler, and works with older versions of Java. It also implements the |
21 | * relevant methods found in <code>java.nio.file.FileSystem</code> and |
22 | * <code>FileSystems</code> |
23 | */ |
24 | public abstract class FilePath { |
25 | |
26 | private static FilePath defaultProvider; |
27 | |
28 | private static Map<String, FilePath> providers; |
29 | |
30 | /** |
31 | * The prefix for temporary files. |
32 | */ |
33 | private static String tempRandom; |
34 | private static long tempSequence; |
35 | |
36 | /** |
37 | * The complete path (which may be absolute or relative, depending on the |
38 | * file system). |
39 | */ |
40 | protected String name; |
41 | |
42 | /** |
43 | * Get the file path object for the given path. |
44 | * Windows-style '\' is replaced with '/'. |
45 | * |
46 | * @param path the path |
47 | * @return the file path object |
48 | */ |
49 | public static FilePath get(String path) { |
50 | path = path.replace('\\', '/'); |
51 | int index = path.indexOf(':'); |
52 | registerDefaultProviders(); |
53 | if (index < 2) { |
54 | // use the default provider if no prefix or |
55 | // only a single character (drive name) |
56 | return defaultProvider.getPath(path); |
57 | } |
58 | String scheme = path.substring(0, index); |
59 | FilePath p = providers.get(scheme); |
60 | if (p == null) { |
61 | // provider not found - use the default |
62 | p = defaultProvider; |
63 | } |
64 | return p.getPath(path); |
65 | } |
66 | |
67 | private static void registerDefaultProviders() { |
68 | if (providers == null || defaultProvider == null) { |
69 | Map<String, FilePath> map = Collections.synchronizedMap( |
70 | New.<String, FilePath>hashMap()); |
71 | for (String c : new String[] { |
72 | "org.h2.store.fs.FilePathDisk", |
73 | "org.h2.store.fs.FilePathMem", |
74 | "org.h2.store.fs.FilePathMemLZF", |
75 | "org.h2.store.fs.FilePathNioMem", |
76 | "org.h2.store.fs.FilePathNioMemLZF", |
77 | "org.h2.store.fs.FilePathSplit", |
78 | "org.h2.store.fs.FilePathNio", |
79 | "org.h2.store.fs.FilePathNioMapped", |
80 | "org.h2.store.fs.FilePathZip" |
81 | }) { |
82 | try { |
83 | FilePath p = (FilePath) Class.forName(c).newInstance(); |
84 | map.put(p.getScheme(), p); |
85 | if (defaultProvider == null) { |
86 | defaultProvider = p; |
87 | } |
88 | } catch (Exception e) { |
89 | // ignore - the files may be excluded in purpose |
90 | } |
91 | } |
92 | providers = map; |
93 | } |
94 | } |
95 | |
96 | /** |
97 | * Register a file provider. |
98 | * |
99 | * @param provider the file provider |
100 | */ |
101 | public static void register(FilePath provider) { |
102 | registerDefaultProviders(); |
103 | providers.put(provider.getScheme(), provider); |
104 | } |
105 | |
106 | /** |
107 | * Unregister a file provider. |
108 | * |
109 | * @param provider the file provider |
110 | */ |
111 | public static void unregister(FilePath provider) { |
112 | registerDefaultProviders(); |
113 | providers.remove(provider.getScheme()); |
114 | } |
115 | |
116 | /** |
117 | * Get the size of a file in bytes |
118 | * |
119 | * @return the size in bytes |
120 | */ |
121 | public abstract long size(); |
122 | |
123 | /** |
124 | * Rename a file if this is allowed. |
125 | * |
126 | * @param newName the new fully qualified file name |
127 | * @param atomicReplace whether the move should be atomic, and the target |
128 | * file should be replaced if it exists and replacing is possible |
129 | */ |
130 | public abstract void moveTo(FilePath newName, boolean atomicReplace); |
131 | |
132 | /** |
133 | * Create a new file. |
134 | * |
135 | * @return true if creating was successful |
136 | */ |
137 | public abstract boolean createFile(); |
138 | |
139 | /** |
140 | * Checks if a file exists. |
141 | * |
142 | * @return true if it exists |
143 | */ |
144 | public abstract boolean exists(); |
145 | |
146 | /** |
147 | * Delete a file or directory if it exists. |
148 | * Directories may only be deleted if they are empty. |
149 | */ |
150 | public abstract void delete(); |
151 | |
152 | /** |
153 | * List the files and directories in the given directory. |
154 | * |
155 | * @return the list of fully qualified file names |
156 | */ |
157 | public abstract List<FilePath> newDirectoryStream(); |
158 | |
159 | /** |
160 | * Normalize a file name. |
161 | * |
162 | * @return the normalized file name |
163 | */ |
164 | public abstract FilePath toRealPath(); |
165 | |
166 | /** |
167 | * Get the parent directory of a file or directory. |
168 | * |
169 | * @return the parent directory name |
170 | */ |
171 | public abstract FilePath getParent(); |
172 | |
173 | /** |
174 | * Check if it is a file or a directory. |
175 | * |
176 | * @return true if it is a directory |
177 | */ |
178 | public abstract boolean isDirectory(); |
179 | |
180 | /** |
181 | * Check if the file name includes a path. |
182 | * |
183 | * @return if the file name is absolute |
184 | */ |
185 | public abstract boolean isAbsolute(); |
186 | |
187 | /** |
188 | * Get the last modified date of a file |
189 | * |
190 | * @return the last modified date |
191 | */ |
192 | public abstract long lastModified(); |
193 | |
194 | /** |
195 | * Check if the file is writable. |
196 | * |
197 | * @return if the file is writable |
198 | */ |
199 | public abstract boolean canWrite(); |
200 | |
201 | /** |
202 | * Create a directory (all required parent directories already exist). |
203 | */ |
204 | public abstract void createDirectory(); |
205 | |
206 | /** |
207 | * Get the file or directory name (the last element of the path). |
208 | * |
209 | * @return the last element of the path |
210 | */ |
211 | public String getName() { |
212 | int idx = Math.max(name.indexOf(':'), name.lastIndexOf('/')); |
213 | return idx < 0 ? name : name.substring(idx + 1); |
214 | } |
215 | |
216 | /** |
217 | * Create an output stream to write into the file. |
218 | * |
219 | * @param append if true, the file will grow, if false, the file will be |
220 | * truncated first |
221 | * @return the output stream |
222 | */ |
223 | public abstract OutputStream newOutputStream(boolean append) throws IOException; |
224 | |
225 | /** |
226 | * Open a random access file object. |
227 | * |
228 | * @param mode the access mode. Supported are r, rw, rws, rwd |
229 | * @return the file object |
230 | */ |
231 | public abstract FileChannel open(String mode) throws IOException; |
232 | |
233 | /** |
234 | * Create an input stream to read from the file. |
235 | * |
236 | * @return the input stream |
237 | */ |
238 | public abstract InputStream newInputStream() throws IOException; |
239 | |
240 | /** |
241 | * Disable the ability to write. |
242 | * |
243 | * @return true if the call was successful |
244 | */ |
245 | public abstract boolean setReadOnly(); |
246 | |
247 | /** |
248 | * Create a new temporary file. |
249 | * |
250 | * @param suffix the suffix |
251 | * @param deleteOnExit if the file should be deleted when the virtual |
252 | * machine exists |
253 | * @param inTempDir if the file should be stored in the temporary directory |
254 | * @return the name of the created file |
255 | */ |
256 | public FilePath createTempFile(String suffix, boolean deleteOnExit, |
257 | boolean inTempDir) throws IOException { |
258 | while (true) { |
259 | FilePath p = getPath(name + getNextTempFileNamePart(false) + suffix); |
260 | if (p.exists() || !p.createFile()) { |
261 | // in theory, the random number could collide |
262 | getNextTempFileNamePart(true); |
263 | continue; |
264 | } |
265 | p.open("rw").close(); |
266 | return p; |
267 | } |
268 | } |
269 | |
270 | /** |
271 | * Get the next temporary file name part (the part in the middle). |
272 | * |
273 | * @param newRandom if the random part of the filename should change |
274 | * @return the file name part |
275 | */ |
276 | protected static synchronized String getNextTempFileNamePart( |
277 | boolean newRandom) { |
278 | if (newRandom || tempRandom == null) { |
279 | tempRandom = MathUtils.randomInt(Integer.MAX_VALUE) + "."; |
280 | } |
281 | return tempRandom + tempSequence++; |
282 | } |
283 | |
284 | /** |
285 | * Get the string representation. The returned string can be used to |
286 | * construct a new object. |
287 | * |
288 | * @return the path as a string |
289 | */ |
290 | @Override |
291 | public String toString() { |
292 | return name; |
293 | } |
294 | |
295 | /** |
296 | * Get the scheme (prefix) for this file provider. |
297 | * This is similar to |
298 | * <code>java.nio.file.spi.FileSystemProvider.getScheme</code>. |
299 | * |
300 | * @return the scheme |
301 | */ |
302 | public abstract String getScheme(); |
303 | |
304 | /** |
305 | * Convert a file to a path. This is similar to |
306 | * <code>java.nio.file.spi.FileSystemProvider.getPath</code>, but may |
307 | * return an object even if the scheme doesn't match in case of the the |
308 | * default file provider. |
309 | * |
310 | * @param path the path |
311 | * @return the file path object |
312 | */ |
313 | public abstract FilePath getPath(String path); |
314 | |
315 | /** |
316 | * Get the unwrapped file name (without wrapper prefixes if wrapping / |
317 | * delegating file systems are used). |
318 | * |
319 | * @return the unwrapped path |
320 | */ |
321 | public FilePath unwrap() { |
322 | return this; |
323 | } |
324 | |
325 | } |