-
Notifications
You must be signed in to change notification settings - Fork 3
/
Path.java
186 lines (161 loc) · 6.92 KB
/
Path.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/*
Copyright 2022 Massimo Santini
This file is part of "Programmazione 2 @ UniMI" teaching material.
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This material is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this file. If not, see <https://www.gnu.org/licenses/>.
*/
import java.nio.file.InvalidPathException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
/** Classe immutabile che rappresenta un <em>path</em>. */
public class Path implements Iterable<String> {
/** Carattere separatore delle parti di un percorso */
public static final String SEPARATOR = ":";
/** Costante corrispondente al path radice (path assoluto, senza parti) */
public static final Path ROOT = new Path(true, Collections.emptyList());
/** Costante corrispondente al path vuoto (path relativo, senza parti) */
public static final Path EMPTY = new Path(false, Collections.emptyList());
/** Indica se il path è assouto. */
private final boolean isAbsolute;
/** Contiene le componenti del path. */
private final List<String> parts;
// RI: parts non è null, non contiene null o stringhe vuote, o che contengano il separatore
/**
* Costruisce un <em>path</em> a partire da un elenco di stringhe e dall'informazione se sia
* <em>assoluto</em> o meno.
*
* @param isAbsolute indica se il path è assoluto.
* @param parts elenco di stringhe che costiuiscono le parti del percorso.
* @throws NullPointerException se le {@code parts} è o contiene {@code null}.
* @throws InvalidPathException se una della parti è vuota, o contiene il separatore.
*/
private Path(final boolean isAbsolute, final List<String> parts) {
this.isAbsolute = isAbsolute;
this.parts = List.copyOf(parts);
for (String p : parts) {
if (p.isEmpty()) throw new InvalidPathException(p, "La componente è vuota.");
if (p.indexOf(SEPARATOR) != -1)
throw new InvalidPathException(p, "La componente contiene il separatore.");
}
}
/**
* Metodo di fabbricazione che restituisce un <em>path</em> a partire da una stringa.
*
* <p>Alcuni esempi di percorso sono:
*
* <ul>
* <li>"<samp>:A:B:C</samp>", percorso assoluto con parti <samp>A</samp>, <samp>B</samp> e
* <samp>C</samp>,
* <li>"<samp>B:C</samp>", percorso relativo con parti <samp>A</samp>, <samp>B</samp> e
* <samp>C</samp>,
* <li>"<samp>:</samp>", percorso assoluto corrispondente alla radice del filesystem,
* <li>"<samp></samp>", percorso vuoto (relativo).
* </ul>
*
* @param path la stringa che corrisponde alla rappresentazione testuale del path.
* @return il path corrispondente alla stringa.
* @throws NullPointerException se path è {@code null}
* @throws InvalidPathException se nella stringa compaiono due separatori immediatamente
* consecutivi.
*/
public static Path fromString(final String path) {
Objects.requireNonNull(path, "Il path non può essere null");
if (path.isEmpty()) return EMPTY;
final String[] parts = path.split(SEPARATOR);
if (parts.length == 0) return ROOT;
if (parts[0].isEmpty()) return new Path(true, Arrays.asList(parts).subList(1, parts.length));
return new Path(false, Arrays.asList(parts));
}
/**
* Consente di sapere se il <em>path</em> è <em>assoluto</em>.
*
* @return {@code true} sse il path è assoluto.
*/
public boolean isAbsolute() {
return isAbsolute;
}
/**
* Restituisce il prefisso di questo <em>path</em> a meno dell'ultima componente (o quello vuoto,
* se questo è vuoto).
*
* @return un path corrispondente a questo, ma privato dell'ultima componente (se presente).
*/
public Path parent() {
if (parts.isEmpty()) return this;
return new Path(isAbsolute, parts.subList(0, parts.size() - 1));
}
/**
* Restituisce l'ultima componente di questo <em>path</em>.
*
* @return l'ultima componente di questo path, o {@code null} se il path è vuoto.
*/
public String name() {
if (parts.isEmpty()) return null;
return parts.get(parts.size() - 1);
}
/**
* Risolve il <em>path</em> dato rispetto a questo.
*
* <p>Se il <em>path</em> dato come parametro è assoluto esso viene banalmente restituito, se è
* vuoto, viene invece restituito questo <em>path</em>. In ogni altro caso, questo <em>path</em>
* viene considearato una directory alla quale sono aggiunte le parti de <em>path</em> passato
* come argomento (che è relativo); il risultato è assoluto sse lo è questo <em>path</em>.
*
* <p>Per esempio, se questo <em>path</em> è <samp>:A:B</samp> e il parametro è <samp>C:D</samp>
* la risoluzione è <samp>:A:B:C:D</samp>.
*
* @param other il percorso da risolvere.
* @return il percorso risolto.
*/
public Path resolve(final Path other) {
if (Objects.requireNonNull(other, "Il path da risolvere non può essere null.").isAbsolute())
return other;
final List<String> parts = new ArrayList<>(this.parts);
parts.addAll(other.parts);
return new Path(isAbsolute, parts);
}
/**
* Costruisce un <em>path</em> relativo tra questo e quello dato.
*
* <p>La relativizzazione è l'inverso di {@link #resolve(Path)}, restituisce un <em>path</em> che
* identifica lo stesso file se risolto rispetto a questo.
*
* <p>Per esempio, se questo <em>path</em> è <samp>:A:B</samp> e il parametro è
* <samp>:A:B:C:D</samp> la relativizzazione è <samp>C:D</samp>.
*
* @param other l'altro path.
* @return il path relativizzato.
* @throws IllegalArgumentException se questo path non è assoluto, ma lo è l'argomento, o se
* l'elenco di parti di quest questo path non è (come lista) prefisso di quelle
* dell'argomento.
*/
public Path relativize(final Path other) {
Objects.requireNonNull(other, "Il path da relativizzare non può esser null.");
if (!isAbsolute() && other.isAbsolute)
throw new IllegalArgumentException(
"Non si può relativizzare un path assoluto rispetto ad un relativo.");
if (!parts.equals(other.parts.subList(0, parts.size())))
throw new IllegalArgumentException("Il percorso non ha un prefisso in comune con questo.");
return new Path(false, other.parts.subList(parts.size(), other.parts.size()));
}
@Override
public String toString() {
return (isAbsolute ? SEPARATOR : "") + String.join(SEPARATOR, parts);
}
@Override
public Iterator<String> iterator() {
return Collections.unmodifiableList(parts).iterator();
}
}