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}