com.curecomp.primefaces.migrator.PrimefacesMigration.java Source code

Java tutorial

Introduction

Here is the source code for com.curecomp.primefaces.migrator.PrimefacesMigration.java

Source

/*
 * 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);
            }
        }
    }

}