Java tutorial
/* * Copyright 2015 Curecomp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.curecomp.primefaces.migrator; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeSet; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.apache.commons.cli.BasicParser; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.fusesource.jansi.Ansi; /** * * @author Christian Beikov */ public class PrimefacesMigration { private static final BufferedReader in; private static final String DEFAULT_SOURCE_PATTERN = ".+\\.xhtml"; private static final String DEFAULT_DEFAULT_PROMPT_ANSWER = "Y"; private static final Options OPTIONS; static { in = new BufferedReader(new InputStreamReader(System.in)); OPTIONS = new Options(); Option s = new Option("s", true, "source directory"); s.setRequired(true); OPTIONS.addOption(s); OPTIONS.addOption("p", true, "source pattern (default '" + DEFAULT_SOURCE_PATTERN + "')"); OPTIONS.addOption("q", false, "quiet, uses default values for prompts"); OPTIONS.addOption("t", false, "test write, see on the console what would be written"); OPTIONS.addOption("da", "default-answer", true, "default answer for replace prompts (default is " + DEFAULT_DEFAULT_PROMPT_ANSWER + ")"); } public static void main(String[] args) throws Exception { // Let's use some colors :) // AnsiConsole.systemInstall(); CommandLineParser cliParser = new BasicParser(); CommandLine cli = null; try { cli = cliParser.parse(OPTIONS, args); } catch (ParseException e) { printHelp(); } if (!cli.hasOption("s")) { printHelp(); } String sourcePattern; if (cli.hasOption("p")) { sourcePattern = cli.getOptionValue("p"); } else { sourcePattern = DEFAULT_SOURCE_PATTERN; } String defaultAnswer; if (cli.hasOption("default-answer")) { defaultAnswer = cli.getOptionValue("default-answer"); } else { defaultAnswer = DEFAULT_DEFAULT_PROMPT_ANSWER; } boolean defaultAnswerYes = defaultAnswer.equalsIgnoreCase("y"); boolean quiet = cli.hasOption("q"); boolean testWrite = cli.hasOption("t"); Path sourceDirectory = Paths.get(cli.getOptionValue("s")).toAbsolutePath(); // Since we use IO we will have some blocking threads hanging around int threadCount = Runtime.getRuntime().availableProcessors() * 2; ThreadPoolExecutor threadPool = new ThreadPoolExecutor(threadCount, threadCount, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); BlockingQueue<WidgetVarLocation> foundUsages = new LinkedBlockingQueue<>(); BlockingQueue<WidgetVarLocation> unusedOrAmbiguous = new LinkedBlockingQueue<>(); BlockingQueue<WidgetVarLocation> skippedUsages = new LinkedBlockingQueue<>(); List<Future<?>> futures = new ArrayList<>(); findWidgetVars(sourceDirectory, sourcePattern, threadPool).forEach(widgetVarLocation -> { // We can't really find usages of widget vars that use EL expressions :( if (widgetVarLocation.widgetVar.contains("#")) { unusedOrAmbiguous.add(widgetVarLocation); return; } try { FileActionVisitor visitor = new FileActionVisitor(sourceDirectory, sourcePattern, sourceFile -> futures.add(threadPool.submit((Callable<?>) () -> { findWidgetVarUsages(sourceFile, widgetVarLocation, foundUsages, skippedUsages, unusedOrAmbiguous); return null; }))); Files.walkFileTree(sourceDirectory, visitor); } catch (IOException ex) { throw new RuntimeException(ex); } }); awaitAll(futures); new TreeSet<>(skippedUsages).forEach(widgetUsage -> { int startIndex = widgetUsage.columnNr; int endIndex = startIndex + widgetUsage.widgetVar.length(); String relativePath = widgetUsage.location.toAbsolutePath().toString() .substring(sourceDirectory.toString().length()); String previous = replace(widgetUsage.line, startIndex, endIndex, Ansi.ansi().bold().fg(Ansi.Color.RED).a(widgetUsage.widgetVar).reset().toString()); System.out.println("Skipped " + relativePath + " at line " + widgetUsage.lineNr + " and col " + widgetUsage.columnNr + " for widgetVar '" + widgetUsage.widgetVar + "'"); System.out.println("\t" + previous); }); Map<WidgetVarLocation, List<WidgetVarLocation>> written = new HashMap<>(); new TreeSet<>(foundUsages).forEach(widgetUsage -> { WidgetVarLocation key = new WidgetVarLocation(null, widgetUsage.location, widgetUsage.lineNr, -1, null); List<WidgetVarLocation> writtenList = written.get(key); int existing = writtenList == null ? 0 : writtenList.size(); int startIndex = widgetUsage.columnNr; int endIndex = startIndex + widgetUsage.widgetVar.length(); String relativePath = widgetUsage.location.toAbsolutePath().toString() .substring(sourceDirectory.toString().length()); String next = replace(widgetUsage.line, startIndex, endIndex, Ansi.ansi().bold().fg(Ansi.Color.RED) .a("PF('" + widgetUsage.widgetVar + "')").reset().toString()); System.out .println(relativePath + " at line " + widgetUsage.lineNr + " and col " + widgetUsage.columnNr); System.out.println("\t" + next); System.out.print("Replace (Y/N)? [" + (defaultAnswerYes ? "Y" : "N") + "]: "); String input; if (quiet) { input = ""; System.out.println(); } else { try { do { input = in.readLine(); } while (input != null && !input.isEmpty() && !"y".equalsIgnoreCase(input) && !"n".equalsIgnoreCase(input)); } catch (IOException ex) { throw new RuntimeException(ex); } } if (input == null) { System.out.println("Aborted!"); } else if (input.isEmpty() && defaultAnswerYes || !input.isEmpty() && !"n".equalsIgnoreCase(input)) { System.out.println("Replaced!"); System.out.print("\t"); if (writtenList == null) { writtenList = new ArrayList<>(); written.put(key, writtenList); } writtenList.add(widgetUsage); List<String> lines; try { lines = Files.readAllLines(widgetUsage.location); } catch (IOException ex) { throw new RuntimeException(ex); } try (OutputStream os = testWrite ? new ByteArrayOutputStream() : Files.newOutputStream(widgetUsage.location); PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) { String line; for (int i = 0; i < lines.size(); i++) { int lineNr = i + 1; line = lines.get(i); if (lineNr == widgetUsage.lineNr) { int begin = widgetUsage.columnNr + (testWrite ? 0 : existing * 6); int end = begin + widgetUsage.widgetVar.length(); String newLine = replace(line, begin, end, "PF('" + widgetUsage.widgetVar + "')", false); if (testWrite) { System.out.println(newLine); } else { pw.println(newLine); } } else { if (!testWrite) { pw.println(line); } } } } catch (IOException ex) { throw new RuntimeException(ex); } } else { System.out.println("Skipped!"); } }); new TreeSet<>(unusedOrAmbiguous).forEach(widgetUsage -> { int startIndex = widgetUsage.columnNr; int endIndex = startIndex + widgetUsage.widgetVar.length(); String relativePath = widgetUsage.location.toAbsolutePath().toString() .substring(sourceDirectory.toString().length()); String previous = replace(widgetUsage.line, startIndex, endIndex, Ansi.ansi().bold().fg(Ansi.Color.RED).a(widgetUsage.widgetVar).reset().toString()); System.out.println("Skipped unused or ambiguous " + relativePath + " at line " + widgetUsage.lineNr + " and col " + widgetUsage.columnNr); System.out.println("\t" + previous); }); threadPool.shutdown(); } private static Stream<WidgetVarLocation> findWidgetVars(Path sourceDirectory, String sourcePattern, ThreadPoolExecutor threadPool) throws IOException { BlockingQueue<WidgetVarLocation> pipe = new LinkedBlockingQueue<>(); List<Future<?>> futures = new ArrayList<>(); Files.walkFileTree(sourceDirectory, new FileActionVisitor(sourceDirectory, sourcePattern, sourceFile -> futures.add(threadPool.submit(() -> { try (BufferedReader br = Files.newBufferedReader(sourceFile, StandardCharsets.UTF_8)) { int lineNr = 0; String line; while ((line = br.readLine()) != null) { lineNr++; if (line.contains("widgetVar=\"")) { int startIndex = line.indexOf("widgetVar=\"") + "widgetVar=\"".length(); int endIndex = line.indexOf('"', startIndex); String var = line.substring(startIndex, endIndex); WidgetVarLocation widgetVar = new WidgetVarLocation(var, sourceFile, lineNr, startIndex, line); pipe.add(widgetVar); } } } catch (IOException ex) { throw new RuntimeException(ex); } })))); return StreamSupport.stream(new PipeSpliterator(pipe, futures), true); } private static void findWidgetVarUsages(Path sourceFile, WidgetVarLocation widgetVarLocation, BlockingQueue<WidgetVarLocation> foundUsages, BlockingQueue<WidgetVarLocation> skippedUsages, BlockingQueue<WidgetVarLocation> unusedOrAmbiguous) throws IOException { try (BufferedReader br = Files.newBufferedReader(sourceFile, StandardCharsets.UTF_8)) { int lineNr = 0; String line; while ((line = br.readLine()) != null) { lineNr++; int startIndex = 0; int endIndex = -1; while ((startIndex = line.indexOf(widgetVarLocation.widgetVar, endIndex + 1)) > -1) { endIndex = startIndex + widgetVarLocation.widgetVar.length(); if (sourceFile.equals(widgetVarLocation.location) && lineNr == widgetVarLocation.lineNr && startIndex == widgetVarLocation.columnNr) { continue; } WidgetVarLocation usage = new WidgetVarLocation(widgetVarLocation.widgetVar, sourceFile, lineNr, startIndex, line); // Only look at lines that use the word as a whole and not just as a part if ((startIndex == 0 || !Character.isJavaIdentifierStart(line.charAt(startIndex - 1))) && (line.length() == endIndex || !Character.isJavaIdentifierPart(line.charAt(endIndex)))) { // We skip usages that occur as the last word of a line or usages that don't call methods directly if (endIndex == line.length() || endIndex < line.length() && line.charAt(endIndex) != '.') { skippedUsages.add(usage); } else { foundUsages.add(usage); } } else { skippedUsages.add(usage); } unusedOrAmbiguous.remove(widgetVarLocation); } } } } private static String replace(String original, int start, int end, String replace) { return replace(original, start, end, replace, true); } private static String replace(String original, int start, int end, String replace, boolean doTrim) { StringBuilder sb = new StringBuilder(original.length() + replace.length() + start - end); if (doTrim) { sb.append(ltrim(original.substring(0, start))); } else { sb.append(original.substring(0, start)); } sb.append(replace); if (doTrim) { sb.append(rtrim(original.substring(end, original.length()))); } else { sb.append(original.substring(end, original.length())); } return sb.toString(); } private static String ltrim(String s) { int i = 0; while (i < s.length() && Character.isWhitespace(s.charAt(i))) { i++; } return s.substring(i); } private static String rtrim(String s) { int i = s.length() - 1; while (i >= 0 && Character.isWhitespace(s.charAt(i))) { i--; } return s.substring(0, i + 1); } private static void printHelp() { final PrintWriter writer = new PrintWriter(System.out); final HelpFormatter usageFormatter = new HelpFormatter(); usageFormatter.printHelp(writer, 80, "PrimefacesMigration", "Migrates files from Primefaces version 4 to 5", OPTIONS, 10, 10, "curecomp.com"); writer.flush(); System.exit(0); } private static void awaitAll(List<Future<?>> futures) throws InterruptedException { Iterator<Future<?>> iter = futures.iterator(); while (iter.hasNext()) { Future<?> f = iter.next(); while (!f.isDone()) { TimeUnit.SECONDS.sleep(1); } } } }