├── .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 |
--------------------------------------------------------------------------------