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.OutputStream;
26 import java.nio.ByteBuffer;
27 import java.nio.charset.StandardCharsets;
28 import java.nio.file.AccessDeniedException;
29 import java.nio.file.FileSystemException;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.StandardCopyOption;
33 import java.nio.file.attribute.FileTime;
34 import java.util.concurrent.ThreadLocalRandom;
35 import java.util.concurrent.atomic.AtomicBoolean;
36
37 import static java.util.Objects.requireNonNull;
38
39
40
41
42
43
44
45 public class PathProcessorSupport implements PathProcessor {
46
47
48
49
50 protected static final boolean IS_WINDOWS =
51 System.getProperty("os.name", "unknown").startsWith("Windows");
52
53
54
55
56 protected static final boolean ATOMIC_MOVE =
57 Boolean.parseBoolean(System.getProperty(PathProcessor.class.getName() + "ATOMIC_MOVE", "true"));
58
59 @Override
60 public boolean setLastModified(Path path, long value) throws IOException {
61 try {
62 Files.setLastModifiedTime(path, FileTime.fromMillis(value));
63 return true;
64 } catch (FileSystemException e) {
65
66
67 if (e instanceof AccessDeniedException) {
68 throw e;
69 }
70 return false;
71 }
72 }
73
74 @Override
75 public void write(Path target, String data) throws IOException {
76 writeFile(target, p -> Files.write(p, data.getBytes(StandardCharsets.UTF_8)), false);
77 }
78
79 @Override
80 public void write(Path target, InputStream source) throws IOException {
81 writeFile(target, p -> Files.copy(source, p, StandardCopyOption.REPLACE_EXISTING), false);
82 }
83
84 @Override
85 public void writeWithBackup(Path target, String data) throws IOException {
86 writeFile(target, p -> Files.write(p, data.getBytes(StandardCharsets.UTF_8)), true);
87 }
88
89 @Override
90 public void writeWithBackup(Path target, InputStream source) throws IOException {
91 writeFile(target, p -> Files.copy(source, p, StandardCopyOption.REPLACE_EXISTING), true);
92 }
93
94
95
96
97
98
99 @FunctionalInterface
100 public interface FileWriter {
101 void write(Path path) throws IOException;
102 }
103
104
105
106
107
108
109
110
111
112
113
114
115 public void writeFile(Path target, FileWriter writer, boolean doBackup) throws IOException {
116 requireNonNull(target, "target is null");
117 requireNonNull(writer, "writer is null");
118 Path parent = requireNonNull(target.getParent(), "target must have parent");
119
120 try (CollocatedTempFile tempFile = newTempFile(target)) {
121 writer.write(tempFile.getPath());
122 if (doBackup && Files.isRegularFile(target)) {
123 Files.copy(target, parent.resolve(target.getFileName() + ".bak"), StandardCopyOption.REPLACE_EXISTING);
124 }
125 tempFile.move();
126 }
127 }
128
129 @Override
130 public long copy(Path source, Path target, ProgressListener listener) throws IOException {
131 try (InputStream in = new BufferedInputStream(Files.newInputStream(source));
132 CollocatedTempFile tempTarget = newTempFile(target);
133 OutputStream out = new BufferedOutputStream(Files.newOutputStream(tempTarget.getPath()))) {
134 long result = copy(out, in, listener);
135 tempTarget.move();
136 return result;
137 }
138 }
139
140 private long copy(OutputStream os, InputStream is, ProgressListener listener) throws IOException {
141 long total = 0L;
142 byte[] buffer = new byte[1024 * 32];
143 while (true) {
144 int bytes = is.read(buffer);
145 if (bytes < 0) {
146 break;
147 }
148
149 os.write(buffer, 0, bytes);
150
151 total += bytes;
152
153 if (listener != null && bytes > 0) {
154 try {
155 listener.progressed(ByteBuffer.wrap(buffer, 0, bytes));
156 } catch (Exception e) {
157
158 }
159 }
160 }
161
162 return total;
163 }
164
165 @Override
166 public void move(Path source, Path target) throws IOException {
167 final StandardCopyOption[] copyOption = ATOMIC_MOVE
168 ? new StandardCopyOption[] {
169 StandardCopyOption.ATOMIC_MOVE,
170 StandardCopyOption.REPLACE_EXISTING,
171 StandardCopyOption.COPY_ATTRIBUTES
172 }
173 : new StandardCopyOption[] {StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES};
174 if (IS_WINDOWS) {
175 classicCopy(source, target);
176 } else {
177 Files.move(source, target, copyOption);
178 }
179 Files.deleteIfExists(source);
180 }
181
182
183
184 @Override
185 public TempFile newTempFile() throws IOException {
186 Path tempFile = Files.createTempFile("resolver", "tmp");
187 return new TempFile() {
188 @Override
189 public Path getPath() {
190 return tempFile;
191 }
192
193 @Override
194 public void close() throws IOException {
195 Files.deleteIfExists(tempFile);
196 }
197 };
198 }
199
200 @Override
201 public CollocatedTempFile newTempFile(Path file) throws IOException {
202 Path parent = requireNonNull(file.getParent(), "file must have parent");
203 Files.createDirectories(parent);
204 Path tempFile = parent.resolve(file.getFileName() + "."
205 + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp");
206 return new CollocatedTempFile() {
207 private final AtomicBoolean wantsMove = new AtomicBoolean(false);
208 private final StandardCopyOption[] copyOption = ATOMIC_MOVE
209 ? new StandardCopyOption[] {StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING}
210 : new StandardCopyOption[] {StandardCopyOption.REPLACE_EXISTING};
211
212 @Override
213 public Path getPath() {
214 return tempFile;
215 }
216
217 @Override
218 public void move() {
219 wantsMove.set(true);
220 }
221
222 @Override
223 public void close() throws IOException {
224 if (wantsMove.get()) {
225 if (IS_WINDOWS) {
226 classicCopy(tempFile, file);
227 } else {
228 Files.move(tempFile, file, copyOption);
229 }
230 }
231 Files.deleteIfExists(tempFile);
232 }
233 };
234 }
235
236
237
238
239 protected void classicCopy(Path source, Path target) throws IOException {
240 ByteBuffer buffer = ByteBuffer.allocate(1024 * 32);
241 byte[] array = buffer.array();
242 try (InputStream is = Files.newInputStream(source);
243 OutputStream os = Files.newOutputStream(target)) {
244 while (true) {
245 int bytes = is.read(array);
246 if (bytes < 0) {
247 break;
248 }
249 os.write(array, 0, bytes);
250 }
251 }
252 }
253 }