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.NULL;
19 import static net.java.truevfs.kernel.spec.FsUriModifier.PostFix.MOUNT_POINT;
20
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 @Immutable
134 @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
135 public final class FsMountPoint
136 implements Serializable, Comparable<FsMountPoint> {
137
138 private static final long serialVersionUID = 5723957985634276648L;
139
140
141
142
143
144
145
146 public static final String SEPARATOR = "!" + FsNodeName.SEPARATOR;
147
148 @SuppressFBWarnings("JCIP_FIELD_ISNT_FINAL_IN_IMMUTABLE_CLASS")
149 private URI uri;
150
151 private transient @Nullable FsNodePath path;
152
153 private transient volatile @Nullable FsScheme scheme;
154
155 private transient volatile @Nullable URI hierarchical;
156
157
158
159
160 public static FsMountPoint
161 create(URI uri) {
162 return create(uri, NULL);
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178 public static FsMountPoint
179 create(URI uri, FsUriModifier modifier) {
180 try {
181 return new FsMountPoint(uri, modifier);
182 } catch (URISyntaxException ex) {
183 throw new IllegalArgumentException(ex);
184 }
185 }
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201 public static FsMountPoint
202 create(FsScheme scheme, FsNodePath path) {
203 try {
204 return new FsMountPoint(scheme, path);
205 } catch (URISyntaxException ex) {
206 throw new IllegalArgumentException(ex);
207 }
208 }
209
210
211
212
213 @ConstructorProperties("uri")
214 public FsMountPoint(URI uri) throws URISyntaxException {
215 parse(uri, NULL);
216 }
217
218
219
220
221
222
223
224
225
226 public FsMountPoint(URI uri, FsUriModifier modifier)
227 throws URISyntaxException {
228 parse(uri, modifier);
229 }
230
231
232
233
234
235
236
237
238
239
240 public FsMountPoint(final FsScheme scheme,
241 final FsNodePath path)
242 throws URISyntaxException {
243 final URI pu = path.getUri();
244 if (!pu.isAbsolute())
245 throw new QuotedUriSyntaxException(pu, "Path not absolute");
246 final String penup = path.getNodeName().getUri().getPath();
247 if (0 == penup.length())
248 throw new QuotedUriSyntaxException(pu, "Empty node name");
249 this.uri = new UriBuilder(true)
250 .scheme(scheme.toString())
251 .path(pu.getScheme() + ':' + pu.getRawSchemeSpecificPart() + SEPARATOR)
252 .toUri();
253 this.scheme = scheme;
254 this.path = path;
255
256 assert invariants();
257 }
258
259 private void writeObject(ObjectOutputStream out)
260 throws IOException {
261 out.writeObject(uri.toString());
262 }
263
264 private void readObject(ObjectInputStream in)
265 throws IOException, ClassNotFoundException {
266 try {
267 parse(new URI(in.readObject().toString()), NULL);
268 } catch (URISyntaxException ex) {
269 throw (InvalidObjectException) new InvalidObjectException(ex.toString())
270 .initCause(ex);
271 }
272 }
273
274 private void parse(URI uri, final FsUriModifier modifier)
275 throws URISyntaxException {
276 uri = modifier.modify(uri, MOUNT_POINT);
277 if (null != uri.getRawQuery())
278 throw new QuotedUriSyntaxException(uri, "Query component not allowed");
279 if (null != uri.getRawFragment())
280 throw new QuotedUriSyntaxException(uri, "Fragment component not allowed");
281 if (uri.isOpaque()) {
282 final String ssp = uri.getRawSchemeSpecificPart();
283 final int i = ssp.lastIndexOf(SEPARATOR);
284 if (ssp.length() - 2 != i)
285 throw new QuotedUriSyntaxException(uri,
286 "Doesn't end with mount point separator \"" + SEPARATOR + '"');
287 path = new FsNodePath(new URI(ssp.substring(0, i)), modifier);
288 final URI pu = path.getUri();
289 if (!pu.isAbsolute())
290 throw new QuotedUriSyntaxException(uri, "Path not absolute");
291 if (0 == path.getNodeName().getPath().length())
292 throw new QuotedUriSyntaxException(uri, "Empty URI path of node name of node path");
293 if (NULL != modifier) {
294 URI nuri = new UriBuilder(true)
295 .scheme(uri.getScheme())
296 .path(pu.getScheme() + ':' + pu.getRawSchemeSpecificPart() + SEPARATOR)
297 .toUri();
298 if (!uri.equals(nuri))
299 uri = nuri;
300 }
301 } else {
302 if (!uri.isAbsolute())
303 throw new QuotedUriSyntaxException(uri, "Not absolute");
304 if (!uri.getRawPath().endsWith(FsNodeName.SEPARATOR))
305 throw new QuotedUriSyntaxException(uri,
306 "Path component doesn't end with separator \"" + FsNodeName.SEPARATOR + '"');
307 path = null;
308 }
309 this.uri = uri;
310
311 assert invariants();
312 }
313
314 private boolean invariants() {
315 assert null != getUri();
316 assert getUri().isAbsolute();
317 assert null == getUri().getRawQuery();
318 assert null == getUri().getRawFragment();
319 if (getUri().isOpaque()) {
320 assert getUri().getRawSchemeSpecificPart().endsWith(SEPARATOR);
321 final FsNodePath path = getPath();
322 assert null != path;
323 assert path.getUri().isAbsolute();
324 assert null == path.getUri().getRawFragment();
325 assert 0 != path.getNodeName().getUri().getRawPath().length();
326 } else {
327 assert getUri().normalize() == getUri();
328 assert getUri().getRawPath().endsWith(FsNodeName.SEPARATOR);
329 assert null == getPath();
330 }
331 return true;
332 }
333
334
335
336
337
338
339 public URI getUri() { return uri; }
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354 public URI toHierarchicalUri() {
355 final URI hierarchical = this.hierarchical;
356 return null != hierarchical
357 ? hierarchical
358 : (this.hierarchical = uri.isOpaque()
359 ? path.toHierarchicalUri()
360 : uri);
361 }
362
363
364
365
366
367
368 public FsScheme getScheme() {
369 final FsScheme scheme = this.scheme;
370 return null != scheme
371 ? scheme
372 : (this.scheme = FsScheme.create(uri.getScheme()));
373 }
374
375
376
377
378
379
380
381
382 public @Nullable FsNodePath getPath() { return path; }
383
384
385
386
387
388
389
390
391
392 public @Nullable FsMountPoint getParent() {
393 assert null == path || null != path.getMountPoint();
394 return null == path ? null : path.getMountPoint();
395 }
396
397
398
399
400
401
402
403 public FsNodePath resolve(FsNodeName name) {
404 return new FsNodePath(this, name);
405 }
406
407
408
409
410
411 @Override
412 public int compareTo(FsMountPoint that) {
413 return this.uri.compareTo(that.uri);
414 }
415
416
417
418
419
420
421 @Override
422 public boolean equals(@CheckForNull Object that) {
423 return this == that
424 || that instanceof FsMountPoint
425 && this.uri.equals(((FsMountPoint) that).uri);
426 }
427
428
429
430
431 @Override
432 public int hashCode() { return uri.hashCode(); }
433
434
435
436
437 @Override
438 public String toString() { return uri.toString(); }
439 }