1
2
3
4
5 package net.java.truevfs.kernel.spec;
6
7 import java.beans.ConstructorProperties;
8 import java.io.*;
9 import java.net.URI;
10 import java.net.URISyntaxException;
11 import javax.annotation.CheckForNull;
12 import javax.annotation.Nullable;
13 import javax.annotation.concurrent.Immutable;
14
15 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
16 import net.java.truecommons.shed.QuotedUriSyntaxException;
17 import net.java.truecommons.shed.UriBuilder;
18 import static net.java.truevfs.kernel.spec.FsUriModifier.CANONICALIZE;
19 import static net.java.truevfs.kernel.spec.FsUriModifier.NULL;
20 import static net.java.truevfs.kernel.spec.FsUriModifier.PostFix.NODE_PATH;
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138 @Immutable
139 public final class FsNodePath
140 implements Serializable, Comparable<FsNodePath> {
141
142 private static final long serialVersionUID = 5798435461242930648L;
143
144 private static final URI DOT = URI.create(".");
145
146 @SuppressFBWarnings("JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS")
147 private URI uri;
148
149 private transient @Nullable FsMountPoint mountPoint;
150
151 private transient FsNodeName nodeName;
152
153 private transient volatile @Nullable URI hierarchical;
154
155
156
157
158 public static FsNodePath
159 create(URI uri) {
160 return create(uri, NULL);
161 }
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176 public static FsNodePath
177 create(URI uri, FsUriModifier modifier) {
178 try {
179 return new FsNodePath(uri, modifier);
180 } catch (URISyntaxException ex) {
181 throw new IllegalArgumentException(ex);
182 }
183 }
184
185
186
187
188
189 public FsNodePath(File file) {
190 try {
191 parse(file.toURI(), CANONICALIZE);
192 } catch (URISyntaxException ex) {
193 throw new AssertionError(ex);
194 }
195 }
196
197
198
199
200 @ConstructorProperties("uri")
201 public FsNodePath(URI uri) throws URISyntaxException {
202 parse(uri, NULL);
203 }
204
205
206
207
208
209
210
211
212
213 public FsNodePath(URI uri, FsUriModifier modifier)
214 throws URISyntaxException {
215 parse(uri, modifier);
216 }
217
218
219
220
221
222
223
224
225 public FsNodePath(
226 final @CheckForNull FsMountPoint mountPoint,
227 final FsNodeName nodeName) {
228 URI mpu;
229 if (null == mountPoint) {
230 this.uri = nodeName.getUri();
231 } else if (nodeName.isRoot()) {
232 this.uri = mountPoint.getUri();
233 } else if ((mpu = mountPoint.getUri()).isOpaque()) {
234 try {
235
236
237 final String mpussp = mpu.getRawSchemeSpecificPart();
238 final int mpusspl = mpussp.length();
239 final URI enu = nodeName.getUri();
240 final String enup = enu.getRawPath();
241 final int enupl = enup.length();
242 final String enuq = enu.getRawQuery();
243 final int enuql = null == enuq ? 0 : enuq.length() + 1;
244 final StringBuilder ssp =
245 new StringBuilder(mpusspl + enupl + enuql)
246 .append(mpussp)
247 .append(enup);
248 if (null != enuq)
249 ssp.append('?').append(enuq);
250 this.uri = new UriBuilder(true)
251 .scheme(mpu.getScheme())
252 .path(ssp.toString())
253 .fragment(enu.getRawFragment())
254 .getUri();
255 } catch (URISyntaxException ex) {
256 throw new AssertionError(ex);
257 }
258 } else {
259 this.uri = mpu.resolve(nodeName.getUri());
260 }
261 this.mountPoint = mountPoint;
262 this.nodeName = nodeName;
263
264 assert invariants();
265 }
266
267 private void writeObject(ObjectOutputStream out)
268 throws IOException {
269 out.writeObject(uri.toString());
270 }
271
272 private void readObject(ObjectInputStream in)
273 throws IOException, ClassNotFoundException {
274 try {
275 parse(new URI(in.readObject().toString()), NULL);
276 } catch (URISyntaxException ex) {
277 throw (InvalidObjectException) new InvalidObjectException(ex.toString())
278 .initCause(ex);
279 }
280 }
281
282 private void parse(URI uri, final FsUriModifier modifier)
283 throws URISyntaxException {
284 uri = modifier.modify(uri, NODE_PATH);
285 if (null != uri.getRawFragment())
286 throw new QuotedUriSyntaxException(uri, "Fragment component not allowed");
287 if (uri.isOpaque()) {
288 final String ssp = uri.getRawSchemeSpecificPart();
289 final int i = ssp.lastIndexOf(FsMountPoint.SEPARATOR);
290 if (0 > i)
291 throw new QuotedUriSyntaxException(uri,
292 "Missing mount point separator \"" + FsMountPoint.SEPARATOR + '"');
293 final UriBuilder b = new UriBuilder(true);
294 mountPoint = new FsMountPoint(
295 b.scheme(uri.getScheme())
296 .path(ssp.substring(0, i + 2))
297 .toUri(),
298 modifier);
299 nodeName = new FsNodeName(
300 b.clear()
301 .pathQuery(ssp.substring(i + 2))
302 .fragment(uri.getRawFragment())
303 .toUri(),
304 modifier);
305 if (NULL != modifier) {
306 URI mpu = mountPoint.getUri();
307 URI nuri = new URI(mpu.getScheme() + ':' + mpu.getRawSchemeSpecificPart() + nodeName.getUri());
308 if (!uri.equals(nuri))
309 uri = nuri;
310 }
311 } else if (uri.isAbsolute()) {
312 mountPoint = new FsMountPoint(uri.resolve(DOT), modifier);
313 nodeName = new FsNodeName(mountPoint.getUri().relativize(uri), modifier);
314 } else {
315 mountPoint = null;
316 nodeName = new FsNodeName(uri, modifier);
317 if (NULL != modifier)
318 uri = nodeName.getUri();
319 }
320 this.uri = uri;
321
322 assert invariants();
323 }
324
325 private boolean invariants() {
326 assert null != getUri();
327 assert null == getUri().getRawFragment();
328 assert (null != getMountPoint()) == getUri().isAbsolute();
329 assert null != getNodeName();
330 if (getUri().isOpaque()) {
331 assert getUri().getRawSchemeSpecificPart().contains(FsMountPoint.SEPARATOR);
332
333
334
335
336
337 } else if (getUri().isAbsolute()) {
338 assert getUri().normalize() == getUri();
339 assert getUri().equals(getMountPoint().getUri().resolve(getNodeName().getUri()));
340 } else {
341 assert getUri().normalize() == getUri();
342 assert getNodeName().getUri() == getUri();
343 }
344 return true;
345 }
346
347
348
349
350
351
352 public URI getUri() { return uri; }
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367 public URI toHierarchicalUri() {
368 final URI hierarchical = this.hierarchical;
369 if (null != hierarchical) return hierarchical;
370 if (uri.isOpaque()) {
371 final URI mpu = mountPoint.toHierarchicalUri();
372 final URI enu = nodeName.getUri();
373 try {
374 return this.hierarchical = enu.toString().isEmpty()
375 ? mpu
376 : new UriBuilder(mpu, true)
377 .path(mpu.getRawPath() + FsNodeName.SEPARATOR)
378 .getUri()
379 .resolve(enu);
380 } catch (URISyntaxException ex) {
381 throw new AssertionError(ex);
382 }
383 } else {
384 return this.hierarchical = uri;
385 }
386 }
387
388
389
390
391
392
393
394 public @Nullable FsMountPoint getMountPoint() { return mountPoint; }
395
396
397
398
399
400
401
402 public FsNodeName getNodeName() { return nodeName; }
403
404
405
406
407
408
409
410 public FsNodePath
411 resolve(final FsNodeName nodeName) {
412 if (nodeName.isRoot() && null == this.uri.getQuery()) return this;
413 return new FsNodePath(
414 this.mountPoint,
415 new FsNodeName(this.nodeName, nodeName));
416 }
417
418
419
420
421
422 @Override
423 public int compareTo(FsNodePath that) {
424 return this.uri.compareTo(that.uri);
425 }
426
427
428
429
430
431
432 @Override
433 public boolean equals(@CheckForNull Object that) {
434 return this == that
435 || that instanceof FsNodePath
436 && this.uri.equals(((FsNodePath) that).uri);
437 }
438
439
440
441
442 @Override
443 public int hashCode() { return uri.hashCode(); }
444
445
446
447
448 @Override
449 public String toString() { return uri.toString(); }
450 }