    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005// http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012package org.apache.tapestry5.versionmigrator;
    013
    014import java.io.BufferedOutputStream;
    015import java.io.BufferedReader;
    016import java.io.File;
    017import java.io.FileOutputStream;
    018import java.io.IOException;
    019import java.io.InputStream;
    020import java.io.InputStreamReader;
    021import java.io.OutputStream;
    022import java.nio.file.Files;
    023import java.nio.file.Path;
    024import java.nio.file.Paths;
    025import java.util.ArrayList;
    026import java.util.Arrays;
    027import java.util.Comparator;
    028import java.util.Formatter;
    029import java.util.List;
    030import java.util.Optional;
    031import java.util.Properties;
    032import java.util.concurrent.atomic.AtomicInteger;
    033import java.util.stream.Collectors;
    034
    035import org.apache.tapestry5.versionmigrator.internal.ArtifactChangeRefactorCommitParser;
    036import org.apache.tapestry5.versionmigrator.internal.PackageAndArtifactChangeRefactorCommitParser;
    037import org.apache.tapestry5.versionmigrator.internal.PackageChangeRefactorCommitParser;
    038
    039public class Main
    040{
    041
    042    public static void main(String[] args)
    043    {
    044        if (args.length == 0)
    045        {
    046            printHelp();
    047        }
    048        else
    049        {
    050            TapestryVersion version = getTapestryVersion(args[1]);
    051            switch (args[0])
    052            {
    053                case "generate":
    054                    createVersionFile(version);
    055                    break;
    056
    057                case "upgrade":
    058                    upgrade(version);
    059                    break;
    060
    061                default:
    062                    printHelp();
    063            }
    064        }
    065    }
    066
    067    private static void upgrade(TapestryVersion version)
    068    {
    069
    070        String path = "/" + getFileRelativePath(getSimpleFileName(version));
    071        Properties properties = new Properties();
    072        try (InputStream inputStream = Main.class.getResourceAsStream(path))
    073        {
    074            properties.load(inputStream);
    075        } catch (IOException e) {
    076            throw new RuntimeException(e);
    077        }
    078
    079        List<File> sourceFiles = getJavaFiles();
    080
    081        System.out.println("Number of renamed or moved classes: " + properties.size());
    082        System.out.println("Number of source files found: " + sourceFiles.size());
    083
    084        int totalCount = 0;
    085        int totalChanged = 0;
    086        for (File file : sourceFiles)
    087        {
    088            boolean changed = upgrade(file, properties);
    089            if (changed) {
    090                totalChanged++;
    091                try {
    092                    System.out.println("Changed and upgraded file " + file.getCanonicalPath());
    093                } catch (IOException e) {
    094                    throw new RuntimeException(e);
    095                }
    096            }
    097            totalCount++;
    098            if (totalCount % 100 == 0)
    099            {
    100                System.out.printf("Processed %5d out of %d files (%.1f%%)\n",
    101                        totalCount, sourceFiles.size(), totalCount * 100.0 / sourceFiles.size());
    102            }
    103        }
    104
    105        System.out.printf("Upgrade finished successfully. %s files changed out of %s.", totalChanged, totalCount);
    106
    107    }
    108
    109    private static boolean upgrade(File file, Properties properties)
    110    {
    111        Path path = Paths.get(file.toURI());
    112        String content;
    113        boolean changed = false;
    114        try {
    115            content = new String(Files.readAllBytes(path));
    116            String newContent = content;
    117            String newClassName;
    118            for (String oldClassName : properties.stringPropertyNames())
    119            {
    120                newClassName = properties.getProperty(oldClassName);
    121                newContent = newContent.replace(oldClassName, newClassName);
    122            }
    123            if (!newContent.equals(content))
    124            {
    125                changed = true;
    126                Files.write(path, newContent.getBytes());
    127            }
    128        } catch (IOException e) {
    129            throw new RuntimeException(e);
    130        }
    131        return changed;
    132    }
    133
    134    private static List<File> getJavaFiles()
    135    {
    136        ArrayList<File> files = new ArrayList<>();
    137        collectJavaFiles(new File("."), files);
    138        return files;
    139    }
    140
    141    private static void collectJavaFiles(File currentFolder, List<File> javaFiles)
    142    {
    143        File[] javaFilesInFolder = currentFolder.listFiles((f) -> f.isFile() && (f.getName().endsWith(".java") || f.getName().endsWith(".groovy")));
    144        for (File file : javaFilesInFolder) {
    145            javaFiles.add(file);
    146        }
    147        File[] subfolders = currentFolder.listFiles((f) -> f.isDirectory());
    148        for (File subfolder : subfolders) {
    149            collectJavaFiles(subfolder, javaFiles);
    150        }
    151    }
    152
    153    private static void printHelp()
    154    {
    155        System.out.println("Apache Tapestry version migrator options:");
    156        System.out.println("\t upgrade [version number]: updates references to classes which have been moved or renamed in Java source files in the current folder and its subfolders.");
    157        System.out.println("\t generate [version number]: analyzes version control and outputs information about moved classes.");
    158        System.out.println("Apache Tapestry versions available in this tool: " +
    159                Arrays.stream(TapestryVersion.values())
    160                    .map(TapestryVersion::getNumber)
    161                    .collect(Collectors.joining(", ")));
    162    }
    163
    164    private static TapestryVersion getTapestryVersion(String versionNumber) {
    165        final TapestryVersion tapestryVersion = Arrays.stream(TapestryVersion.values())
    166            .filter(v -> versionNumber.equals(v.getNumber()))
    167            .findFirst()
    168            .orElseThrow(() -> new IllegalArgumentException("Unknown Tapestry version: " + versionNumber + ". "));
    169        return tapestryVersion;
    170    }
    171
    172    private static void createVersionFile(TapestryVersion version)
    173    {
    174        final String commandLine = String.format("git diff --summary %s %s",
    175                version.getPreviousVersionGitHash(), version.getVersionGitHash());
    176        final Process process;
    177
    178        System.out.printf("Running command line '%s'\n", commandLine);
    179        List<String> lines = new ArrayList<>();
    180        try
    181        {
    182            process = Runtime.getRuntime().exec(commandLine);
    183        } catch (IOException e) {
    184            throw new RuntimeException(e);
    185        }
    186        try (
    187            final InputStream inputStream = process.getInputStream();
    188            final InputStreamReader isr = new InputStreamReader(inputStream);
    189            final BufferedReader reader = new BufferedReader(isr))
    190        {
    191            String line = reader.readLine();
    192            while (line != null)
    193            {
    194                lines.add(line);
    195                line = reader.readLine();
    196            }
    197        } catch (IOException e) {
    198            throw new RuntimeException(e);
    199        }
    200        List<ClassRefactor> refactors = parse(lines);
    201        AtomicInteger packageChange = new AtomicInteger();
    202        AtomicInteger artifactChange = new AtomicInteger();
    203        AtomicInteger packageAndArtifactChange = new AtomicInteger();
    204
    205        refactors.stream().forEach(r -> {
    206            if (r.isMovedBetweenArtifacts() && r.isRenamed()) {
    207                packageAndArtifactChange.incrementAndGet();
    208            }
    209            if (r.isMovedBetweenArtifacts()) {
    210                artifactChange.incrementAndGet();
    211            }
    212            if (r.isRenamed()) {
    213                packageChange.incrementAndGet();
    214            }
    215        });
    216
    217        System.out.println("Stats:");
    218        System.out.printf("\t%d classes changed package or artifact\n", refactors.size());
    219        System.out.printf("\t%d classes changed packages\n", packageChange.get());
    220        System.out.printf("\t%d classes changed artifacts\n", artifactChange.get());
    221        System.out.printf("\t%d classes changed both package and artifact\n", packageAndArtifactChange.get());
    222
    223        writeVersionFile(version, refactors);
    224        writeRefactorsFile(version, refactors);
    225    }
    226
    227    private static void writeRefactorsFile(TapestryVersion version, List<ClassRefactor> refactors)
    228    {
    229        File file = getFile("change-report-" + version.getNumber() + ".html");
    230        List<ClassRefactor> sorted = new ArrayList<>(refactors);
    231        sorted.sort(Comparator.comparing(
    232                ClassRefactor::isInternal).thenComparing(
    233                        ClassRefactor::getSimpleOldClassName));
    234        try (Formatter formatter = new Formatter(file))
    235        {
    236            formatter.format("<html>");
    237            formatter.format("\t<head>");
    238            formatter.format("\t\t<title>Changes introduced in Apache Tapestry %s</title>", version.getNumber());
    239            formatter.format("\t</head>");
    240            formatter.format("\t<body>");
    241            formatter.format("\t\t<table>");
    242            formatter.format("\t\t\t<thead>");
    243            formatter.format("\t\t\t\t<th>Old class name</th>");
    244            formatter.format("\t\t\t\t<th>Renamed or moved?</th>");
    245            formatter.format("\t\t\t\t<th>New package location</th>");
    246            formatter.format("\t\t\t\t<th>Moved artifacts?</th>");
    247            formatter.format("\t\t\t\t<th>Old artifact location</th>");
    248            formatter.format("\t\t\t\t<th>New artifact location</th>");
    249            formatter.format("\t\t\t</thead>");
    250            formatter.format("\t\t\t<tbody>");
    251            sorted.stream().forEach(r -> {
    252                formatter.format("\t\t\t\t<tr>");
    253                formatter.format("\t\t\t\t\t<td>%s</td>", r.getSimpleOldClassName());
    254                boolean renamed = r.isRenamed();
    255                boolean movedBetweenArtifacts = r.isMovedBetweenArtifacts();
    256                formatter.format("\t\t\t\t\t<td>%s</td>", renamed ? "yes" : "no");
    257                formatter.format("\t\t\t\t\t<td>%s</td>", renamed ? r.getNewPackageName() : "");
    258                formatter.format("\t\t\t\t\t<td>%s</td>", movedBetweenArtifacts ? "yes" : "no");
    259                formatter.format("\t\t\t\t\t<td>%s</td>", movedBetweenArtifacts ? r.getSourceArtifact() : "");
    260                formatter.format("\t\t\t\t\t<td>%s</td>", movedBetweenArtifacts ? r.getDestinationArtifact() : "");
    261                formatter.format("\t\t\t\t\t</tr>");
    262            });
    263            formatter.format("\t\t\t</tbody>");
    264            formatter.format("\t\t</table>");
    265            formatter.format("\t</body>");
    266            formatter.format("</html>");
    267            System.out.println("Change report file successfully written to " + file.getAbsolutePath());
    268        } catch (Exception e) {
    269            throw new RuntimeException(e);
    270        }
    271    }
    272
    273    private static void writeVersionFile(TapestryVersion version, List<ClassRefactor> refactors)
    274    {
    275        Properties properties = new Properties();
    276        refactors.stream()
    277            .filter(ClassRefactor::isRenamed)
    278            .forEach(r -> properties.setProperty(r.getOldClassName(), r.getNewClassName()));
    279
    280        final File file = getChangesFile(version);
    281        try (
    282                OutputStream outputStream = new FileOutputStream(file);
    283                BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream))
    284        {
    285            properties.store(bufferedOutputStream, version.toString());
    286        } catch (Exception e) {
    287            throw new RuntimeException(e);
    288        }
    289        System.out.println("Version file successfully written to " + file.getAbsolutePath());
    290    }
    291
    292    private static File getChangesFile(TapestryVersion version) {
    293        String filename = getSimpleFileName(version);
    294        final File file = getFile(filename);
    295        return file;
    296    }
    297
    298    private static String getSimpleFileName(TapestryVersion version) {
    299        return version.getNumber() + ".properties";
    300    }
    301
    302    private static File getFile(String filename) {
    303        final String fileRelativePath = getFileRelativePath(filename);
    304        final File file = new File("src/main/resources/" + fileRelativePath);
    305        file.getParentFile().mkdirs();
    306        return file;
    307    }
    308
    309    private static String getFileRelativePath(String filename) {
    310        final String fileRelativePath =
    311                Main.class.getPackage().getName().replace('.', '/')
    312                + "/" + filename;
    313        return fileRelativePath;
    314    }
    315
    316    private static List<ClassRefactor> parse(List<String> lines)
    317    {
    318        System.out.println("Lines to process: " + lines.size());
    319
    320        lines = lines.stream()
    321            .map(s -> s.trim())
    322            .filter(s -> s.startsWith("rename"))
    323            .filter(s -> !s.contains("test"))
    324            .filter(s -> !s.contains("package-info"))
    325            .filter(s -> !s.contains("/resources/"))
    326            .filter(s -> !s.contains("/filtered-resources/"))
    327            .map(s -> s.replaceFirst("rename", "").trim())
    328            .collect(Collectors.toList());
    329
    330        List<ClassRefactor> refactors = new ArrayList<>(lines.size());
    331
    332        for (String line : lines)
    333        {
    334            PackageAndArtifactChangeRefactorCommitParser packageAndArtifactParser = new PackageAndArtifactChangeRefactorCommitParser();
    335            ArtifactChangeRefactorCommitParser artifactParser = new ArtifactChangeRefactorCommitParser();
    336            PackageChangeRefactorCommitParser packageParser = new PackageChangeRefactorCommitParser();
    337            Optional<ClassRefactor> maybeMove = packageAndArtifactParser.apply(line);
    338            if (!maybeMove.isPresent()) {
    339                maybeMove = packageParser.apply(line);
    340            }
    341            if (!maybeMove.isPresent()) {
    342                maybeMove = artifactParser.apply(line);
    343            }
    344            ClassRefactor move = maybeMove.orElseThrow(() -> new RuntimeException("Commit not handled: " + line));
    345            refactors.add(move);
    346        }
    347
    348        return refactors;
    349
    350    }
    351
    352}