1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.spi.io;
20
21 import java.io.BufferedInputStream;
22 import java.io.BufferedOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InterruptedIOException;
26 import java.io.OutputStream;
27 import java.nio.ByteBuffer;
28 import java.nio.charset.StandardCharsets;
29 import java.nio.file.AccessDeniedException;
30 import java.nio.file.AtomicMoveNotSupportedException;
31 import java.nio.file.FileSystemException;
32 import java.nio.file.Files;
33 import java.nio.file.Path;
34 import java.nio.file.StandardCopyOption;
35 import java.nio.file.attribute.FileTime;
36 import java.util.concurrent.ThreadLocalRandom;
37 import java.util.concurrent.atomic.AtomicBoolean;
38
39 import static java.util.Objects.requireNonNull;
40
41
42
43
44
45
46
47 public class PathProcessorSupport implements PathProcessor {
48
49
50
51
52 protected static final boolean IS_WINDOWS =
53 System.getProperty("os.name", "unknown").startsWith("Windows");
54
55
56
57
58 protected static final boolean ATOMIC_MOVE =
59 Boolean.parseBoolean(System.getProperty(PathProcessor.class.getName() + "ATOMIC_MOVE", "true"));
60
61
62
63
64
65 protected static final int WINDOWS_MOVE_ATTEMPTS =
66 Math.max(1, Integer.getInteger(PathProcessor.class.getName() + "WINDOWS_MOVE_ATTEMPTS", 5));
67
68
69
70
71 protected static final long WINDOWS_MOVE_RETRY_DELAY =
72 Long.getLong(PathProcessor.class.getName() + "WINDOWS_MOVE_RETRY_DELAY", 50L);
73
74 @Override
75 public boolean setLastModified(Path path, long value) throws IOException {
76 try {
77 Files.setLastModifiedTime(path, FileTime.fromMillis(value));
78 return true;
79 } catch (FileSystemException e) {
80
81
82 if (e instanceof AccessDeniedException) {
83 throw e;
84 }
85 return false;
86 }
87 }
88
89 @Override
90 public void write(Path target, String data) throws IOException {
91 writeFile(target, p -> Files.write(p, data.getBytes(StandardCharsets.UTF_8)), false);
92 }
93
94 @Override
95 public void write(Path target, InputStream source) throws IOException {
96 writeFile(target, p -> Files.copy(source, p, StandardCopyOption.REPLACE_EXISTING), false);
97 }
98
99 @Override
100 public void writeWithBackup(Path target, String data) throws IOException {
101 writeFile(target, p -> Files.write(p, data.getBytes(StandardCharsets.UTF_8)), true);
102 }
103
104 @Override
105 public void writeWithBackup(Path target, InputStream source) throws IOException {
106 writeFile(target, p -> Files.copy(source, p, StandardCopyOption.REPLACE_EXISTING), true);
107 }
108
109
110
111
112
113
114 @FunctionalInterface
115 public interface FileWriter {
116 void write(Path path) throws IOException;
117 }
118
119
120
121
122
123
124
125
126
127
128
129
130 public void writeFile(Path target, FileWriter writer, boolean doBackup) throws IOException {
131 requireNonNull(target, "target is null");
132 requireNonNull(writer, "writer is null");
133 Path parent = requireNonNull(target.getParent(), "target must have parent");
134
135 try (CollocatedTempFile tempFile = newTempFile(target)) {
136 writer.write(tempFile.getPath());
137 if (doBackup && Files.isRegularFile(target)) {
138 Files.copy(target, parent.resolve(target.getFileName() + ".bak"), StandardCopyOption.REPLACE_EXISTING);
139 }
140 tempFile.move();
141 }
142 }
143
144 @Override
145 public long copy(Path source, Path target, ProgressListener listener) throws IOException {
146 try (InputStream in = new BufferedInputStream(Files.newInputStream(source));
147 CollocatedTempFile tempTarget = newTempFile(target);
148 OutputStream out = new BufferedOutputStream(Files.newOutputStream(tempTarget.getPath()))) {
149 long result = copy(out, in, listener);
150 tempTarget.move();
151 return result;
152 }
153 }
154
155 private long copy(OutputStream os, InputStream is, ProgressListener listener) throws IOException {
156 long total = 0L;
157 byte[] buffer = new byte[1024 * 32];
158 while (true) {
159 int bytes = is.read(buffer);
160 if (bytes < 0) {
161 break;
162 }
163
164 os.write(buffer, 0, bytes);
165
166 total += bytes;
167
168 if (listener != null && bytes > 0) {
169 try {
170 listener.progressed(ByteBuffer.wrap(buffer, 0, bytes));
171 } catch (Exception e) {
172
173 }
174 }
175 }
176
177 return total;
178 }
179
180 @Override
181 public void move(Path source, Path target) throws IOException {
182 final StandardCopyOption[] copyOption = ATOMIC_MOVE
183 ? new StandardCopyOption[] {
184 StandardCopyOption.ATOMIC_MOVE,
185 StandardCopyOption.REPLACE_EXISTING,
186 StandardCopyOption.COPY_ATTRIBUTES
187 }
188 : new StandardCopyOption[] {StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES};
189 if (IS_WINDOWS) {
190 retryingMove(source, target, copyOption);
191 } else {
192 Files.move(source, target, copyOption);
193 }
194 Files.deleteIfExists(source);
195 }
196
197
198
199 @Override
200 public TempFile newTempFile() throws IOException {
201 Path tempFile = Files.createTempFile("resolver", "tmp");
202 return new TempFile() {
203 @Override
204 public Path getPath() {
205 return tempFile;
206 }
207
208 @Override
209 public void close() throws IOException {
210 Files.deleteIfExists(tempFile);
211 }
212 };
213 }
214
215 @Override
216 public CollocatedTempFile newTempFile(Path file) throws IOException {
217 Path parent = requireNonNull(file.getParent(), "file must have parent");
218 Files.createDirectories(parent);
219 Path tempFile = parent.resolve(file.getFileName() + "."
220 + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp");
221 return new CollocatedTempFile() {
222 private final AtomicBoolean wantsMove = new AtomicBoolean(false);
223 private final StandardCopyOption[] copyOption = ATOMIC_MOVE
224 ? new StandardCopyOption[] {StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING}
225 : new StandardCopyOption[] {StandardCopyOption.REPLACE_EXISTING};
226
227 @Override
228 public Path getPath() {
229 return tempFile;
230 }
231
232 @Override
233 public void move() {
234 wantsMove.set(true);
235 }
236
237 @Override
238 public void close() throws IOException {
239 if (wantsMove.get()) {
240 if (IS_WINDOWS) {
241 retryingMove(tempFile, file, copyOption);
242 } else {
243 Files.move(tempFile, file, copyOption);
244 }
245 }
246 Files.deleteIfExists(tempFile);
247 }
248 };
249 }
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265 protected void retryingMove(Path source, Path target, StandardCopyOption[] copyOptions) throws IOException {
266 FileSystemException lastException = null;
267 for (int attempt = 0; attempt < WINDOWS_MOVE_ATTEMPTS; attempt++) {
268 try {
269 fileSystemMove(source, target, copyOptions);
270 return;
271 } catch (AtomicMoveNotSupportedException e) {
272 lastException = e;
273 break;
274 } catch (FileSystemException e) {
275
276 lastException = e;
277 try {
278 Thread.sleep(WINDOWS_MOVE_RETRY_DELAY);
279 } catch (InterruptedException ie) {
280 Thread.currentThread().interrupt();
281 InterruptedIOException interrupted =
282 new InterruptedIOException("Interrupted while moving " + source + " to " + target);
283 interrupted.initCause(ie);
284 interrupted.addSuppressed(e);
285 throw interrupted;
286 }
287 }
288 }
289 if (lastException instanceof AtomicMoveNotSupportedException) {
290 Path staged = target.resolveSibling(target.getFileName() + "."
291 + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp");
292 try {
293 classicCopy(source, staged);
294 fileSystemMove(staged, target, copyOptions);
295 return;
296 } finally {
297 Files.deleteIfExists(staged);
298 }
299 }
300 throw lastException;
301 }
302
303
304
305
306 protected void fileSystemMove(Path source, Path target, StandardCopyOption... copyOptions) throws IOException {
307 Files.move(source, target, copyOptions);
308 }
309
310
311
312
313
314 protected void classicCopy(Path source, Path target) throws IOException {
315 ByteBuffer buffer = ByteBuffer.allocate(1024 * 32);
316 byte[] array = buffer.array();
317 try (InputStream is = Files.newInputStream(source);
318 OutputStream os = Files.newOutputStream(target)) {
319 while (true) {
320 int bytes = is.read(array);
321 if (bytes < 0) {
322 break;
323 }
324 os.write(array, 0, bytes);
325 }
326 }
327 }
328 }