|
16 | 16 |
|
17 | 17 | import static java.nio.charset.StandardCharsets.UTF_8;
|
18 | 18 |
|
| 19 | +import com.google.common.base.Strings; |
19 | 20 | import com.google.common.collect.ImmutableList;
|
20 | 21 | import com.google.common.collect.Iterables;
|
21 | 22 | import com.google.common.collect.Iterators;
|
| 23 | +import com.google.common.collect.Lists; |
22 | 24 | import com.google.common.collect.Range;
|
23 | 25 | import com.google.common.collect.RangeSet;
|
24 | 26 | import com.google.common.collect.TreeRangeSet;
|
|
31 | 33 | import com.google.googlejavaformat.Newlines;
|
32 | 34 | import com.google.googlejavaformat.Op;
|
33 | 35 | import com.google.googlejavaformat.OpsBuilder;
|
| 36 | + |
| 37 | +import java.io.File; |
34 | 38 | import java.io.IOError;
|
35 | 39 | import java.io.IOException;
|
36 | 40 | import java.net.URI;
|
| 41 | +import java.nio.file.Files; |
| 42 | +import java.nio.file.Path; |
| 43 | +import java.nio.file.Paths; |
37 | 44 | import java.util.ArrayList;
|
38 | 45 | import java.util.Collection;
|
| 46 | +import java.util.Collections; |
39 | 47 | import java.util.List;
|
| 48 | +import java.util.concurrent.CompletableFuture; |
| 49 | +import java.util.concurrent.ExecutionException; |
| 50 | +import java.util.concurrent.ExecutorService; |
| 51 | +import java.util.concurrent.Future; |
| 52 | +import java.util.concurrent.LinkedBlockingQueue; |
| 53 | +import java.util.concurrent.ThreadPoolExecutor; |
| 54 | +import java.util.concurrent.TimeUnit; |
| 55 | +import java.util.concurrent.TimeoutException; |
| 56 | +import java.util.stream.Collectors; |
| 57 | + |
40 | 58 | import org.openjdk.javax.tools.Diagnostic;
|
41 | 59 | import org.openjdk.javax.tools.DiagnosticCollector;
|
42 | 60 | import org.openjdk.javax.tools.DiagnosticListener;
|
|
86 | 104 | */
|
87 | 105 | @Immutable
|
88 | 106 | public final class Formatter {
|
| 107 | + static final ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor( |
| 108 | + Runtime.getRuntime().availableProcessors(), |
| 109 | + Math.min(Runtime.getRuntime().availableProcessors() * 2, 50), |
| 110 | + 60, |
| 111 | + TimeUnit.SECONDS, |
| 112 | + new LinkedBlockingQueue<>() |
| 113 | + ); |
89 | 114 |
|
90 | 115 | static final Range<Integer> EMPTY_RANGE = Range.closedOpen(-1, -1);
|
91 | 116 |
|
@@ -189,6 +214,134 @@ public void formatSource(CharSource input, CharSink output)
|
189 | 214 | output.write(formatSource(input.read()));
|
190 | 215 | }
|
191 | 216 |
|
| 217 | + /** |
| 218 | + * Format an input file (or dir), this method will judge the input {@code file}, if it is |
| 219 | + * A directory, then this method will scan the directory to find all of the java source files. |
| 220 | + * Then format every java file. if the {@code useExecutor} is true, then this method will use |
| 221 | + * The ExecuteService {@link Formatter#EXECUTOR_SERVICE} to submit a {@link FormatFileCallable}. |
| 222 | + * You should check the result list's element. if the element is empty, you should do not overwrite |
| 223 | + * it to source file path. |
| 224 | + * |
| 225 | + * @param file the input |
| 226 | + * @param useExecutor whether multi-thread mode |
| 227 | + * @return the formatted java source |
| 228 | + */ |
| 229 | + public List<String> formatSourceFile(String file, boolean useExecutor) |
| 230 | + throws IOException, FormatterException { |
| 231 | + if (Strings.isNullOrEmpty(file)) { |
| 232 | + return Collections.emptyList(); |
| 233 | + } |
| 234 | + File sourceFile = new File(file); |
| 235 | + if (sourceFile.isFile()) { |
| 236 | + // this is a file |
| 237 | + if (useExecutor) { |
| 238 | + CompletableFuture<List<String>> formattedFuture = CompletableFuture.supplyAsync(()->{ |
| 239 | + Path path = Paths.get(file); |
| 240 | + String formatted; |
| 241 | + try { |
| 242 | + formatted = formatSource(new String(Files.readAllBytes(path), UTF_8)); |
| 243 | + } catch (FormatterException | IOException e) { |
| 244 | + formatted = ""; |
| 245 | + } |
| 246 | + return ImmutableList.of(formatted); // do not update the file source. |
| 247 | + }, EXECUTOR_SERVICE); |
| 248 | + try { |
| 249 | + if (formattedFuture.get(1, TimeUnit.SECONDS) == null) { |
| 250 | + return Collections.emptyList(); |
| 251 | + } else { |
| 252 | + return formattedFuture.getNow(Collections.emptyList()); |
| 253 | + } |
| 254 | + } catch (InterruptedException | ExecutionException | TimeoutException e) { |
| 255 | + return Collections.emptyList(); |
| 256 | + } |
| 257 | + } else { |
| 258 | + Path path = Paths.get(file); |
| 259 | + String formatted = formatSource(new String(Files.readAllBytes(path), UTF_8)); |
| 260 | + return ImmutableList.of(formatted); // do not update the file source. |
| 261 | + } |
| 262 | + } else if (sourceFile.isDirectory()) { |
| 263 | + // this is a directory |
| 264 | + List<String> filePathLit = Lists.newArrayList(); |
| 265 | + scanDirectory(file, filePathLit); |
| 266 | + if (filePathLit.isEmpty()) { |
| 267 | + return Collections.emptyList(); |
| 268 | + } |
| 269 | + if (useExecutor) { |
| 270 | + List<CompletableFuture<String>> formattedFutureList = Lists.newArrayList(); |
| 271 | + for (String p : filePathLit) { |
| 272 | + formattedFutureList.add(CompletableFuture.supplyAsync(() -> { |
| 273 | + Path path = Paths.get(p); |
| 274 | + String formatted; |
| 275 | + try { |
| 276 | + formatted = formatSource(new String(Files.readAllBytes(path), UTF_8)); |
| 277 | + } catch (FormatterException | IOException e) { |
| 278 | + formatted = ""; |
| 279 | + } |
| 280 | + return formatted; |
| 281 | + }, EXECUTOR_SERVICE)); |
| 282 | + } |
| 283 | + // check the status. |
| 284 | + CompletableFuture<List<String>> formattedResultFuture = |
| 285 | + CompletableFuture |
| 286 | + .allOf(formattedFutureList.toArray(new CompletableFuture[formattedFutureList.size()])) |
| 287 | + .thenApply(formattedFuture -> formattedFutureList |
| 288 | + .stream() |
| 289 | + .map(CompletableFuture::join) |
| 290 | + .collect(Collectors.toList())); |
| 291 | + try { |
| 292 | + if (formattedResultFuture.get(formattedFutureList.size(), TimeUnit.SECONDS) == null) { |
| 293 | + return Collections.emptyList(); |
| 294 | + } else { |
| 295 | + return formattedResultFuture.getNow(Collections.emptyList()); |
| 296 | + } |
| 297 | + } catch (InterruptedException | ExecutionException | TimeoutException e) { |
| 298 | + return Collections.emptyList(); |
| 299 | + } |
| 300 | + } else { |
| 301 | + List<String> formattedList = Lists.newArrayList(); |
| 302 | + for (String p : filePathLit) { |
| 303 | + Path path = Paths.get(p); |
| 304 | + String formatted = formatSource(new String(Files.readAllBytes(path), UTF_8)); |
| 305 | + formattedList.add(formatted); |
| 306 | + } |
| 307 | + return formattedList; |
| 308 | + } |
| 309 | + } else { |
| 310 | + // doNothing. |
| 311 | + return Collections.emptyList(); |
| 312 | + } |
| 313 | + } |
| 314 | + |
| 315 | + /** |
| 316 | + * Scan the directory {@code directory} to find all of the java files. |
| 317 | + * |
| 318 | + * @param directory the scan directory |
| 319 | + * @param filePathList the result. |
| 320 | + */ |
| 321 | + private void scanDirectory(String directory, List<String> filePathList) { |
| 322 | + if (Strings.isNullOrEmpty(directory)) { |
| 323 | + return; |
| 324 | + } |
| 325 | + File sourceFile = new File(directory); |
| 326 | + if (sourceFile.isFile()) { |
| 327 | + // this is a file |
| 328 | + filePathList.add(directory); |
| 329 | + return; |
| 330 | + } |
| 331 | + // this is a directory. |
| 332 | + File[] fileList = sourceFile.listFiles(); |
| 333 | + if (fileList == null) { |
| 334 | + return; // no file |
| 335 | + } |
| 336 | + for (File file : fileList) { |
| 337 | + if (file.isFile()) { |
| 338 | + filePathList.add(file.getAbsolutePath()); |
| 339 | + } else if (file.isDirectory()) { |
| 340 | + scanDirectory(directory, filePathList); |
| 341 | + } |
| 342 | + } |
| 343 | + } |
| 344 | + |
192 | 345 | /**
|
193 | 346 | * Format an input string (a Java compilation unit) into an output string.
|
194 | 347 | *
|
|
0 commit comments