├── .gitignore ├── LICENSE.txt ├── README.md ├── copyright.txt ├── docs ├── jdk8-jdk9-api-diff.html └── jdk9-jdk10-api-diff.html ├── pom.xml └── src └── main ├── groovy └── exportJdkHomes.groovy └── java ├── de └── gunnarmorling │ └── jdkapidiff │ ├── ModuleRepackager.java │ ├── ProcessExecutor.java │ └── repackager │ ├── Jdk8Repackager.java │ ├── Jdk9Repackager.java │ └── JdkRepackager.java └── module-info.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .metadata 3 | .recommenders 4 | .classpath 5 | .project 6 | .settings 7 | .factorypath 8 | .checkstyle 9 | .externalToolBuilders 10 | 11 | # IntelliJ 12 | *.iml 13 | *.ipr 14 | *.iws 15 | .idea 16 | 17 | # Netbeans 18 | nb-configuration.xml 19 | 20 | # Build 21 | /**/target/ 22 | test-output 23 | 24 | # Misc. 25 | .DS_Store 26 | /**/dependency-reduced-pom.xml 27 | 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2017 Gunnar Morling (http://www.gunnarmorling.de/) 2 | and/or other contributors as indicated by the @authors tag. See the 3 | copyright.txt file in the distribution for a full listing of all 4 | contributors. 5 | 6 | MapStruct is licensed under the Apache License, Version 2.0 (the 7 | "License"); you may not use this software except in compliance with the 8 | License. You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JDK API Diff Report Generator 2 | 3 | *Note:* This project has been migrated to the AdoptOpenJDK organization (https://github.com/AdoptOpenJDK/jdk-api-diff) and this repository is not actively maintained any longer. 4 | 5 | This project creates a report of all API changes between two different JDK versions, e.g. JDK 8 and 9, using [JapiCmp](https://github.com/siom79/japicmp). 6 | 7 | ## Published reports 8 | 9 | Report created by this generator can be found here (excluding any unsupported Sun/Oracle/Apple modules): 10 | 11 | * [comparing JDK 9.0.1 against JDK 1.8.0_151](https://gunnarmorling.github.io/jdkapidiff/jdk8-jdk9-api-diff.html) 12 | (it's 16 MB, so loading may take a bit) 13 | * [comparing JDK 10-ea (b42) against JDK 9.0.4](https://gunnarmorling.github.io/jdkapidiff/jdk9-jdk10-api-diff.html) 14 | 15 | ## Usage 16 | 17 | To create the report yourself, e.g. with different settings, run `mvn clean install`. 18 | The API change report can be found at _target/jdk-api-diff.html_. 19 | 20 | [Maven Toolchains](https://maven.apache.org/guides/mini/guide-using-toolchains.html) are used to locate the different JDKs. 21 | There must be a toolchain of type `jdk` for the JDKs to compare. 22 | Provide a file _~.m2/toolchains.xml_ like this: 23 | 24 | ```xml 25 | 26 | 27 | 28 | jdk 29 | 30 | 1.8 31 | oracle 32 | 33 | 34 | /path/to/jdk-1.8 35 | 36 | 37 | 38 | jdk 39 | 40 | 9 41 | oracle 42 | 43 | 44 | /path/to/jdk-9 45 | 46 | 47 | 48 | ``` 49 | 50 | Specify two properties, `jdk1` and `jdk2` in your _pom.xml_, identifying the base and target JDK version for the comparison. 51 | The values are comma-separated requirements matched against the `` configurations of the existing toolchain entries. 52 | Both properties must unambiguously identify one toolchain, for example: 53 | 54 | ```xml 55 | version=9,vendor=oracle 56 | version=10,vendor=oracle 57 | ``` 58 | 59 | If there's no matching toolchain or multiple ones match the given requirements, an exception will be raised. 60 | 61 | The report is created via the `ModuleRepackager` class which is executed with the Maven exec plug-in. 62 | Adjust the following options passed to that class in _pom.xml_ as needed: 63 | 64 | * `--exported-packages-only`: `true` or `false`, depending on whether only exported packages should be compared 65 | or all packages; only applies if both compared versions are Java 9 or later 66 | * `--excluded-packages`: a comma-separated listed of package names which should be excluded from the comparison 67 | 68 | ## License 69 | 70 | This project is licensed under the Apache License version 2.0. 71 | -------------------------------------------------------------------------------- /copyright.txt: -------------------------------------------------------------------------------- 1 | Contributors 2 | ============ 3 | 4 | Gunnar Morling 5 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 22 | 24 | 4.0.0 25 | 26 | de.gunnarmorling.jdkapidiff 27 | jdkapidiff 28 | 1.0-SNAPSHOT 29 | jar 30 | 31 | jdkapidiff 32 | http://gunnarmorling.de/ 33 | 34 | 35 | UTF-8 36 | 37 | 38 | version=9,vendor=oracle 39 | version=10,vendor=oracle 40 | 41 | 42 | 43 | 44 | 45 | 46 | com.mycila 47 | license-maven-plugin 48 | 3.0 49 | 50 | 51 | org.apache.maven.plugins 52 | maven-compiler-plugin 53 | 54 | 9 55 | 9 56 | 57 | 3.6.2 58 | 59 | 60 | org.codehaus.gmaven 61 | groovy-maven-plugin 62 | 2.0 63 | 64 | 65 | org.codehaus.groovy 66 | groovy-all 67 | 2.4.12 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.codehaus.gmaven 76 | groovy-maven-plugin 77 | 78 | 79 | generate-resources 80 | 81 | execute 82 | 83 | 84 | ${project.basedir}/src/main/groovy/exportJdkHomes.groovy 85 | 86 | 87 | 88 | 89 | 90 | org.codehaus.mojo 91 | exec-maven-plugin 92 | 1.6.0 93 | 94 | 95 | verify 96 | 97 | exec 98 | 99 | 100 | 101 | 102 | java 103 | 104 | -Xdebug 105 | -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 106 | --add-modules 107 | java.xml.bind 108 | --module-path 109 | 110 | --module 111 | de.gunnarmorling.jdkapidiff/de.gunnarmorling.jdkapidiff.ModuleRepackager 112 | --javaHome1 113 | ${javaHome1} 114 | --javaHome2 115 | ${javaHome2} 116 | --working-dir 117 | ${project.build.directory} 118 | --exported-packages-only 119 | true 120 | --excluded-packages 121 | apple,com.apple,com.oracle,com.sun,oracle,sun,jdk.management.cmm,jdk.management.jfr,jdk.management.resource 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | com.github.siom79.japicmp 131 | japicmp 132 | 0.11.0 133 | 134 | 135 | com.beust 136 | jcommander 137 | 1.72 138 | 139 | 140 | junit 141 | junit 142 | 4.12 143 | test 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /src/main/groovy/exportJdkHomes.groovy: -------------------------------------------------------------------------------- 1 | def getJdkHome(String jdkSelector) { 2 | tm = session.lookup( 'org.apache.maven.toolchain.ToolchainManager' ) 3 | 4 | requirements = new HashMap<>(); 5 | for( String requirement : jdkSelector.split( "\\," ) ) { 6 | parts = requirement.split( "=" ); 7 | requirements.put( parts[0], parts[1] ); 8 | } 9 | 10 | toolChains = tm.getToolchains( session, 'jdk', requirements ); 11 | 12 | if ( toolChains.isEmpty() ) { 13 | throw new IllegalArgumentException( "No matching toolchain found for requirements " + jdkSelector ); 14 | } 15 | else if ( toolChains.size > 1 ) { 16 | throw new IllegalArgumentException( "Multiple matching toolchains found for requirements " + jdkSelector ); 17 | } 18 | else { 19 | return new java.io.File( toolChains.first().findTool( 'javac' ) ).getParentFile().getParentFile().toString() 20 | } 21 | } 22 | 23 | project.properties.javaHome1 = getJdkHome( project.properties.jdk1 ); 24 | project.properties.javaHome2 = getJdkHome( project.properties.jdk2 ); 25 | -------------------------------------------------------------------------------- /src/main/java/de/gunnarmorling/jdkapidiff/ModuleRepackager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Gunnar Morling (http://www.gunnarmorling.de/) 3 | * and/or other contributors as indicated by the @authors tag. See the 4 | * copyright.txt file in the distribution for a full listing of all 5 | * contributors. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package de.gunnarmorling.jdkapidiff; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.io.PrintWriter; 24 | import java.nio.file.FileVisitResult; 25 | import java.nio.file.Files; 26 | import java.nio.file.Path; 27 | import java.nio.file.SimpleFileVisitor; 28 | import java.nio.file.StandardCopyOption; 29 | import java.nio.file.attribute.BasicFileAttributes; 30 | import java.util.Arrays; 31 | import java.util.Collections; 32 | import java.util.LinkedHashSet; 33 | import java.util.List; 34 | import java.util.Set; 35 | import java.util.stream.Stream; 36 | 37 | import com.beust.jcommander.JCommander; 38 | import com.beust.jcommander.Parameter; 39 | 40 | import de.gunnarmorling.jdkapidiff.repackager.JdkRepackager; 41 | import japicmp.cmp.JApiCmpArchive; 42 | import japicmp.cmp.JarArchiveComparator; 43 | import japicmp.cmp.JarArchiveComparatorOptions; 44 | import japicmp.config.Options; 45 | import japicmp.model.JApiClass; 46 | import japicmp.output.semver.SemverOut; 47 | import japicmp.output.xml.XmlOutput; 48 | import japicmp.output.xml.XmlOutputGenerator; 49 | import japicmp.output.xml.XmlOutputGeneratorOptions; 50 | import japicmp.util.Optional; 51 | 52 | public class ModuleRepackager { 53 | 54 | public static class Args { 55 | 56 | @Parameter(names="--javaHome1") 57 | private File javaHome1; 58 | 59 | @Parameter(names="--excludes1") 60 | private List excludes1; 61 | 62 | @Parameter(names="--javaHome2") 63 | private File javaHome2; 64 | 65 | @Parameter(names="--excludes2") 66 | private List excludes2; 67 | 68 | @Parameter(names="--working-dir") 69 | private File workingDir; 70 | 71 | @Parameter(names="--exported-packages-only", 72 | description= 73 | "Whether only exported packages should be considered or not. Only supported if" + 74 | "the two JDK versions to be compared are Java 9 or later." 75 | ) 76 | private boolean exportedPackagesOnly; 77 | 78 | @Parameter(names="--excluded-packages") 79 | private String excludedPackages; 80 | } 81 | 82 | public static void main(String[] argv) throws Exception { 83 | Args args = new Args(); 84 | JCommander.newBuilder() 85 | .acceptUnknownOptions( true ) 86 | .addObject( args ) 87 | .build() 88 | .parse( argv ); 89 | 90 | Path extractedClassesDir = args.workingDir.toPath().resolve( "extracted-classes" ); 91 | delete( extractedClassesDir ); 92 | 93 | JdkRepackager repackagerOld = JdkRepackager.getJdkRepackager( args.javaHome1.toPath(), args.workingDir.toPath() ); 94 | Set exported = repackagerOld.mergeJavaApi( extractedClassesDir, args.excludes1 != null ? args.excludes1 : Collections.emptyList() ); 95 | 96 | JdkRepackager repackagerNew = JdkRepackager.getJdkRepackager( args.javaHome2.toPath(), args.workingDir.toPath() ); 97 | exported.addAll( repackagerNew.mergeJavaApi( extractedClassesDir, args.excludes2 != null ? args.excludes2 : Collections.emptyList() ) ); 98 | 99 | boolean exportedPackagesOnly = args.exportedPackagesOnly && repackagerOld.supportsExports() && repackagerNew.supportsExports(); 100 | 101 | Set excludedPackages = args.excludedPackages != null ? new LinkedHashSet<>( Arrays.asList( args.excludedPackages.split("\\,") ) ) : null; 102 | 103 | generateDiffReport( args, repackagerOld, repackagerNew, exportedPackagesOnly ? exported : null, excludedPackages ); 104 | } 105 | 106 | private static void generateDiffReport(Args args, JdkRepackager oldJdk, JdkRepackager newJdk, Set includedPackages, Set excludedPackages) throws IOException { 107 | Path outputFile = args.workingDir.toPath().resolve( "jdk-api-diff.html" ); 108 | 109 | Options options = Options.newDefault(); 110 | options.setNoAnnotations( true ); 111 | options.setIgnoreMissingClasses( true ); 112 | options.setOutputOnlyModifications( true ); 113 | options.setOldArchives( Arrays.asList( new JApiCmpArchive( oldJdk.getMergedJarPath().toFile(), oldJdk.getVersion() ) ) ); 114 | options.setNewArchives( Arrays.asList( new JApiCmpArchive( newJdk.getMergedJarPath().toFile(), newJdk.getVersion() ) ) ); 115 | 116 | if ( excludedPackages != null ) { 117 | for ( String excluded : excludedPackages ) { 118 | System.out.println("Excluded: " + excluded + "###"); 119 | options.addExcludeFromArgument( Optional.of( excluded ), false ); 120 | } 121 | } 122 | 123 | if ( includedPackages != null ) { 124 | for ( String included : includedPackages ) { 125 | options.addIncludeFromArgument( Optional.of( included ), true ); 126 | } 127 | } 128 | 129 | options.setHtmlOutputFile( Optional.of( outputFile.toString() ) ); 130 | 131 | List jApiClasses = generateDiff(oldJdk, newJdk, options); 132 | createHtmlReport( oldJdk, newJdk, options, jApiClasses ); 133 | cleanupOutput( outputFile, oldJdk, newJdk ); 134 | } 135 | 136 | private static List generateDiff(JdkRepackager oldJdk, JdkRepackager newJdk, Options options) { 137 | System.out.println( "Generating API diff" ); 138 | 139 | JarArchiveComparatorOptions comparatorOptions = JarArchiveComparatorOptions.of( options ); 140 | JarArchiveComparator jarArchiveComparator = new JarArchiveComparator( comparatorOptions ); 141 | List jApiClasses = jarArchiveComparator.compare( 142 | new JApiCmpArchive( oldJdk.getMergedJarPath().toFile(), oldJdk.getVersion() ), 143 | new JApiCmpArchive( newJdk.getMergedJarPath().toFile(), newJdk.getVersion() ) 144 | ); 145 | return jApiClasses; 146 | } 147 | 148 | private static void createHtmlReport(JdkRepackager oldJdk, JdkRepackager newJdk, Options options, 149 | List jApiClasses) { 150 | System.out.println( "Creating HTML report" ); 151 | 152 | SemverOut semverOut = new SemverOut( options, jApiClasses ); 153 | XmlOutputGeneratorOptions xmlOutputGeneratorOptions = new XmlOutputGeneratorOptions(); 154 | xmlOutputGeneratorOptions.setCreateSchemaFile( true ); 155 | xmlOutputGeneratorOptions.setSemanticVersioningInformation( semverOut.generate() ); 156 | xmlOutputGeneratorOptions.setTitle( "JDK " + oldJdk.getVersion() + " to " + newJdk.getVersion() + " API Change Report" ); 157 | 158 | XmlOutputGenerator xmlGenerator = new XmlOutputGenerator( jApiClasses, options, xmlOutputGeneratorOptions ); 159 | XmlOutput output = xmlGenerator.generate(); 160 | XmlOutputGenerator.writeToFiles( options, output ); 161 | } 162 | 163 | private static void cleanupOutput(Path outputFile, JdkRepackager oldJdk, JdkRepackager newJdk) throws IOException { 164 | Path outputFileTrimmed = outputFile.getParent().resolve( outputFile.getFileName() + ".new" ); 165 | 166 | PrintWriter writer = new PrintWriter( outputFileTrimmed.toFile(), "UTF-8" ); 167 | 168 | try ( Stream stream = Files.lines( outputFile ) ) { 169 | stream.forEach( l -> { 170 | writer.write( l.replaceAll( "\\s+$", "" ) 171 | .replaceAll( oldJdk.getMergedJarPath().toString(), "JDK " + oldJdk.getVersion() ) 172 | .replaceAll( newJdk.getMergedJarPath().toString(), "JDK " + newJdk.getVersion() ) + 173 | System.lineSeparator() 174 | ); 175 | } ); 176 | } 177 | 178 | writer.close(); 179 | 180 | Files.move( outputFileTrimmed, outputFile, StandardCopyOption.REPLACE_EXISTING ); 181 | } 182 | 183 | private static Path delete(Path dir) { 184 | try { 185 | if ( Files.exists( dir ) ) { 186 | Files.walkFileTree( dir, new SimpleFileVisitor() { 187 | @Override 188 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 189 | Files.delete( file ); 190 | return FileVisitResult.CONTINUE; 191 | } 192 | 193 | @Override 194 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 195 | Files.delete( dir ); 196 | return FileVisitResult.CONTINUE; 197 | } 198 | }); 199 | } 200 | 201 | Files.createDirectory( dir ); 202 | } 203 | catch (IOException e) { 204 | throw new RuntimeException( "Couldn't recreate directory " + dir, e ); 205 | } 206 | 207 | return dir; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/main/java/de/gunnarmorling/jdkapidiff/ProcessExecutor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Gunnar Morling (http://www.gunnarmorling.de/) 3 | * and/or other contributors as indicated by the @authors tag. See the 4 | * copyright.txt file in the distribution for a full listing of all 5 | * contributors. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package de.gunnarmorling.jdkapidiff; 20 | 21 | import java.io.BufferedReader; 22 | import java.io.IOException; 23 | import java.io.InputStreamReader; 24 | import java.nio.file.Path; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | /** 29 | * Executes a specified external processor, logging output to the given logger. 30 | * 31 | * @author Gunnar Morling 32 | */ 33 | public class ProcessExecutor { 34 | 35 | public static List run(String name, List command, Path workingDirectory) { 36 | ProcessBuilder builder = new ProcessBuilder( command ).directory( workingDirectory.toFile() ); 37 | 38 | Process process; 39 | List outputLines = new ArrayList<>(); 40 | try { 41 | process = builder.start(); 42 | 43 | BufferedReader in = new BufferedReader( new InputStreamReader( process.getInputStream() ) ); 44 | String line; 45 | while ( ( line = in.readLine() ) != null ) { 46 | outputLines.add( line ); 47 | } 48 | 49 | BufferedReader err = new BufferedReader( new InputStreamReader( process.getErrorStream() ) ); 50 | while ( ( line = err.readLine() ) != null ) { 51 | outputLines.add( "Error: " + line ); 52 | } 53 | 54 | process.waitFor(); 55 | } 56 | catch (IOException | InterruptedException e) { 57 | System.out.println( outputLines); 58 | throw new RuntimeException( "Couldn't run " + name, e ); 59 | } 60 | 61 | if ( process.exitValue() != 0 ) { 62 | System.out.println( outputLines); 63 | throw new RuntimeException( "Execution of " + name + " failed" ); 64 | } 65 | 66 | return outputLines; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/de/gunnarmorling/jdkapidiff/repackager/Jdk8Repackager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Gunnar Morling (http://www.gunnarmorling.de/) 3 | * and/or other contributors as indicated by the @authors tag. See the 4 | * copyright.txt file in the distribution for a full listing of all 5 | * contributors. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package de.gunnarmorling.jdkapidiff.repackager; 20 | 21 | import java.nio.file.Path; 22 | import java.util.Arrays; 23 | import java.util.SortedSet; 24 | import java.util.TreeSet; 25 | 26 | import de.gunnarmorling.jdkapidiff.ProcessExecutor; 27 | 28 | public class Jdk8Repackager extends JdkRepackager { 29 | 30 | public Jdk8Repackager(Path javaHome, String version, Path workingDir) { 31 | super( javaHome, version, workingDir ); 32 | } 33 | 34 | @Override 35 | public SortedSet extractJdkClasses(Path targetDir) { 36 | // Using separate process for using specific target directory 37 | Path rtJar = javaHome.resolve( "jre" ).resolve( "lib" ).resolve( "rt.jar" ); 38 | System.out.println( "Extracting rt.jar" ); 39 | ProcessExecutor.run( "jar", Arrays.asList( "jar", "-xf", rtJar.toString() ), targetDir ); 40 | 41 | Path javawsJar = javaHome.resolve( "jre" ).resolve( "lib" ).resolve( "javaws.jar" ); 42 | System.out.println( "Extracting javaws.jar" ); 43 | ProcessExecutor.run( "jar", Arrays.asList( "jar", "-xf", javawsJar.toString() ), targetDir ); 44 | 45 | Path jfxrtJar = javaHome.resolve( "jre" ).resolve( "lib" ).resolve( "ext" ).resolve( "jfxrt.jar" ); 46 | System.out.println( "Extracting jfxrt.jar" ); 47 | ProcessExecutor.run( "jar", Arrays.asList( "jar", "-xf", jfxrtJar.toString() ), targetDir ); 48 | 49 | Path nashornJar = javaHome.resolve( "jre" ).resolve( "lib" ).resolve( "ext" ).resolve( "nashorn.jar" ); 50 | System.out.println( "Extracting nashorn.jar" ); 51 | ProcessExecutor.run( "jar", Arrays.asList( "jar", "-xf", nashornJar.toString() ), targetDir ); 52 | 53 | Path jceJar = javaHome.resolve( "jre" ).resolve( "lib" ).resolve( "jce.jar" ); 54 | System.out.println( "Extracting jce.jar" ); 55 | ProcessExecutor.run( "jar", Arrays.asList( "jar", "-xf", jceJar.toString() ), targetDir ); 56 | 57 | Path jfrJar = javaHome.resolve( "jre" ).resolve( "lib" ).resolve( "jfr.jar" ); 58 | System.out.println( "Extracting jfr.jar" ); 59 | ProcessExecutor.run( "jar", Arrays.asList( "jar", "-xf", jfrJar.toString() ), targetDir ); 60 | 61 | return new TreeSet<>(); 62 | } 63 | 64 | @Override 65 | public boolean supportsExports() { 66 | return false; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/de/gunnarmorling/jdkapidiff/repackager/Jdk9Repackager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Gunnar Morling (http://www.gunnarmorling.de/) 3 | * and/or other contributors as indicated by the @authors tag. See the 4 | * copyright.txt file in the distribution for a full listing of all 5 | * contributors. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package de.gunnarmorling.jdkapidiff.repackager; 20 | 21 | import java.io.ByteArrayOutputStream; 22 | import java.io.IOException; 23 | import java.io.PrintWriter; 24 | import java.nio.file.Files; 25 | import java.nio.file.Path; 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | import java.util.Optional; 29 | import java.util.Scanner; 30 | import java.util.SortedSet; 31 | import java.util.TreeSet; 32 | import java.util.regex.Matcher; 33 | import java.util.regex.Pattern; 34 | import java.util.spi.ToolProvider; 35 | 36 | class Jdk9Repackager extends JdkRepackager { 37 | 38 | public Jdk9Repackager(Path javaHome, String version, Path workingDir) { 39 | super( javaHome, version, workingDir ); 40 | } 41 | 42 | @Override 43 | public SortedSet extractJdkClasses(Path targetDir) throws IOException { 44 | Optional jmod = ToolProvider.findFirst( "jmod" ); 45 | if ( !jmod.isPresent() ) { 46 | throw new IllegalStateException( "Couldn't find jmod tool" ); 47 | } 48 | 49 | ExportsRetrievingOutputStream exportsRetriever = new ExportsRetrievingOutputStream(); 50 | 51 | Files.list( javaHome.resolve( "jmods" ) ) 52 | .filter( p -> !p.getFileName().toString().startsWith( "jdk.internal") ) 53 | .forEach( module -> { 54 | System.out.println( "Extracting module " + module ); 55 | jmod.get().run( System.out, System.err, "extract", "--dir", targetDir.toString(), module.toString() ); 56 | jmod.get().run( exportsRetriever, new PrintWriter( System.err ), "describe", module.toString() ); 57 | }); 58 | 59 | Files.delete( targetDir.resolve( "classes" ).resolve( "module-info.class") ); 60 | 61 | return new TreeSet<>( exportsRetriever.getExports() ); 62 | } 63 | 64 | @Override 65 | public boolean supportsExports() { 66 | return true; 67 | } 68 | 69 | private static class ExportsRetrievingOutputStream extends PrintWriter { 70 | 71 | // TODO handle qualified exports; currently there seem to be none 72 | private static final Pattern EXPORTS_PATTERN = Pattern.compile("exports (.*)"); 73 | 74 | private List exports = new ArrayList<>(); 75 | 76 | public ExportsRetrievingOutputStream() { 77 | super( new ByteArrayOutputStream() ); 78 | } 79 | 80 | @Override 81 | public void println(String x) { 82 | try(Scanner lines = new Scanner(x)) { 83 | while(lines.hasNext()) { 84 | Matcher matcher = EXPORTS_PATTERN.matcher( lines.nextLine() ); 85 | 86 | if ( matcher.matches() ) { 87 | exports.add( matcher.group( 1 ) ); 88 | } 89 | } 90 | } 91 | } 92 | 93 | public List getExports() { 94 | return exports; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/de/gunnarmorling/jdkapidiff/repackager/JdkRepackager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Gunnar Morling (http://www.gunnarmorling.de/) 3 | * and/or other contributors as indicated by the @authors tag. See the 4 | * copyright.txt file in the distribution for a full listing of all 5 | * contributors. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | package de.gunnarmorling.jdkapidiff.repackager; 20 | 21 | import java.io.IOException; 22 | import java.nio.charset.StandardCharsets; 23 | import java.nio.file.FileVisitResult; 24 | import java.nio.file.Files; 25 | import java.nio.file.Path; 26 | import java.nio.file.Paths; 27 | import java.nio.file.SimpleFileVisitor; 28 | import java.nio.file.StandardOpenOption; 29 | import java.nio.file.attribute.BasicFileAttributes; 30 | import java.util.Arrays; 31 | import java.util.List; 32 | import java.util.Optional; 33 | import java.util.Set; 34 | import java.util.spi.ToolProvider; 35 | 36 | import de.gunnarmorling.jdkapidiff.ProcessExecutor; 37 | 38 | public abstract class JdkRepackager { 39 | 40 | protected final Path javaHome; 41 | protected final String version; 42 | private final Path workingDir; 43 | 44 | public static JdkRepackager getJdkRepackager(Path javaHome, Path workingDir) { 45 | String version = getVersion( javaHome ); 46 | 47 | if ( version.startsWith( "1.") ) { 48 | return new Jdk8Repackager( javaHome, version, workingDir ); 49 | } 50 | else { 51 | return new Jdk9Repackager( javaHome, version, workingDir ); 52 | } 53 | } 54 | 55 | protected JdkRepackager(Path javaHome, String version, Path workingDir) { 56 | this.javaHome = javaHome; 57 | this.version = version; 58 | this.workingDir = workingDir; 59 | } 60 | 61 | /** 62 | * Merges the represented JDK's classes into a single JAR for comparison 63 | * purposes. 64 | * 65 | * @return The packages exported by the represented JDK 66 | */ 67 | public Set mergeJavaApi(Path extractedClassesDir, List excludes) throws IOException { 68 | System.out.println( "Merging JARs/modules from " + javaHome + " (version " + version + ")" ); 69 | 70 | Path targetDir = extractedClassesDir.resolve( version ); 71 | Files.createDirectories( targetDir ); 72 | 73 | Set exportedPackages = extractJdkClasses( targetDir ); 74 | 75 | Path fileList = Paths.get( workingDir.toUri() ).resolve( version + "-files" ); 76 | 77 | Files.write( 78 | fileList, 79 | getFileList( targetDir, excludes ).getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE 80 | ); 81 | 82 | System.out.println( "Creating " + getMergedJarPath() ); 83 | 84 | Optional jar = ToolProvider.findFirst( "jar" ); 85 | if ( !jar.isPresent() ) { 86 | throw new IllegalStateException( "Couldn't find jar tool" ); 87 | } 88 | 89 | jar.get().run( 90 | System.out, 91 | System.err, 92 | "-cf", getMergedJarPath().toString(), 93 | "@" + fileList 94 | ); 95 | 96 | return exportedPackages; 97 | } 98 | 99 | public Path getMergedJarPath() { 100 | String apiJarName = "java-" + version + "-api.jar"; 101 | return workingDir.resolve( apiJarName ); 102 | } 103 | 104 | public String getVersion() { 105 | return version; 106 | } 107 | 108 | /** 109 | * Whether the extracted JDK has a defined API represented by exports (JDK 9 and onwards) or not. 110 | */ 111 | public abstract boolean supportsExports(); 112 | 113 | protected abstract Set extractJdkClasses(Path targetDir) throws IOException; 114 | 115 | private static String getVersion(Path javaHome) { 116 | List output = ProcessExecutor.run( "java", Arrays.asList( javaHome.resolve( "bin" ).resolve( "java" ).toString(), "-version" ), javaHome.resolve( "bin" ) ); 117 | String version = output.get( 0 ); 118 | return version.substring( version.indexOf( "\"" ) + 1, version.lastIndexOf( "\"" ) ); 119 | } 120 | 121 | private static String getFileList(Path java8Dir, List excludes) { 122 | StringBuilder fileList = new StringBuilder(); 123 | 124 | try { 125 | Files.walkFileTree( java8Dir, new SimpleFileVisitor() { 126 | @Override 127 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 128 | java8Dir.relativize( file ); 129 | 130 | for ( String exclude : excludes ) { 131 | if ( file.startsWith( exclude ) ) { 132 | return FileVisitResult.CONTINUE; 133 | } 134 | } 135 | 136 | fileList.append( file ).append( System.lineSeparator() ); 137 | 138 | return FileVisitResult.CONTINUE; 139 | } 140 | }); 141 | } 142 | catch (IOException e) { 143 | throw new RuntimeException( e ); 144 | } 145 | 146 | return fileList.toString(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Gunnar Morling (http://www.gunnarmorling.de/) 3 | * and/or other contributors as indicated by the @authors tag. See the 4 | * copyright.txt file in the distribution for a full listing of all 5 | * contributors. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | module de.gunnarmorling.jdkapidiff { 20 | requires jcommander; 21 | exports de.gunnarmorling.jdkapidiff; 22 | opens de.gunnarmorling.jdkapidiff to jcommander; 23 | } 24 | --------------------------------------------------------------------------------