You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2020/11/01 21:55:13 UTC

[tapestry-5] branch java9modules updated: TAP5-2641: first working version of migrator tool

This is an automated email from the ASF dual-hosted git repository.

thiagohp pushed a commit to branch java9modules
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git


The following commit(s) were added to refs/heads/java9modules by this push:
     new f293fff  TAP5-2641: first working version of migrator tool
f293fff is described below

commit f293fffb28e69c2571ebe257d514a5781b268a56
Author: Thiago H. de Paula Figueiredo <th...@arsmachina.com.br>
AuthorDate: Sun Nov 1 18:50:33 2020 -0300

    TAP5-2641: first working version of migrator tool
---
 tapestry-version-migrator/build.gradle             |  11 +-
 .../tapestry5/versionmigrator/ClassRefactor.java   | 135 ++++++++++
 .../versionmigrator/FileRefactorCommitParser.java  |  45 ++++
 .../org/apache/tapestry5/versionmigrator/Main.java | 280 +++++++++++++++++++--
 .../tapestry5/versionmigrator/TapestryVersion.java |   5 +
 .../ArtifactChangeRefactorCommitParser.java        |  49 ++++
 ...ckageAndArtifactChangeRefactorCommitParser.java |  55 ++++
 .../PackageChangeRefactorCommitParser.java         |  57 +++++
 .../tapestry5/versionmigrator/5.7.0.properties     | 129 ++++++++++
 .../versionmigrator/change-report-5.7.0.html       |   1 +
 .../versionmigrator/ClassRefactorTest.java         | 112 +++++++++
 .../ArtifactChangeRefactorCommitParserTest.java    |  41 +++
 ...eAndArtifactChangeRefactorCommitParserTest.java |  40 +++
 .../PackageChangeRefactorCommitParserTest.java     |  40 +++
 .../test-sources/ClassAtRootFolder.java            |  11 +
 .../subfolder/subsubfolder/ClassAtSubFolder.java   |  15 ++
 16 files changed, 997 insertions(+), 29 deletions(-)

diff --git a/tapestry-version-migrator/build.gradle b/tapestry-version-migrator/build.gradle
index 50ffffe..8769d68 100644
--- a/tapestry-version-migrator/build.gradle
+++ b/tapestry-version-migrator/build.gradle
@@ -1,14 +1,17 @@
 description = "Tool to help migrate source code using Tapestry from one version to another. Initially built for 5.7.0"
 
 dependencies {
+    testCompile group: 'org.testng', name: 'testng', version: '7.3.0'
 }
 
 test {
-    useJUnit()
+    useTestNG() {
+        useDefaultListeners = true // Tells TestNG to execute its default reporting structure
+    }
 }
 
-/*jar {
+jar {
     manifest {
-        attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.json.modules.JSONModule'
+        attributes 'Main-Class': 'org.apache.tapestry5.versionmigrator.Main'
     }
-}*/
+}
diff --git a/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/ClassRefactor.java b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/ClassRefactor.java
new file mode 100644
index 0000000..5cd2e9c
--- /dev/null
+++ b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/ClassRefactor.java
@@ -0,0 +1,135 @@
+// 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 org.apache.tapestry5.versionmigrator;
+
+/**
+ * Class that represents information about one class being renamed and/or moved
+ * between artifacts (JARs) and/or packages.
+ */
+final public class ClassRefactor 
+{
+    
+    final private String newClassName;
+    final private String oldClassName;
+    final private String sourceArtifact;
+    final private String destinationArtifact;
+    
+    /**
+     * Constructor for classes being moved from one artifact to another
+     * and possibly being renamed or moved between packages.
+     */
+    public ClassRefactor(String newClassName, String oldClassName, String sourceArtifact, String destinationArtifact) 
+    {
+        super();
+        verifyNotBlank("newClassName", newClassName);
+        verifyNotBlank("oldClassName", oldClassName);
+        verifyNotBlank("sourceArtifact", sourceArtifact);
+        verifyNotBlank("destinationArtifact", destinationArtifact);
+        this.newClassName = newClassName;
+        this.oldClassName = oldClassName;
+        this.sourceArtifact = sourceArtifact;
+        this.destinationArtifact = destinationArtifact;
+    }
+    
+    /**
+     * Returns the new fully-qualified class name.
+     */
+    public String getNewClassName() 
+    {
+        return newClassName;
+    }
+    
+    /**
+     * Returns the old fully-qualified class name.
+     */
+    public String getOldClassName() 
+    {
+        return oldClassName;
+    }
+    
+    /**
+     * Returns the artifact where the class was located.
+     */
+    public String getSourceArtifact() 
+    {
+        return sourceArtifact;
+    }
+    
+    /**
+     * Returns the artifact where the class is now located.
+     */
+    public String getDestinationArtifact() 
+    {
+        return destinationArtifact;
+    }
+    
+    /**
+     * Returns whether the class was moved between artifacts.
+     */
+    public boolean isMovedBetweenArtifacts() 
+    {
+        return !sourceArtifact.equals(destinationArtifact);
+    }
+
+    /**
+     * Returns whether the class had its fully qualified class name changed.
+     * This includes package changes.
+     */
+    public boolean isRenamed() 
+    {
+        return !oldClassName.equals(newClassName);
+    }
+
+    @Override
+    public String toString() {
+        return "ClassMoveInformation [newClassName=" + newClassName + ", oldClassName=" + oldClassName + ", sourceArtifact=" + sourceArtifact
+                + ", destinationArtifact=" + destinationArtifact + "]";
+    }
+ 
+    final static boolean isNotBlank(String string)
+    {
+        return string != null && string.trim().length() > 0;
+    }
+    
+    final static void verifyNotBlank(String parameterName, String parameterValue)
+    {
+        if (!isNotBlank(parameterValue))
+        {
+            throw new IllegalArgumentException(
+                    String.format("Parameter %s cannot be null nor blank", parameterName));
+        }
+    }
+ 
+    /**
+     * Returns the simple old class name.
+     */
+    public String getSimpleOldClassName() {
+        return oldClassName.substring(oldClassName.lastIndexOf(".") + 1);
+    }
+    
+    /**
+     * Returns whether the class is internal or not.
+     */
+    public boolean isInternal()
+    {
+        return oldClassName.contains(".internal.");
+    }
+    
+    /**
+     * Returns the new package location.
+     */
+    public String getNewPackageName() 
+    {
+        return newClassName.substring(0, newClassName.lastIndexOf("."));
+    }
+
+}
\ No newline at end of file
diff --git a/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/FileRefactorCommitParser.java b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/FileRefactorCommitParser.java
new file mode 100644
index 0000000..f6bd9c9
--- /dev/null
+++ b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/FileRefactorCommitParser.java
@@ -0,0 +1,45 @@
+// 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 org.apache.tapestry5.versionmigrator;
+
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.regex.Pattern;
+
+/**
+ * Interface that defines a file refactor parser.
+ */
+public interface FileRefactorCommitParser extends Function<String, Optional<ClassRefactor>> 
+{
+    /**
+     * Extracts a package or class name from a string which may contain <code>src/main/java/</code>
+     * or <code>src/test/java/</code>
+     */
+    static String extractPackageOrClassName(String string) 
+    {
+        return string
+                .replace("src/main/java/", "")
+                .replace("src/test/java/", "")
+                .replace("/", ".");
+    }
+    
+    /**
+     * Builds a class name given some parts.
+     */
+    static String buildClassName(final String rootPackageName, String packageNameSuffix, final String className) 
+    {
+        return (rootPackageName + packageNameSuffix.replace("/", ".") + "." + className.replace("/", "."))
+                .replaceAll(Pattern.quote(".."), ".");
+    }
+
+    
+}
diff --git a/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/Main.java b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/Main.java
index b1b07dc..48dc5ba 100644
--- a/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/Main.java
+++ b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/Main.java
@@ -11,21 +11,31 @@
 // limitations under the License.
 package org.apache.tapestry5.versionmigrator;
 
+import java.io.BufferedOutputStream;
 import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.StringReader;
-import java.io.StringWriter;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
+import java.util.Comparator;
+import java.util.Formatter;
 import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
+import org.apache.tapestry5.versionmigrator.internal.ArtifactChangeRefactorCommitParser;
+import org.apache.tapestry5.versionmigrator.internal.PackageAndArtifactChangeRefactorCommitParser;
+import org.apache.tapestry5.versionmigrator.internal.PackageChangeRefactorCommitParser;
+
 public class Main 
 {
 
@@ -37,10 +47,15 @@ public class Main
         }
         else 
         {
+            TapestryVersion version = getTapestryVersion(args[1]);
             switch (args[0])
             {
                 case "generate": 
-                    createVersionFile(args[1]);
+                    createVersionFile(version);
+                    break;
+
+                case "upgrade": 
+                    upgrade(version);
                     break;
                     
                 default:
@@ -49,26 +64,116 @@ public class Main
         }
     }
     
-    private static void printHelp() {
-        // TODO Auto-generated method stub
+    private static void upgrade(TapestryVersion version) 
+    {
+        
+        String path = "/" + getFileRelativePath(getSimpleFileName(version));
+        Properties properties = new Properties();
+        try (InputStream inputStream = Main.class.getResourceAsStream(path))
+        {
+            properties.load(inputStream);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+
+        List<File> javaFiles = getJavaFiles();
+        
+        System.out.println("Number of renamed or moved classes: " + properties.size());
+        System.out.println("Number of Java source files found: " + javaFiles.size());
+        
+        int totalCount = 0;
+        int totalChanged = 0;
+        for (File file : javaFiles) 
+        {
+            boolean changed = upgrade(file, properties);
+            if (changed) {
+                totalChanged++;
+                try {
+                    System.out.println("Changed and upgraded file " + file.getCanonicalPath());
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            totalCount++;
+            if (totalCount % 100 == 0)
+            {
+                System.out.printf("Processed %5d out of %d files (%.1f%%)\n", 
+                        totalCount, javaFiles.size(), totalCount * 100.0 / javaFiles.size());
+            }
+        }
+        
+        System.out.printf("Upgrade finished successfully. %s files changed out of %s.", totalChanged, totalCount);
         
     }
+    
+    private static boolean upgrade(File file, Properties properties)
+    {
+        Path path = Paths.get(file.toURI());
+        String content;
+        boolean changed = false;
+        try {
+            content = new String(Files.readAllBytes(path));
+            String newContent = content;
+            String newClassName;
+            for (String oldClassName : properties.stringPropertyNames()) 
+            {
+                newClassName = properties.getProperty(oldClassName);
+                newContent = newContent.replace(oldClassName, newClassName);
+            }
+            if (!newContent.equals(content))
+            {
+                changed = true;
+                Files.write(path, newContent.getBytes());
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return changed;
+    }
+    
+    private static List<File> getJavaFiles() 
+    {
+        ArrayList<File> files = new ArrayList<>();
+        collectJavaFiles(new File("."), files);
+        return files;
+    }
+    
+    private static void collectJavaFiles(File currentFolder, List<File> javaFiles) 
+    {
+        File[] javaFilesInFolder = currentFolder.listFiles((f) -> f.isFile() && f.getName().endsWith(".java"));
+        for (File file : javaFilesInFolder) {
+            javaFiles.add(file);
+        }
+        File[] subfolders = currentFolder.listFiles((f) -> f.isDirectory());
+        for (File subfolder : subfolders) {
+            collectJavaFiles(subfolder, javaFiles);
+        }
+    }
 
-    private static void createVersionFile(String versionNumber) 
+    private static void printHelp() 
     {
+        System.out.println("Apache Tapestry version migrator options:");
+        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.");
+        System.out.println("\t generate [version number]: analyzes version control and outputs information about moved classes.");
+        System.out.println("Apache Tapestry versions available in this tool: " + 
+                Arrays.stream(TapestryVersion.values())
+                    .map(TapestryVersion::getNumber)
+                    .collect(Collectors.joining(", ")));
+    }
+
+    private static TapestryVersion getTapestryVersion(String versionNumber) {
         final TapestryVersion tapestryVersion = Arrays.stream(TapestryVersion.values())
             .filter(v -> versionNumber.equals(v.getNumber()))
             .findFirst()
             .orElseThrow(() -> new IllegalArgumentException("Unknown Tapestry version: " + versionNumber + ". "));
-        createVersionFile(tapestryVersion);
+        return tapestryVersion;
     }
-
+    
     private static void createVersionFile(TapestryVersion version) 
     {
         final String commandLine = String.format("git diff --summary %s %s", 
                 version.getPreviousVersionGitHash(), version.getVersionGitHash());
         final Process process;
-        final Map<String, String> renames = new HashMap<>();
         
         System.out.printf("Running command line '%s'\n", commandLine);
         List<String> lines = new ArrayList<>();
@@ -92,31 +197,156 @@ public class Main
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
-        process(lines, renames);
+        List<ClassRefactor> refactors = parse(lines);
+        AtomicInteger packageChange = new AtomicInteger();
+        AtomicInteger artifactChange = new AtomicInteger();
+        AtomicInteger packageAndArtifactChange = new AtomicInteger();
+        
+        refactors.stream().forEach(r -> {
+            if (r.isMovedBetweenArtifacts() && r.isRenamed()) {
+                packageAndArtifactChange.incrementAndGet();
+            }
+            if (r.isMovedBetweenArtifacts()) {
+                artifactChange.incrementAndGet();
+            }
+            if (r.isRenamed()) {
+                packageChange.incrementAndGet();
+            }
+        });
+        
+        System.out.println("Stats:");
+        System.out.printf("\t%d classes changed package or artifact\n", refactors.size()); 
+        System.out.printf("\t%d classes changed packages\n", packageChange.get()); 
+        System.out.printf("\t%d classes changed artifacts\n", artifactChange.get()); 
+        System.out.printf("\t%d classes changed both package and artifact\n", packageAndArtifactChange.get()); 
+        
+        writeVersionFile(version, refactors);
+        writeRefactorsFile(version, refactors);
+    }
+    
+    private static void writeRefactorsFile(TapestryVersion version, List<ClassRefactor> refactors) 
+    {
+        File file = getFile("change-report-" + version.getNumber() + ".html");
+        List<ClassRefactor> sorted = new ArrayList<>(refactors);
+        sorted.sort(Comparator.comparing(
+                ClassRefactor::isInternal).thenComparing(
+                        ClassRefactor::getSimpleOldClassName));
+        try (Formatter formatter = new Formatter(file))
+        {
+            formatter.format("<html>");
+            formatter.format("\t<head>");
+            formatter.format("\t\t<title>Changes introduced in Apache Tapestry %s</title>", version.getNumber());
+            formatter.format("\t</head>");
+            formatter.format("\t<body>");
+            formatter.format("\t\t<table>");
+            formatter.format("\t\t\t<thead>");
+            formatter.format("\t\t\t\t<th>Old class name</th>");
+            formatter.format("\t\t\t\t<th>Renamed or moved?</th>");
+            formatter.format("\t\t\t\t<th>New package location</th>");
+            formatter.format("\t\t\t\t<th>Moved artifacts?</th>");
+            formatter.format("\t\t\t\t<th>Old artifact location</th>");            
+            formatter.format("\t\t\t\t<th>New artifact location</th>");
+            formatter.format("\t\t\t</thead>");
+            formatter.format("\t\t\t<tbody>");
+            sorted.stream().forEach(r -> {
+                formatter.format("\t\t\t\t<tr>");
+                formatter.format("\t\t\t\t\t<td>%s</td>", r.getSimpleOldClassName());
+                boolean renamed = r.isRenamed();
+                boolean movedBetweenArtifacts = r.isMovedBetweenArtifacts();
+                formatter.format("\t\t\t\t\t<td>%s</td>", renamed ? "yes" : "no");
+                formatter.format("\t\t\t\t\t<td>%s</td>", renamed ? r.getNewPackageName() : "");
+                formatter.format("\t\t\t\t\t<td>%s</td>", movedBetweenArtifacts ? "yes" : "no");
+                formatter.format("\t\t\t\t\t<td>%s</td>", movedBetweenArtifacts ? r.getSourceArtifact() : "");
+                formatter.format("\t\t\t\t\t<td>%s</td>", movedBetweenArtifacts ? r.getDestinationArtifact() : "");
+                formatter.format("\t\t\t\t\t</tr>");
+            });
+            formatter.format("\t\t\t</tbody>");
+            formatter.format("\t\t</table>");            
+            formatter.format("\t</body>");            
+            formatter.format("</html>");
+            System.out.println("Change report file successfully written to " + file.getAbsolutePath());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static void writeVersionFile(TapestryVersion version, List<ClassRefactor> refactors) 
+    {
+        Properties properties = new Properties();
+        refactors.stream()
+            .filter(ClassRefactor::isRenamed)
+            .forEach(r -> properties.setProperty(r.getOldClassName(), r.getNewClassName()));
+        
+        final File file = getChangesFile(version);
+        try (
+                OutputStream outputStream = new FileOutputStream(file);
+                BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream))
+        {
+            properties.store(bufferedOutputStream, version.toString());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        System.out.println("Version file successfully written to " + file.getAbsolutePath());
+    }
+
+    private static File getChangesFile(TapestryVersion version) {
+        String filename = getSimpleFileName(version);
+        final File file = getFile(filename);
+        return file;
+    }
+
+    private static String getSimpleFileName(TapestryVersion version) {
+        return version.getNumber() + ".properties";
+    }
+
+    private static File getFile(String filename) {
+        final String fileRelativePath = getFileRelativePath(filename);
+        final File file = new File("src/main/resources/" + fileRelativePath);
+        file.getParentFile().mkdirs();
+        return file;
     }
 
-    private static void process(List<String> lines, Map<String, String> renames) {
+    private static String getFileRelativePath(String filename) {
+        final String fileRelativePath = 
+                Main.class.getPackage().getName().replace('.', '/')
+                + "/" + filename;
+        return fileRelativePath;
+    }
+
+    private static List<ClassRefactor> parse(List<String> lines) 
+    {
+        System.out.println("Lines to process: " + lines.size());
+        
         lines = lines.stream()
             .map(s -> s.trim())
             .filter(s -> s.startsWith("rename"))
             .filter(s -> !s.contains("test"))
             .filter(s -> !s.contains("package-info"))
+            .filter(s -> !s.contains("/resources/"))
+            .filter(s -> !s.contains("/filtered-resources/"))            
             .map(s -> s.replaceFirst("rename", "").trim())
             .collect(Collectors.toList());
         
-        for (String line : lines) {
-            if (line.startsWith("{")) {
-                
+        List<ClassRefactor> refactors = new ArrayList<>(lines.size());
+
+        for (String line : lines) 
+        {
+            PackageAndArtifactChangeRefactorCommitParser packageAndArtifactParser = new PackageAndArtifactChangeRefactorCommitParser();
+            ArtifactChangeRefactorCommitParser artifactParser = new ArtifactChangeRefactorCommitParser();
+            PackageChangeRefactorCommitParser packageParser = new PackageChangeRefactorCommitParser();
+            Optional<ClassRefactor> maybeMove = packageAndArtifactParser.apply(line);
+            if (!maybeMove.isPresent()) {
+                maybeMove = packageParser.apply(line);
             }
-            else {
-                Pattern pattern = Pattern.compile("([^/]*)" + Pattern.quote("/") + "(.*)" + Pattern.quote("{") + "(.*)\\s=>\\s(.*)" + Pattern.quote("}/") + "([^\\.]*).*");
-                
-                final Matcher matcher = pattern.matcher(line);
-                if (matcher.matches()) {
-                    System.out.printf("%s %s %s %s %s\n", matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4), matcher.group(5));
-                }
+            if (!maybeMove.isPresent()) {
+                maybeMove = artifactParser.apply(line);
             }
+            ClassRefactor move = maybeMove.orElseThrow(() -> new RuntimeException("Commit not handled: " + line));
+            refactors.add(move);
         }
+        
+        return refactors;
+        
     }
 
 }
diff --git a/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/TapestryVersion.java b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/TapestryVersion.java
index ea316b7..3a01e41 100644
--- a/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/TapestryVersion.java
+++ b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/TapestryVersion.java
@@ -55,5 +55,10 @@ public enum TapestryVersion
     {
         return versionGitHash;
     }
+    
+    public String toString() 
+    {
+        return String.format("Apache Tapestry %s", getNumber());
+    }
 
 }
\ No newline at end of file
diff --git a/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/internal/ArtifactChangeRefactorCommitParser.java b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/internal/ArtifactChangeRefactorCommitParser.java
new file mode 100644
index 0000000..d51e1ef
--- /dev/null
+++ b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/internal/ArtifactChangeRefactorCommitParser.java
@@ -0,0 +1,49 @@
+// 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 org.apache.tapestry5.versionmigrator.internal;
+
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.tapestry5.versionmigrator.ClassRefactor;
+import org.apache.tapestry5.versionmigrator.FileRefactorCommitParser;
+
+/**
+ * Parses lines like this, in which just the artifact is changed:
+ * <code>{tapestry-ioc => tapestry5-annotations}/src/main/java/org/apache/tapestry5/ioc/annotations/Advise.java (100%)</code>. 
+ */
+public class ArtifactChangeRefactorCommitParser implements FileRefactorCommitParser {
+
+    final public static String EXAMPLE = "{tapestry-ioc => tapestry5-annotations}/src/main/java/org/apache/tapestry5/ioc/annotations/Advise.java";
+    
+    final private static Pattern PATTERN = 
+            Pattern.compile("\\{(.*)\\s=>\\s([^}]*)}\\/([^\\.]*).*");
+    
+    @Override
+    public Optional<ClassRefactor> apply(String line) {
+        final Matcher matcher = PATTERN.matcher(line);
+        ClassRefactor move = null;
+        if (matcher.matches()) 
+        {
+//            System.out.printf("1(%s) 2(%s) 3(%s)\n", otherMatcher.group(1), otherMatcher.group(2), otherMatcher.group(3));
+            final String className = FileRefactorCommitParser.extractPackageOrClassName(matcher.group(3));
+            final String sourceArtifactName = matcher.group(1);
+            final String destinationArtifactName = matcher.group(2);
+            
+            move  = new ClassRefactor(className, className, sourceArtifactName, destinationArtifactName);
+        }
+        return Optional.ofNullable(move);
+    }
+
+}
diff --git a/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/internal/PackageAndArtifactChangeRefactorCommitParser.java b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/internal/PackageAndArtifactChangeRefactorCommitParser.java
new file mode 100644
index 0000000..e8598a9
--- /dev/null
+++ b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/internal/PackageAndArtifactChangeRefactorCommitParser.java
@@ -0,0 +1,55 @@
+// 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 org.apache.tapestry5.versionmigrator.internal;
+
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.tapestry5.versionmigrator.ClassRefactor;
+import org.apache.tapestry5.versionmigrator.FileRefactorCommitParser;
+
+/**
+ * Parses lines like this, in which both artifact and package are changed:
+ * <code>{tapestry-ioc/src/main/java/org/apache/tapestry5/ioc => commons/src/main/java/org/apache/tapestry5/commons}/ObjectProvider.java</code>. 
+ */
+public class PackageAndArtifactChangeRefactorCommitParser implements FileRefactorCommitParser {
+
+    final public static String EXAMPLE = "{tapestry-ioc/src/main/java/org/apache/tapestry5/ioc => commons/src/main/java/org/apache/tapestry5/commons}/ObjectProvider.java";
+    
+    final private static Pattern PATTERN = 
+            Pattern.compile("\\{([^\\/]+)\\/([^\\s]+)\\s=>\\s([^\\/]+)\\/([^}]+)}([^\\.]+).*");
+    
+    @Override
+    public Optional<ClassRefactor> apply(String line) 
+    {        
+        ClassRefactor move = null;
+          final Matcher matcher = PATTERN.matcher(line);
+          if (matcher.matches()) 
+          {
+//              System.out.printf("1(%s) 2(%s) 3(%s) 4(%s) 5(%s)\n", matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4), matcher.group(5));
+              String newPackageNameSuffix = FileRefactorCommitParser.extractPackageOrClassName(matcher.group(4));
+              String oldPackageNameSuffix = FileRefactorCommitParser.extractPackageOrClassName(matcher.group(2));
+              final String className = matcher.group(5);
+              final String newClassName = FileRefactorCommitParser.buildClassName("", newPackageNameSuffix, className);
+              final String oldClassName = FileRefactorCommitParser.buildClassName("", oldPackageNameSuffix, className);
+              final String sourceArtifactName = matcher.group(1);
+              final String destinationArtifactName = matcher.group(3);
+              
+              move  = new ClassRefactor(newClassName, oldClassName, sourceArtifactName, destinationArtifactName);
+          }
+        return Optional.ofNullable(move);
+
+    }
+
+}
diff --git a/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/internal/PackageChangeRefactorCommitParser.java b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/internal/PackageChangeRefactorCommitParser.java
new file mode 100644
index 0000000..d751632
--- /dev/null
+++ b/tapestry-version-migrator/src/main/java/org/apache/tapestry5/versionmigrator/internal/PackageChangeRefactorCommitParser.java
@@ -0,0 +1,57 @@
+// 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 org.apache.tapestry5.versionmigrator.internal;
+
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.tapestry5.versionmigrator.ClassRefactor;
+import org.apache.tapestry5.versionmigrator.FileRefactorCommitParser;
+
+/**
+ * Parses lines like this, in which just the package is changed:
+ * <code>commons/src/main/java/org/apache/tapestry5/{ioc => commons}/Messages.java (98%)</code>. 
+ */
+public class PackageChangeRefactorCommitParser implements FileRefactorCommitParser {
+
+    final public static String EXAMPLE = "commons/src/main/java/org/apache/tapestry5/{ioc => commons}/Messages.java (98%)";
+    
+    final private static Pattern PATTERN = 
+            Pattern.compile("([^/]*)/(.*)" + Pattern.quote("{") + "(.*)\\s=>\\s(.*)" + Pattern.quote("}/") + "([^\\.]*).*");
+    
+    @Override
+    public Optional<ClassRefactor> apply(String line) 
+    {
+        final Matcher matcher = PATTERN.matcher(line);
+        ClassRefactor move = null;
+        if (matcher.matches()) 
+        {
+            String newPackageNameSuffix = matcher.group(4);
+            String oldPackageNameSuffix = matcher.group(3);
+//                    System.out.printf("1(%s) 2(%s) 3(%s) 4(%s) 5(%s)\n", matcher.group(1), matcher.group(2), oldPackageNameSuffix, newPackageNameSuffix, matcher.group(5));
+            
+            final String rootPackageName = matcher.group(2)
+                    .replace("src/main/java/", "")
+                    .replace("/", ".");
+            final String className = matcher.group(5);
+            final String newClassName = FileRefactorCommitParser.buildClassName(rootPackageName, newPackageNameSuffix, className);
+            final String oldClassName = FileRefactorCommitParser.buildClassName(rootPackageName, oldPackageNameSuffix, className);
+            final String artifactName = matcher.group(1);
+            
+            move = new ClassRefactor(newClassName, oldClassName, artifactName, artifactName);
+        }
+        return Optional.ofNullable(move);
+    }
+
+}
diff --git a/tapestry-version-migrator/src/main/resources/org/apache/tapestry5/versionmigrator/5.7.0.properties b/tapestry-version-migrator/src/main/resources/org/apache/tapestry5/versionmigrator/5.7.0.properties
new file mode 100644
index 0000000..27b8719
--- /dev/null
+++ b/tapestry-version-migrator/src/main/resources/org/apache/tapestry5/versionmigrator/5.7.0.properties
@@ -0,0 +1,129 @@
+#Apache Tapestry 5.7.0
+#Sun Nov 01 10:43:36 BRT 2020
+org.apache.tapestry5.Link=org.apache.tapestry5.http.Link
+org.apache.tapestry5.ioc.internal.services.CompoundCoercion=org.apache.tapestry5.commons.internal.services.CompoundCoercion
+org.apache.tapestry5.internal.hibernate.PersistedTransientEntity=org.apache.tapestry5.hibernate.web.internal.PersistedTransientEntity
+org.apache.tapestry5.ioc.MessageFormatter=org.apache.tapestry5.commons.MessageFormatter
+org.apache.tapestry5.internal.hibernate.SessionRestorable=org.apache.tapestry5.hibernate.web.internal.SessionRestorable
+org.apache.tapestry5.ioc.OrderedConfiguration=org.apache.tapestry5.commons.OrderedConfiguration
+org.apache.tapestry5.internal.SyntheticSymbolSourceContributionDef=org.apache.tapestry5.http.internal.SyntheticSymbolSourceContributionDef
+org.apache.tapestry5.services.GenericsResolver=org.apache.tapestry5.commons.services.GenericsResolver
+org.apache.tapestry5.ioc.util.StrategyRegistry=org.apache.tapestry5.commons.util.StrategyRegistry
+org.apache.tapestry5.internal.ServletContextSymbolProvider=org.apache.tapestry5.http.internal.ServletContextSymbolProvider
+org.apache.tapestry5.ioc.services.Coercion=org.apache.tapestry5.commons.services.Coercion
+org.apache.tapestry5.ContentType=org.apache.tapestry5.http.ContentType
+org.apache.tapestry5.ioc.internal.services.ServiceMessages=org.apache.tapestry5.commons.internal.services.ServiceMessages
+org.apache.tapestry5.ioc.ObjectProvider=org.apache.tapestry5.commons.ObjectProvider
+org.apache.tapestry5.internal.hibernate.HibernateEntityValueEncoder=org.apache.tapestry5.hibernate.web.internal.HibernateEntityValueEncoder
+org.apache.tapestry5.internal.hibernate.PersistedEntity=org.apache.tapestry5.hibernate.web.internal.PersistedEntity
+org.apache.tapestry5.ioc.util.ExceptionUtils=org.apache.tapestry5.commons.util.ExceptionUtils
+org.apache.tapestry5.ioc.internal.BasicDataTypeAnalyzers=org.apache.tapestry5.commons.internal.BasicDataTypeAnalyzers
+org.apache.tapestry5.services.Context=org.apache.tapestry5.http.services.Context
+org.apache.tapestry5.hibernate.HibernatePersistenceConstants=org.apache.tapestry5.hibernate.web.HibernatePersistenceConstants
+org.apache.tapestry5.internal.services.RequestImpl=org.apache.tapestry5.http.internal.services.RequestImpl
+org.apache.tapestry5.internal.services.OptimizedSessionPersistedObjectAnalyzer=org.apache.tapestry5.http.internal.services.OptimizedSessionPersistedObjectAnalyzer
+org.apache.tapestry5.ioc.Configuration=org.apache.tapestry5.commons.Configuration
+org.apache.tapestry5.services.Session=org.apache.tapestry5.http.services.Session
+org.apache.tapestry5.services.Request=org.apache.tapestry5.http.services.Request
+org.apache.tapestry5.internal.services.ResponseImpl=org.apache.tapestry5.http.internal.services.ResponseImpl
+org.apache.tapestry5.internal.services.ResponseCompressionAnalyzerImpl=org.apache.tapestry5.http.internal.services.ResponseCompressionAnalyzerImpl
+org.apache.tapestry5.services.RequestFilter=org.apache.tapestry5.http.services.RequestFilter
+org.apache.tapestry5.ioc.internal.services.AnnotationProviderChain=org.apache.tapestry5.commons.internal.services.AnnotationProviderChain
+org.apache.tapestry5.services.HttpServletRequestHandler=org.apache.tapestry5.http.services.HttpServletRequestHandler
+org.apache.tapestry5.internal.util.MultiKey=org.apache.tapestry5.commons.util.MultiKey
+org.apache.tapestry5.services.ApplicationGlobals=org.apache.tapestry5.http.services.ApplicationGlobals
+org.apache.tapestry5.services.RequestGlobals=org.apache.tapestry5.http.services.RequestGlobals
+org.apache.tapestry5.internal.util.IntegerRange=org.apache.tapestry5.commons.util.IntegerRange
+org.apache.tapestry5.services.Response=org.apache.tapestry5.http.services.Response
+org.apache.tapestry5.internal.hibernate.HibernateSessionManagerImpl=org.apache.tapestry5.hibernate.internal.HibernateSessionManagerImpl
+org.apache.tapestry5.internal.services.StringInternerImpl=org.apache.tapestry5.commons.internal.services.StringInternerImpl
+org.apache.tapestry5.internal.hibernate.DefaultHibernateConfigurer=org.apache.tapestry5.hibernate.internal.DefaultHibernateConfigurer
+org.apache.tapestry5.ioc.MappedConfiguration=org.apache.tapestry5.commons.MappedConfiguration
+org.apache.tapestry5.services.ServletApplicationInitializerFilter=org.apache.tapestry5.http.services.ServletApplicationInitializerFilter
+org.apache.tapestry5.internal.genericsresolverguava.GuavaGenericsResolver=org.apache.tapestry5.genericsresolverguava.internal.GuavaGenericsResolver
+org.apache.tapestry5.ioc.services.PropertyAdapter=org.apache.tapestry5.commons.services.PropertyAdapter
+org.apache.tapestry5.LinkSecurity=org.apache.tapestry5.http.LinkSecurity
+org.apache.tapestry5.ioc.internal.util.MessagesImpl=org.apache.tapestry5.commons.internal.util.MessagesImpl
+org.apache.tapestry5.ioc.services.PropertyAccess=org.apache.tapestry5.commons.services.PropertyAccess
+org.apache.tapestry5.internal.hibernate.HibernateSessionSourceImpl=org.apache.tapestry5.hibernate.internal.HibernateSessionSourceImpl
+org.apache.tapestry5.services.Dispatcher=org.apache.tapestry5.http.services.Dispatcher
+org.apache.tapestry5.services.ComponentLayer=org.apache.tapestry5.ioc.annotations.ComponentLayer
+org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils=org.apache.tapestry5.commons.internal.util.InternalCommonsUtils
+org.apache.tapestry5.services.ComponentClasses=org.apache.tapestry5.ioc.annotations.ComponentClasses
+org.apache.tapestry5.internal.services.TapestrySessionFactory=org.apache.tapestry5.http.internal.services.TapestrySessionFactory
+org.apache.tapestry5.hibernate.modules.HibernateModule=org.apache.tapestry5.hibernate.web.modules.HibernateModule
+org.apache.tapestry5.internal.services.SessionImpl=org.apache.tapestry5.http.internal.services.SessionImpl
+org.apache.tapestry5.ioc.AnnotationProvider=org.apache.tapestry5.commons.AnnotationProvider
+org.apache.tapestry5.ioc.internal.BasicTypeCoercions=org.apache.tapestry5.commons.internal.BasicTypeCoercions
+org.apache.tapestry5.services.RequestHandler=org.apache.tapestry5.http.services.RequestHandler
+org.apache.tapestry5.internal.hibernate.HibernateTransactionAdvisorImpl=org.apache.tapestry5.hibernate.internal.HibernateTransactionAdvisorImpl
+org.apache.tapestry5.ioc.Messages=org.apache.tapestry5.commons.Messages
+org.apache.tapestry5.ioc.internal.util.CollectionFactory=org.apache.tapestry5.commons.util.CollectionFactory
+org.apache.tapestry5.internal.util.DelegatingSymbolProvider=org.apache.tapestry5.http.internal.util.DelegatingSymbolProvider
+org.apache.tapestry5.ioc.services.ClassPropertyAdapter=org.apache.tapestry5.commons.services.ClassPropertyAdapter
+org.apache.tapestry5.internal.services.RequestGlobalsImpl=org.apache.tapestry5.http.internal.services.RequestGlobalsImpl
+org.apache.tapestry5.internal.services.ContextImpl=org.apache.tapestry5.http.internal.services.ContextImpl
+org.apache.tapestry5.services.InvalidationEventHub=org.apache.tapestry5.commons.services.InvalidationEventHub
+org.apache.tapestry5.internal.AbstractContributionDef=org.apache.tapestry5.http.internal.AbstractContributionDef
+org.apache.tapestry5.internal.hibernate.EntityApplicationStatePersistenceStrategy=org.apache.tapestry5.hibernate.web.internal.EntityApplicationStatePersistenceStrategy
+org.apache.tapestry5.internal.TapestryAppInitializer=org.apache.tapestry5.http.internal.TapestryAppInitializer
+org.apache.tapestry5.internal.SingleKeySymbolProvider=org.apache.tapestry5.http.internal.SingleKeySymbolProvider
+org.apache.tapestry5.internal.hibernate.PackageNameHibernateConfigurer=org.apache.tapestry5.hibernate.internal.PackageNameHibernateConfigurer
+org.apache.tapestry5.internal.services.ClusteredSessionImpl=org.apache.tapestry5.http.internal.services.ClusteredSessionImpl
+org.apache.tapestry5.ioc.internal.services.StringLocation=org.apache.tapestry5.commons.internal.services.StringLocation
+org.apache.tapestry5.internal.services.SessionLock=org.apache.tapestry5.http.internal.services.SessionLock
+org.apache.tapestry5.internal.services.AnnotationDataTypeAnalyzer=org.apache.tapestry5.commons.internal.services.AnnotationDataTypeAnalyzer
+org.apache.tapestry5.services.UpdateListenerHub=org.apache.tapestry5.ioc.services.UpdateListenerHub
+org.apache.tapestry5.ioc.internal.services.TypeCoercerImpl=org.apache.tapestry5.commons.internal.services.TypeCoercerImpl
+org.apache.tapestry5.internal.hibernate.EntityPersistentFieldStrategy=org.apache.tapestry5.hibernate.web.internal.EntityPersistentFieldStrategy
+org.apache.tapestry5.ioc.Locatable=org.apache.tapestry5.commons.Locatable
+org.apache.tapestry5.ioc.ObjectCreator=org.apache.tapestry5.commons.ObjectCreator
+org.apache.tapestry5.ioc.util.AvailableValues=org.apache.tapestry5.commons.util.AvailableValues
+org.apache.tapestry5.ioc.internal.services.AccessableObjectAnnotationProvider=org.apache.tapestry5.commons.internal.services.AccessableObjectAnnotationProvider
+org.apache.tapestry5.internal.SyntheticModuleDef=org.apache.tapestry5.http.internal.SyntheticModuleDef
+org.apache.tapestry5.internal.services.GenericsResolverImpl=org.apache.tapestry5.commons.internal.services.GenericsResolverImpl
+org.apache.tapestry5.ioc.services.TypeCoercer=org.apache.tapestry5.commons.services.TypeCoercer
+org.apache.tapestry5.internal.services.BaseURLSourceImpl=org.apache.tapestry5.http.internal.services.BaseURLSourceImpl
+org.apache.tapestry5.ioc.internal.util.InheritanceSearch=org.apache.tapestry5.commons.internal.util.InheritanceSearch
+org.apache.tapestry5.ioc.internal.util.TapestryException=org.apache.tapestry5.commons.internal.util.TapestryException
+org.apache.tapestry5.internal.services.ApplicationGlobalsImpl=org.apache.tapestry5.http.internal.services.ApplicationGlobalsImpl
+org.apache.tapestry5.ioc.util.UnknownValueException=org.apache.tapestry5.commons.util.UnknownValueException
+org.apache.tapestry5.OptimizedSessionPersistedObject=org.apache.tapestry5.http.OptimizedSessionPersistedObject
+org.apache.tapestry5.services.DataTypeAnalyzer=org.apache.tapestry5.commons.services.DataTypeAnalyzer
+org.apache.tapestry5.ioc.util.TimeInterval=org.apache.tapestry5.commons.util.TimeInterval
+org.apache.tapestry5.corelib.pages.HibernateStatistics=org.apache.tapestry5.hibernate.web.pages.HibernateStatistics
+org.apache.tapestry5.ioc.util.AbstractMessages=org.apache.tapestry5.commons.util.AbstractMessages
+org.apache.tapestry5.services.ApplicationInitializerFilter=org.apache.tapestry5.http.services.ApplicationInitializerFilter
+org.apache.tapestry5.internal.services.DefaultDataTypeAnalyzer=org.apache.tapestry5.commons.internal.services.DefaultDataTypeAnalyzer
+org.apache.tapestry5.services.HttpServletRequestFilter=org.apache.tapestry5.http.services.HttpServletRequestFilter
+org.apache.tapestry5.ioc.util.CaseInsensitiveMap=org.apache.tapestry5.commons.util.CaseInsensitiveMap
+org.apache.tapestry5.ioc.services.PlasticProxyFactory=org.apache.tapestry5.commons.services.PlasticProxyFactory
+org.apache.tapestry5.services.SessionPersistedObjectAnalyzer=org.apache.tapestry5.http.services.SessionPersistedObjectAnalyzer
+org.apache.tapestry5.services.BaseURLSource=org.apache.tapestry5.http.services.BaseURLSource
+org.apache.tapestry5.services.ApplicationInitializer=org.apache.tapestry5.http.services.ApplicationInitializer
+org.apache.tapestry5.services.ServletApplicationInitializer=org.apache.tapestry5.http.services.ServletApplicationInitializer
+org.apache.tapestry5.internal.gzip.GZipFilter=org.apache.tapestry5.http.internal.gzip.GZipFilter
+org.apache.tapestry5.internal.services.TapestrySessionFactoryImpl=org.apache.tapestry5.http.internal.services.TapestrySessionFactoryImpl
+org.apache.tapestry5.ioc.util.Stack=org.apache.tapestry5.commons.util.Stack
+org.apache.tapestry5.ioc.Location=org.apache.tapestry5.commons.Location
+com.yahoo.platform.yui.compressor.CssCompressor=org.apache.tapestry5.internal.webresources.CssCompressor
+org.apache.tapestry5.ioc.services.CoercionTuple=org.apache.tapestry5.commons.services.CoercionTuple
+org.apache.tapestry5.services.UpdateListener=org.apache.tapestry5.ioc.services.UpdateListener
+org.apache.tapestry5.ioc.ObjectLocator=org.apache.tapestry5.commons.ObjectLocator
+org.apache.tapestry5.ioc.internal.NullAnnotationProvider=org.apache.tapestry5.commons.internal.NullAnnotationProvider
+org.apache.tapestry5.annotations.ImmutableSessionPersistedObject=org.apache.tapestry5.http.annotations.ImmutableSessionPersistedObject
+org.apache.tapestry5.internal.hibernate.CommitAfterWorker=org.apache.tapestry5.hibernate.web.internal.CommitAfterWorker
+org.apache.tapestry5.services.ResponseCompressionAnalyzer=org.apache.tapestry5.http.services.ResponseCompressionAnalyzer
+org.apache.tapestry5.services.assets.CompressionAnalyzer=org.apache.tapestry5.http.services.CompressionAnalyzer
+org.apache.tapestry5.internal.hibernate.HibernateTransactionDecoratorImpl=org.apache.tapestry5.hibernate.internal.HibernateTransactionDecoratorImpl
+org.apache.tapestry5.util.StringToEnumCoercion=org.apache.tapestry5.commons.util.StringToEnumCoercion
+org.apache.tapestry5.internal.services.DefaultSessionPersistedObjectAnalyzer=org.apache.tapestry5.http.internal.services.DefaultSessionPersistedObjectAnalyzer
+org.apache.tapestry5.internal.services.StringInterner=org.apache.tapestry5.commons.internal.services.StringInterner
+org.apache.tapestry5.internal.gzip.BufferedGZipOutputStream=org.apache.tapestry5.http.internal.gzip.BufferedGZipOutputStream
+org.apache.tapestry5.services.InvalidationListener=org.apache.tapestry5.commons.services.InvalidationListener
+org.apache.tapestry5.ioc.internal.util.MessageFormatterImpl=org.apache.tapestry5.commons.internal.util.MessageFormatterImpl
+org.apache.tapestry5.ioc.internal.util.GenericsUtils=org.apache.tapestry5.commons.internal.util.GenericsUtils
+org.apache.tapestry5.hibernate.HibernateGridDataSource=org.apache.tapestry5.hibernate.web.HibernateGridDataSource
+org.apache.tapestry5.internal.gzip.GZIPEnabledResponse=org.apache.tapestry5.http.internal.gzip.GZIPEnabledResponse
+org.apache.tapestry5.ioc.internal.util.LockSupport=org.apache.tapestry5.commons.internal.util.LockSupport
+org.apache.tapestry5.ioc.Resource=org.apache.tapestry5.commons.Resource
diff --git a/tapestry-version-migrator/src/main/resources/org/apache/tapestry5/versionmigrator/change-report-5.7.0.html b/tapestry-version-migrator/src/main/resources/org/apache/tapestry5/versionmigrator/change-report-5.7.0.html
new file mode 100644
index 0000000..6eb1a58
--- /dev/null
+++ b/tapestry-version-migrator/src/main/resources/org/apache/tapestry5/versionmigrator/change-report-5.7.0.html
@@ -0,0 +1 @@
+<html>	<head>		<title>Changes introduced in Apache Tapestry 5.7.0</title>	</head>	<body>		<table>			<thead>				<th>Old class name</th>				<th>Renamed or moved?</th>				<th>New package location</th>				<th>Moved artifacts?</th>				<th>Old artifact location</th>				<th>New artifact location</th>			</thead>			<tbody>				<tr>					<td>AbstractMessages</td>					<td>yes</td>					<td>org.apache.tapestry5.commons.util</td>					<td>no</td>					<td></td>					<td></td>					</tr>				<tr>					<td>Advi [...]
\ No newline at end of file
diff --git a/tapestry-version-migrator/src/test/java/org/apache/tapestry5/versionmigrator/ClassRefactorTest.java b/tapestry-version-migrator/src/test/java/org/apache/tapestry5/versionmigrator/ClassRefactorTest.java
new file mode 100644
index 0000000..f1bef36
--- /dev/null
+++ b/tapestry-version-migrator/src/test/java/org/apache/tapestry5/versionmigrator/ClassRefactorTest.java
@@ -0,0 +1,112 @@
+// 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 org.apache.tapestry5.versionmigrator;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * Test class for {@link ClassRefactor}.
+ */
+@Test(groups = "unit")
+public class ClassRefactorTest 
+{
+    final private static String VALID = "valid";
+    final private static String SIMPLE_CLASS_NAME = "Something";
+    final private static String PACKAGE_NAME = "org.apache.tapestry5";
+    final private static String CLASS_NAME = PACKAGE_NAME + "." + SIMPLE_CLASS_NAME;
+    final private static String ARTIFACT_NAME = "tapestry-subproject";
+    
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void constructor_first_parameter_check() 
+    {
+        new ClassRefactor(null, VALID, VALID, VALID);
+    }
+    
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void constructor_second_parameter_check() 
+    {
+        new ClassRefactor(VALID, null, VALID, VALID);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void constructor_third_parameter_check() 
+    {
+        new ClassRefactor(VALID, VALID, null, VALID);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class)
+    public void constructor_fourth_parameter_check() 
+    {
+        new ClassRefactor(VALID, VALID, VALID, null);
+    }
+    
+    @Test
+    public void is_moved_between_artifacts() 
+    {
+        
+        Assert.assertTrue(
+                new ClassRefactor(
+                        CLASS_NAME, CLASS_NAME, 
+                        ARTIFACT_NAME, ARTIFACT_NAME + "blah").isMovedBetweenArtifacts(), 
+                "Artifact changed");
+
+        Assert.assertFalse(
+                new ClassRefactor(
+                        CLASS_NAME, CLASS_NAME + "Blah", 
+                        ARTIFACT_NAME, ARTIFACT_NAME).isMovedBetweenArtifacts(), 
+                "Artifact not changed");
+        
+    }
+
+    @Test
+    public void is_renamed() 
+    {
+        
+        Assert.assertTrue(
+                new ClassRefactor(
+                        CLASS_NAME, CLASS_NAME + "Blah", 
+                        ARTIFACT_NAME, ARTIFACT_NAME).isRenamed(), 
+                "Fully qualified class name changed");
+
+        Assert.assertFalse(
+                new ClassRefactor(
+                        CLASS_NAME, CLASS_NAME, 
+                        ARTIFACT_NAME, ARTIFACT_NAME + "blah").isRenamed(), 
+                "Fully qualified class name changed");        
+    }
+    
+    @Test
+    public void get_simple_old_class_name() 
+    {
+        
+        Assert.assertEquals(
+                new ClassRefactor(
+                        CLASS_NAME, CLASS_NAME, 
+                        ARTIFACT_NAME, ARTIFACT_NAME).getSimpleOldClassName(), 
+                SIMPLE_CLASS_NAME,
+                "Wrong simple old class name.");
+    }
+
+    @Test
+    public void get_new_package_name() 
+    {
+        
+        Assert.assertEquals(
+                new ClassRefactor(
+                        CLASS_NAME, CLASS_NAME.replace("org.", "com."), 
+                        ARTIFACT_NAME, ARTIFACT_NAME).getNewPackageName(), 
+                PACKAGE_NAME,
+                "Wrong new package name.");
+    }
+
+}
diff --git a/tapestry-version-migrator/src/test/java/org/apache/tapestry5/versionmigrator/internal/ArtifactChangeRefactorCommitParserTest.java b/tapestry-version-migrator/src/test/java/org/apache/tapestry5/versionmigrator/internal/ArtifactChangeRefactorCommitParserTest.java
new file mode 100644
index 0000000..f4cf979
--- /dev/null
+++ b/tapestry-version-migrator/src/test/java/org/apache/tapestry5/versionmigrator/internal/ArtifactChangeRefactorCommitParserTest.java
@@ -0,0 +1,41 @@
+// 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 org.apache.tapestry5.versionmigrator.internal;
+
+import java.util.Optional;
+
+import org.apache.tapestry5.versionmigrator.ClassRefactor;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * Test class for {@link ArtifactChangeRefactorCommitParser}.
+ */
+@Test(groups = "unit")
+public class ArtifactChangeRefactorCommitParserTest 
+{
+    @Test
+    public void valid_line() 
+    {
+        // {tapestry-ioc => tapestry5-annotations}/src/main/java/org/apache/tapestry5/ioc/annotations/Advise.java
+        ArtifactChangeRefactorCommitParser parser = new ArtifactChangeRefactorCommitParser();
+        Optional<ClassRefactor> optionalRefactor = parser.apply(ArtifactChangeRefactorCommitParser.EXAMPLE);
+        Assert.assertTrue(optionalRefactor.isPresent(), "Line not detected as a change of artifact.");
+        ClassRefactor refactor = optionalRefactor.get();
+        final String expectedClassName = "org.apache.tapestry5.ioc.annotations.Advise";
+        Assert.assertEquals(refactor.getNewClassName(), expectedClassName);
+        Assert.assertEquals(refactor.getOldClassName(), expectedClassName);
+        Assert.assertEquals(refactor.getDestinationArtifact(), "tapestry5-annotations");
+        Assert.assertEquals(refactor.getSourceArtifact(), "tapestry-ioc");
+    }
+
+}
diff --git a/tapestry-version-migrator/src/test/java/org/apache/tapestry5/versionmigrator/internal/PackageAndArtifactChangeRefactorCommitParserTest.java b/tapestry-version-migrator/src/test/java/org/apache/tapestry5/versionmigrator/internal/PackageAndArtifactChangeRefactorCommitParserTest.java
new file mode 100644
index 0000000..fab4063
--- /dev/null
+++ b/tapestry-version-migrator/src/test/java/org/apache/tapestry5/versionmigrator/internal/PackageAndArtifactChangeRefactorCommitParserTest.java
@@ -0,0 +1,40 @@
+// 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 org.apache.tapestry5.versionmigrator.internal;
+
+import java.util.Optional;
+
+import org.apache.tapestry5.versionmigrator.ClassRefactor;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * Test class for {@link PackageAndArtifactChangeRefactorCommitParser}.
+ */
+@Test(groups = "unit")
+public class PackageAndArtifactChangeRefactorCommitParserTest 
+{
+    @Test
+    public void valid_line() 
+    {
+        // {tapestry-ioc/src/main/java/org/apache/tapestry5/ioc => commons/src/main/java/org/apache/tapestry5/commons}/ObjectProvider.java
+        PackageAndArtifactChangeRefactorCommitParser parser = new PackageAndArtifactChangeRefactorCommitParser();
+        Optional<ClassRefactor> optionalRefactor = parser.apply(PackageAndArtifactChangeRefactorCommitParser.EXAMPLE);
+        Assert.assertTrue(optionalRefactor.isPresent(), "Line not detected as a change of package and artifact.");
+        ClassRefactor refactor = optionalRefactor.get();
+        Assert.assertEquals(refactor.getNewClassName(), "org.apache.tapestry5.commons.ObjectProvider");
+        Assert.assertEquals(refactor.getOldClassName(), "org.apache.tapestry5.commons.ObjectProvider");
+        Assert.assertEquals(refactor.getDestinationArtifact(), "commons");
+        Assert.assertEquals(refactor.getSourceArtifact(), "tapestry-ioc");
+    }
+
+}
diff --git a/tapestry-version-migrator/src/test/java/org/apache/tapestry5/versionmigrator/internal/PackageChangeRefactorCommitParserTest.java b/tapestry-version-migrator/src/test/java/org/apache/tapestry5/versionmigrator/internal/PackageChangeRefactorCommitParserTest.java
new file mode 100644
index 0000000..09d2402
--- /dev/null
+++ b/tapestry-version-migrator/src/test/java/org/apache/tapestry5/versionmigrator/internal/PackageChangeRefactorCommitParserTest.java
@@ -0,0 +1,40 @@
+// 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 org.apache.tapestry5.versionmigrator.internal;
+
+import java.util.Optional;
+
+import org.apache.tapestry5.versionmigrator.ClassRefactor;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * Test class for {@link PackageChangeRefactorCommitParser}.
+ */
+@Test(groups = "unit")
+public class PackageChangeRefactorCommitParserTest 
+{
+    @Test
+    public void valid_line() 
+    {
+        // commons/src/main/java/org/apache/tapestry5/{ioc => commons}/Messages.java (98%)
+        PackageChangeRefactorCommitParser parser = new PackageChangeRefactorCommitParser();
+        Optional<ClassRefactor> optionalRefactor = parser.apply(PackageChangeRefactorCommitParser.EXAMPLE);
+        Assert.assertTrue(optionalRefactor.isPresent(), "Line not detected as a change of package.");
+        ClassRefactor refactor = optionalRefactor.get();
+        Assert.assertEquals(refactor.getNewClassName(), "org.apache.tapestry5.commons.Messages");
+        Assert.assertEquals(refactor.getOldClassName(), "org.apache.tapestry5.commons.Messages");
+        Assert.assertEquals(refactor.getDestinationArtifact(), "commons");
+        Assert.assertEquals(refactor.getSourceArtifact(), "commons");
+    }
+
+}
diff --git a/tapestry-version-migrator/test-sources/ClassAtRootFolder.java b/tapestry-version-migrator/test-sources/ClassAtRootFolder.java
new file mode 100644
index 0000000..e2e153f
--- /dev/null
+++ b/tapestry-version-migrator/test-sources/ClassAtRootFolder.java
@@ -0,0 +1,11 @@
+import org.apache.tapestry5.Link;
+
+public class ClassAtRootFolder {
+
+	org.apache.tapestry5.ioc.internal.services.CompoundCoercion field;
+
+	public void blah() {
+	
+	}
+
+}
diff --git a/tapestry-version-migrator/test-sources/subfolder/subsubfolder/ClassAtSubFolder.java b/tapestry-version-migrator/test-sources/subfolder/subsubfolder/ClassAtSubFolder.java
new file mode 100644
index 0000000..4ed4682
--- /dev/null
+++ b/tapestry-version-migrator/test-sources/subfolder/subsubfolder/ClassAtSubFolder.java
@@ -0,0 +1,15 @@
+import org.apache.tapestry5.Link;
+
+import org.apache.tapestry5.ioc.services.Coercion;
+import org.apache.tapestry5.ContentType;
+
+public class ClassAtSubSubFolder {
+
+	org.apache.tapestry5.ContentType contentType;
+	org.apache.tapestry5.ioc.internal.services.CompoundCoercion field;
+	
+	public void blah() {
+	
+	}
+
+}