]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.api.acr/src/org/argeo/api/acr/fs/AbstractFsPath.java
Merge tag 'v2.3.18' into testing
[lgpl/argeo-commons.git] / org.argeo.api.acr / src / org / argeo / api / acr / fs / AbstractFsPath.java
1 package org.argeo.api.acr.fs;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.net.URI;
6 import java.net.URISyntaxException;
7 import java.nio.file.LinkOption;
8 import java.nio.file.Path;
9 import java.nio.file.WatchEvent.Kind;
10 import java.nio.file.WatchEvent.Modifier;
11 import java.nio.file.WatchKey;
12 import java.nio.file.WatchService;
13 import java.util.Arrays;
14 import java.util.Iterator;
15 import java.util.NoSuchElementException;
16
17 public abstract class AbstractFsPath<FS extends AbstractFsSystem<ST>, ST extends AbstractFsStore> implements Path {
18 private final FS fs;
19 /** null for non absolute paths */
20 private final ST fileStore;
21
22 private final String[] segments;// null means root
23 private final boolean absolute;
24
25 private final String separator;
26
27 // optim
28 private final int hashCode;
29
30 public AbstractFsPath(FS filesSystem, String path) {
31 if (path == null)
32 throw new IllegalArgumentException("Path cannot be null");
33 this.fs = filesSystem;
34 this.separator = fs.getSeparator();
35 // TODO deal with both path and separator being empty strings
36 if (path.equals(separator)) {// root
37 this.segments = null;
38 this.absolute = true;
39 this.hashCode = 0;
40 this.fileStore = fs.getBaseFileStore();
41 return;
42 } else if (path.equals("")) {// empty path
43 this.segments = new String[] { "" };
44 this.absolute = false;
45 this.hashCode = "".hashCode();
46 this.fileStore = null;
47 return;
48 }
49
50 this.absolute = path.startsWith(toStringRoot());
51
52 String trimmedPath = path.substring(absolute ? toStringRoot().length() : 0,
53 path.endsWith(separator) ? path.length() - separator.length() : path.length());
54 this.segments = trimmedPath.split(separator);
55 // clean up
56 for (int i = 0; i < this.segments.length; i++) {
57 this.segments[i] = cleanUpSegment(this.segments[i]);
58 }
59 this.hashCode = this.segments[this.segments.length - 1].hashCode();
60
61 this.fileStore = isAbsolute() ? fs.getFileStore(path) : null;
62 }
63
64 protected AbstractFsPath(FS filesSystem, ST fileStore, String[] segments, boolean absolute) {
65 this.segments = segments;
66 this.absolute = absolute;
67 this.hashCode = segments == null ? 0 : segments[segments.length - 1].hashCode();
68 this.separator = filesSystem.getSeparator();
69 // super(path, path == null ? true : absolute, filesSystem.getSeparator());
70 // assert path == null ? absolute == true : true;
71 this.fs = filesSystem;
72 // this.path = path;
73 // this.absolute = path == null ? true : absolute;
74 if (isAbsolute() && fileStore == null)
75 throw new IllegalArgumentException("Absolute path requires a file store");
76 if (!isAbsolute() && fileStore != null)
77 throw new IllegalArgumentException("A file store should not be provided for a relative path");
78 this.fileStore = fileStore;
79 assert !(absolute && fileStore == null);
80 }
81
82 protected Path retrieve(String path) {
83 return getFileSystem().getPath(path);
84 }
85
86 @Override
87 public FS getFileSystem() {
88 return fs;
89 }
90
91 public ST getFileStore() {
92 return fileStore;
93 }
94
95 @Override
96 public boolean isAbsolute() {
97 return absolute;
98 }
99
100 @Override
101 public URI toUri() {
102 try {
103 return new URI(fs.provider().getScheme(), toString(), null);
104 } catch (URISyntaxException e) {
105 throw new IllegalStateException("Cannot create URI for " + toString(), e);
106 }
107 }
108
109 @Override
110 public Path toAbsolutePath() {
111 if (isAbsolute())
112 return this;
113 // FIXME it doesn't seem right
114 return newInstance(getSegments(), true);
115 }
116
117 @Override
118 public Path toRealPath(LinkOption... options) throws IOException {
119 return this;
120 }
121
122 @Override
123 public File toFile() {
124 throw new UnsupportedOperationException();
125 }
126
127 /*
128 * PATH OPERATIONS
129 */
130 public final Path resolveSibling(Path other) {
131 if (other == null)
132 throw new NullPointerException();
133 Path parent = getParent();
134 return (parent == null) ? other : parent.resolve(other);
135 }
136
137 @Override
138 public final Path resolveSibling(String other) {
139 return resolveSibling(getFileSystem().getPath(other));
140 }
141
142 public final Path resolve(String other) {
143 return resolve(retrieve(other));
144 }
145
146 public boolean startsWith(Path other) {
147 return toString().startsWith(other.toString());
148 }
149
150 public boolean endsWith(Path other) {
151 return toString().endsWith(other.toString());
152 }
153
154 @Override
155 public Path normalize() {
156 // always normalized
157 return this;
158 }
159
160 @Override
161 public final Iterator<Path> iterator() {
162 return new Iterator<Path>() {
163 private int i = 0;
164
165 @Override
166 public boolean hasNext() {
167 return (i < getNameCount());
168 }
169
170 @Override
171 public Path next() {
172 if (i < getNameCount()) {
173 Path result = getName(i);
174 i++;
175 return result;
176 } else {
177 throw new NoSuchElementException();
178 }
179 }
180
181 @Override
182 public void remove() {
183 throw new UnsupportedOperationException();
184 }
185 };
186 }
187
188 @Override
189 public int compareTo(Path other) {
190 return toString().compareTo(other.toString());
191 }
192
193 public Path resolve(Path other) {
194 AbstractFsPath<?, ?> otherPath = (AbstractFsPath<?, ?>) other;
195 if (otherPath.isAbsolute())
196 return other;
197 String[] newPath;
198 if (isRoot()) {
199 newPath = new String[otherPath.segments.length];
200 System.arraycopy(otherPath.segments, 0, newPath, 0, otherPath.segments.length);
201 } else {
202 newPath = new String[segments.length + otherPath.segments.length];
203 System.arraycopy(segments, 0, newPath, 0, segments.length);
204 System.arraycopy(otherPath.segments, 0, newPath, segments.length, otherPath.segments.length);
205 }
206 if (!absolute)
207 return newInstance(newPath, absolute);
208 else {
209 return newInstance(toString(newPath));
210 }
211 }
212
213 public Path relativize(Path other) {
214 if (equals(other))
215 return newInstance("");
216 if (other.toString().startsWith(this.toString())) {
217 String p1 = toString();
218 String p2 = other.toString();
219 String relative = p2.substring(p1.length(), p2.length());
220 if (relative.charAt(0) == '/')
221 relative = relative.substring(1);
222 return newInstance(relative);
223 }
224 throw new IllegalArgumentException(other + " cannot be relativized against " + this);
225 }
226
227 /*
228 * FACTORIES
229 */
230 protected abstract AbstractFsPath<FS, ST> newInstance(String path);
231
232 protected abstract AbstractFsPath<FS, ST> newInstance(String[] segments, boolean absolute);
233
234 /*
235 * CUSTOMISATIONS
236 */
237 protected String toStringRoot() {
238 return separator;
239 }
240
241 protected String cleanUpSegment(String segment) {
242 return segment;
243 }
244
245 protected boolean isRoot() {
246 return segments == null;
247 }
248
249 protected boolean isEmpty() {
250 return segments.length == 1 && "".equals(segments[0]);
251 }
252
253 /*
254 * PATH OPERATIONS
255 */
256 public AbstractFsPath<FS, ST> getRoot() {
257 return newInstance(toStringRoot());
258 }
259
260 public AbstractFsPath<FS, ST> getParent() {
261 if (isRoot())
262 return null;
263 // FIXME empty path?
264 if (segments.length == 1)// first level
265 return newInstance(toStringRoot());
266 String[] parentPath = Arrays.copyOfRange(segments, 0, segments.length - 1);
267 if (!absolute)
268 return newInstance(parentPath, absolute);
269 else
270 return newInstance(toString(parentPath));
271 }
272
273 public AbstractFsPath<FS, ST> getFileName() {
274 if (isRoot())
275 return null;
276 return newInstance(segments[segments.length - 1]);
277 }
278
279 public int getNameCount() {
280 if (isRoot())
281 return 0;
282 return segments.length;
283 }
284
285 public AbstractFsPath<FS, ST> getName(int index) {
286 if (isRoot())
287 return null;
288 return newInstance(segments[index]);
289 }
290
291 public AbstractFsPath<FS, ST> subpath(int beginIndex, int endIndex) {
292 if (isRoot())
293 return null;
294 String[] parentPath = Arrays.copyOfRange(segments, beginIndex, endIndex);
295 return newInstance(parentPath, false);
296 }
297
298 public boolean startsWith(String other) {
299 return toString().startsWith(other);
300 }
301
302 public boolean endsWith(String other) {
303 return toString().endsWith(other);
304 }
305
306 /*
307 * UTILITIES
308 */
309 protected String toString(String[] path) {
310 if (isRoot())
311 return toStringRoot();
312 StringBuilder sb = new StringBuilder();
313 if (isAbsolute())
314 sb.append(separator);
315 for (int i = 0; i < path.length; i++) {
316 if (i != 0)
317 sb.append(separator);
318 sb.append(path[i]);
319 }
320 return sb.toString();
321 }
322
323 @Override
324 public String toString() {
325 return toString(segments);
326 }
327
328 @Override
329 public int hashCode() {
330 return hashCode;
331 }
332
333 @Override
334 public boolean equals(Object obj) {
335 if (!(obj instanceof AbstractFsPath))
336 return false;
337 AbstractFsPath<?, ?> other = (AbstractFsPath<?, ?>) obj;
338
339 if (isRoot()) {// root
340 if (other.isRoot())// root
341 return true;
342 else
343 return false;
344 } else {
345 if (other.isRoot())// root
346 return false;
347 }
348 // non root
349 if (segments.length != other.segments.length)
350 return false;
351 for (int i = 0; i < segments.length; i++) {
352 if (!segments[i].equals(other.segments[i]))
353 return false;
354 }
355 return true;
356 }
357
358 @Override
359 protected Object clone() throws CloneNotSupportedException {
360 return newInstance(toString());
361 }
362
363 /*
364 * GETTERS / SETTERS
365 */
366 protected String[] getSegments() {
367 return segments;
368 }
369
370 protected String getSeparator() {
371 return separator;
372 }
373
374 /*
375 * UNSUPPORTED
376 */
377 @Override
378 public WatchKey register(WatchService watcher, Kind<?>[] events, Modifier... modifiers) throws IOException {
379 throw new UnsupportedOperationException();
380 }
381
382 @Override
383 public WatchKey register(WatchService watcher, Kind<?>... events) throws IOException {
384 throw new UnsupportedOperationException();
385 }
386
387 }