-
-
Save LarsEckart/4eaa468a009cf3250bd8fb7bf5afe1a6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2009-2015, Data Geekery GmbH (http://www.datageekery.com) | |
* All rights reserved. | |
* | |
* This work is dual-licensed | |
* - under the Apache Software License 2.0 (the "ASL") | |
* - under the jOOQ License and Maintenance Agreement (the "jOOQ License") | |
* ============================================================================= | |
* You may choose which license applies to you: | |
* | |
* - If you're using this work with Open Source databases, you may choose | |
* either ASL or jOOQ License. | |
* - If you're using this work with at least one commercial database, you must | |
* choose jOOQ License | |
* | |
* For more information, please visit http://www.jooq.org/licenses | |
* | |
* Apache Software License 2.0: | |
* ----------------------------------------------------------------------------- | |
* 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. | |
* | |
* jOOQ License and Maintenance Agreement: | |
* ----------------------------------------------------------------------------- | |
* Data Geekery grants the Customer the non-exclusive, timely limited and | |
* non-transferable license to install and use the Software under the terms of | |
* the jOOQ License and Maintenance Agreement. | |
* | |
* This library is distributed with a LIMITED WARRANTY. See the jOOQ License | |
* and Maintenance Agreement for more details: http://www.jooq.org/licensing | |
*/ | |
package org.jooq.web; | |
import static java.util.Collections.nCopies; | |
import static java.util.stream.Collectors.joining; | |
import static org.jooq.web.ApiDiff.Modification.added; | |
import static org.jooq.web.ApiDiff.Modification.contravariance; | |
import static org.jooq.web.ApiDiff.Modification.deprecated; | |
import static org.jooq.web.ApiDiff.Modification.pulledup; | |
import static org.jooq.web.ApiDiff.Modification.removed; | |
import static org.jooq.web.Versions.API_DIFF; | |
import static org.jooq.web.Versions.GROUP_ID; | |
import static org.jooq.web.Versions.JAVADOC; | |
import static org.jooq.web.Versions.MINOR; | |
import static org.jooq.web.Versions.PATCH; | |
import static org.jooq.web.Versions.VERSIONS; | |
import java.io.File; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.PrintStream; | |
import java.util.ArrayList; | |
import java.util.Comparator; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.concurrent.atomic.AtomicBoolean; | |
import java.util.function.BiConsumer; | |
import java.util.function.Consumer; | |
import java.util.jar.JarEntry; | |
import java.util.jar.JarFile; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import java.util.stream.Collectors; | |
import java.util.stream.Stream; | |
import org.jooq.lambda.Seq; | |
import org.jooq.tools.StringUtils; | |
import org.apache.commons.io.IOUtils; | |
import org.objectweb.asm.ClassReader; | |
import org.objectweb.asm.Opcodes; | |
import org.objectweb.asm.tree.AnnotationNode; | |
import org.objectweb.asm.tree.ClassNode; | |
import org.objectweb.asm.tree.MethodNode; | |
/** | |
* @author Lukas Eder | |
*/ | |
public class ApiDiff { | |
static final int MAX_DEGREE = 5; | |
final String repository; | |
final Version oldVersion; | |
final Version newVersion; | |
final Map<String, List<String>> parameters; | |
final Map<String, List<String>> parametersShortenTypes; | |
public ApiDiff(String repository, Version oldVersion, Version newVersion) { | |
this.repository = repository; | |
this.oldVersion = oldVersion; | |
this.newVersion = newVersion; | |
this.parameters = new HashMap<>(); | |
this.parametersShortenTypes = new HashMap<>(); | |
} | |
private void run() throws Exception { | |
System.out.println("API diff generation from " + oldVersion + " to " + newVersion); | |
oldVersion.init(); | |
newVersion.init(); | |
try (PrintStream out = new PrintStream(new FileOutputStream(new File("jooq.org/api-diff/" + oldVersion.minor + "-" + newVersion.minor + ".php")))) { | |
Iterator<ClassNode> i1 = classes(oldVersion.classes); | |
Iterator<ClassNode> i2 = classes(newVersion.classes); | |
out.println( | |
"<?php\n" | |
+ "// The following content has been generated by ApiDiff.java\n" | |
+ "// Please do not edit this content manually\n" | |
+ "require '../frame.php';\n" | |
+ "function getH1() {\n" | |
+ " return 'API diff between " + oldVersion + " and " + newVersion + "';\n" | |
+ "}\n" | |
+ "function getActiveMenu() {\n" | |
+ " return 'learn';\n" | |
+ "}\n" | |
+ "function printTheme() {\n" | |
+ " noTheme();\n" | |
+ "}\n" | |
+ "function printContent() {\n" | |
+ " global $root;\n" | |
+ "?>\n" | |
+ "<div class='row col col-100 col-white headline'>\n" | |
+ "<h1><?=getH1()?></h1>\n" | |
+ "</div>\n" | |
+ "<style>\n" | |
+ "<?php require 'styles.css'; ?>\n" | |
+ "</style>\n" | |
+ "<div class='row col col-100 col-white'>\n" | |
+ "<table border='0' cellpadding='0' cellspacing='0' class='api-diff'>\n" | |
+ "<tr><th>Object</th>" + (newVersion.proAnnotationAvailable() ? "<th>Edition</th>" : "") + "<th>Modification</th></tr>\n"); | |
Set<String> packagesPrinted = new HashSet<>(); | |
BiConsumer<ClassNode, Modification> classWithPackageHeader = (c, modification) -> { | |
if (packagesPrinted.add(packageName(c))) | |
out.println("<tr><td class='api-diff-package'><span class='package-name'>" + formatPackage(c, modification) + "</span></td>" + (newVersion.proAnnotationAvailable() ? "<td></td>" : "") + "<td class='api-diff-modification'></td></tr>"); | |
out.println("<tr><td class='api-diff-class api-diff-" + modification + "'><span class='" + classType(c) + "-name'>" + formatClass(c, modification) + "</span></td>" + (newVersion.proAnnotationAvailable() ? "<td>" + edition(c) + "</td>" : "") + "<td class='api-diff-modification api-diff-modification-" + modification + "'><a href='#legend'>" + (modification != null ? modification : "") + "</a></td></tr>"); | |
}; | |
append(i1, i2, CLASS_COMP, | |
c -> classWithPackageHeader.accept(c, added), | |
c -> classWithPackageHeader.accept(c, removed), | |
(c1, c2) -> { | |
Modification classModification = !deprecated(c1) && deprecated(c2) | |
? deprecated | |
: null; | |
AtomicBoolean classPrinted = new AtomicBoolean(); | |
BiConsumer<MethodNode, Modification> methodWithClassHeader = (m, modification) -> { | |
if (classPrinted.compareAndSet(false, true)) | |
classWithPackageHeader.accept(c2, classModification); | |
out.println("<tr><td class='api-diff-method api-diff-" + modification + "'><span class='method-name'>" + formatMethod(modification == removed ? c1 : c2, m, modification, modification == removed ? oldVersion : newVersion) + "</span></td>" + (newVersion.proAnnotationAvailable() ? "<td>" + edition(m) + "</td>" : "") + "<td class='api-diff-modification api-diff-modification-" + modification + "'><a href='#legend'>" + (modification != null ? modification : "") + "</a></td></tr>"); | |
}; | |
append(methods(c1), methods(c2), METHOD_COMP, | |
m -> methodWithClassHeader.accept(m, addedOrPushedDown(c2, m)), | |
m -> methodWithClassHeader.accept(m, removedOrPulledUp(c2, m)), | |
(m1, m2) -> { | |
Modification methodModification = | |
!deprecated(m1) && deprecated(m2) | |
? deprecated | |
: increasedContravariance(m1, m2) | |
? contravariance | |
: null; | |
if (methodModification != null) | |
methodWithClassHeader.accept(m2, methodModification); | |
} | |
); | |
} | |
); | |
out.println( | |
"</table>\n" | |
+ "</div>\n" | |
+ "<?php\n" | |
+ "require 'legend.php';\n" | |
+ "?>\n" | |
+ "<div class='row col col-100 col-white'>\n" | |
+ "Eclipse icons copyright by <a href='http://www.eclipse.org/'>Eclipse</a> licensed under <a href='http://www.eclipse.org/legal/epl-v10.html'>EPL</a>. Inspiration taken from <a href='https://javaalmanac.io/'>https://javaalmanac.io/</a>" | |
+ "</div>"); | |
out.println("<?php } ?>"); | |
} | |
} | |
private boolean increasedContravariance(MethodNode m1, MethodNode m2) { | |
return increasedContravariance(parameters(m1), parameters(m2)); | |
} | |
private boolean increasedContravariance(List<String> p1, List<String> p2) { | |
if (p1.size() != p2.size()) | |
return false; | |
boolean result = false; | |
for (int i = 0; i < p1.size(); i++) { | |
String s1 = p1.get(i).replaceAll("\\.\\.\\.", "[]"); | |
String s2 = p2.get(i).replaceAll("\\.\\.\\.", "[]"); | |
while (s1.endsWith("[]") && s2.endsWith("[]")) { | |
s1 = s1.replaceFirst("\\[\\]", ""); | |
s2 = s2.replaceFirst("\\[\\]", ""); | |
} | |
if (!s1.equals(s2)) { | |
if (!subtype(newVersion.classesBySourceName.get(s1), newVersion.classesBySourceName.get(s2))) | |
return false; | |
else | |
result = true; | |
} | |
} | |
return result; | |
} | |
private boolean subtype(ClassNode c1, ClassNode c2) { | |
if (c1 == null || c2 == null) | |
return false; | |
if (c1.name.equals(c2.name)) | |
return true; | |
if (subtype(newVersion.classes.get(c1.superName), c2)) | |
return true; | |
if (c1.interfaces != null) | |
for (String i : c1.interfaces) | |
if (subtype(newVersion.classes.get(i), c2)) | |
return true; | |
return false; | |
} | |
private String classType(ClassNode c) { | |
if ((c.access & Opcodes.ACC_ENUM) != 0) | |
return "enum"; | |
else if ((c.access & Opcodes.ACC_INTERFACE) != 0) | |
return "interface"; | |
else if ((c.access & Opcodes.ACC_ANNOTATION) != 0) | |
return "annotation"; | |
else | |
return "class"; | |
} | |
private boolean deprecated(ClassNode c) { | |
return deprecated(c.visibleAnnotations); | |
} | |
private boolean deprecated(MethodNode m) { | |
return deprecated(m.visibleAnnotations); | |
} | |
private boolean deprecated(List<AnnotationNode> annotations) { | |
return annotations != null && annotations.stream().anyMatch(a -> a.desc.equals("Ljava/lang/Deprecated;")); | |
} | |
private Modification addedOrPushedDown(ClassNode c2, MethodNode m) { | |
// TODO: This pusheddown algorithm is not yet correct. If the overridden method | |
// is also new, then there was no push down! | |
return override(c2, m) ? added : added; | |
} | |
private boolean override(ClassNode c2, MethodNode m) { | |
return override(c2, m, false); | |
} | |
private boolean override(ClassNode c2, MethodNode m, boolean checkMethods) { | |
if (c2 == null) | |
return false; | |
if (override(newVersion.classes.get(c2.superName), m, true)) | |
return true; | |
if (c2.interfaces != null) | |
for (String i : c2.interfaces) | |
if (override(newVersion.classes.get(i), m, true)) | |
return true; | |
if (checkMethods) | |
for (MethodNode m2 : c2.methods) | |
if (m2.name.equals(m.name) && m2.desc.equals(m.desc)) | |
return true; | |
return false; | |
} | |
private Modification removedOrPulledUp(ClassNode c2, MethodNode m) { | |
if (c2 == null) | |
return removed; | |
if (removedOrPulledUp(newVersion.classes.get(c2.superName), m) == pulledup) | |
return pulledup; | |
if (c2.interfaces != null) | |
for (String i : c2.interfaces) | |
if (removedOrPulledUp(newVersion.classes.get(i), m) == pulledup) | |
return pulledup; | |
for (MethodNode m2 : c2.methods) | |
if (m2.name.equals(m.name) && m2.desc.equals(m.desc)) | |
return pulledup; | |
return removed; | |
} | |
private static String sourceName(String bytecodeName) { | |
return bytecodeName.replace("/", ".").replace("$", "."); | |
} | |
private String version(Modification modification) { | |
return modification == removed ? oldVersion.version : newVersion.version; | |
} | |
private String packagePath(ClassNode c) { | |
return c.name.replaceAll("(.*)/.*", "$1"); | |
} | |
private String className(ClassNode c) { | |
return sourceName(c.name.replaceAll(".*/", "")).replaceAll("1$", "1 - 22"); | |
} | |
private String packageName(ClassNode c) { | |
return sourceName(packagePath(c)); | |
} | |
private String formatPackage(ClassNode c, Modification modification) { | |
return "<a href='<?=$root?>/javadoc/" + version(modification) + "/org.jooq/" + packagePath(c) + "/package-summary.html'>" + packageName(c) + "</a>"; | |
} | |
private String formatClass(ClassNode c, Modification modification) { | |
return "<a href='<?=$root?>/javadoc/" + version(modification) + "/org.jooq/" + c.name + ".html'>" + className(c) + "</a>"; | |
} | |
private String formatMethod(ClassNode c, MethodNode m, Modification modification, Version version) { | |
return "<a href='<?=$root?>/javadoc/" + version(modification) + "/org.jooq/" + c.name + ".html#" + ("8".equals(version.javadoc) ? methodName(m, false).replaceAll("[(), ]+", "-") : methodName(m, false)) + "'>" + escape(methodName(m, true)) + "</a>" + degreeSuffix(m, MAX_DEGREE - 1); | |
} | |
private String degreeSuffix(MethodNode m, int degree) { | |
return degreeNorMore(parameters(m), degree) ? " <sub>... and more overloads</sub>" : ""; | |
} | |
private String methodName(MethodNode m, boolean shortenTypes) { | |
return m.desc == null && StringUtils.isBlank(m.desc) | |
? m.name + "()" | |
: m.name + "(" + parameters(m, shortenTypes).stream().collect(Collectors.joining(", ")) + ")"; | |
} | |
private String edition(ClassNode c) { | |
return edition(c.visibleAnnotations); | |
} | |
private String edition(MethodNode m) { | |
return edition(m.visibleAnnotations); | |
} | |
private String edition(List<AnnotationNode> annotations) { | |
return annotations != null && annotations.stream().anyMatch(a -> a.desc.contains("Pro")) ? "Pro" : "All"; | |
} | |
private String escape(String string) { | |
return string.replace("<", "<").replace(">", ">"); | |
} | |
private final <N> void append( | |
Iterator<? extends N> i1, | |
Iterator<? extends N> i2, | |
Comparator<? super N> comp, | |
Consumer<N> create, | |
Consumer<N> drop, | |
BiConsumer<N, N> merge | |
) { | |
N n1 = null; | |
N n2 = null; | |
for (;;) { | |
if (n1 == null && i1.hasNext()) | |
n1 = i1.next(); | |
if (n2 == null && i2.hasNext()) | |
n2 = i2.next(); | |
if (n1 == null && n2 == null) | |
break; | |
int c = n1 == null | |
? 1 | |
: n2 == null | |
? -1 | |
: comp.compare(n1, n2); | |
if (c < 0) { | |
if (drop != null) | |
drop.accept(n1); | |
n1 = null; | |
} | |
else if (c > 0) { | |
if (create != null) | |
create.accept(n2); | |
n2 = null; | |
} | |
else { | |
if (merge != null) | |
merge.accept(n1, n2); | |
n1 = n2 = null; | |
} | |
} | |
} | |
private static final Pattern P_PARAM_DESC = Pattern.compile("\\(([^)]*)\\).*?"); | |
private List<String> parameters(MethodNode m) { | |
return parameters(m, false); | |
} | |
private List<String> parameters(MethodNode m, boolean shortenTypes) { | |
return parameters(m.desc, shortenTypes, (m.access & Opcodes.ACC_VARARGS) != 0); | |
} | |
private List<String> parameters(String desc, boolean shortenTypes, boolean varargs) { | |
return (shortenTypes ? parametersShortenTypes : parameters).computeIfAbsent(desc, s -> { | |
List<String> result = new ArrayList<>(); | |
Matcher matcher = P_PARAM_DESC.matcher(desc); | |
if (matcher.find()) { | |
String content = matcher.group(1); | |
int dimensions = 0; | |
contentLoop: | |
for (int i = 0; i < content.length(); i++) { | |
switch (content.charAt(i)) { | |
case '[': dimensions++; continue contentLoop; | |
case 'Z': result.add(array("boolean", dimensions, varargs)); break; | |
case 'B': result.add(array("byte", dimensions, varargs)); break; | |
case 'C': result.add(array("char", dimensions, varargs)); break; | |
case 'D': result.add(array("double", dimensions, varargs)); break; | |
case 'F': result.add(array("float", dimensions, varargs)); break; | |
case 'I': result.add(array("int", dimensions, varargs)); break; | |
case 'J': result.add(array("long", dimensions, varargs)); break; | |
case 'S': result.add(array("short", dimensions, varargs)); break; | |
case 'V': result.add(array("void", dimensions, varargs)); break; | |
case 'L': { | |
String type = content.substring(i + 1, content.indexOf(';', i)); | |
result.add(array(shortenTypes ? sourceName(type.replaceAll(".*/", "")) : sourceName(type), dimensions, varargs)); | |
i += type.length() + 1; | |
break; | |
} | |
} | |
dimensions = 0; | |
} | |
} | |
return result; | |
}); | |
} | |
private String array(String type, int dimensions, boolean varargs) { | |
for (int i = 0; i < dimensions; i++) | |
type = type + (varargs && i == dimensions - 1 ? "..." : "[]"); | |
return type; | |
} | |
private final Comparator<MethodNode> METHOD_COMP = Comparator | |
.<MethodNode, String>comparing(m -> m.name) | |
.thenComparing((m1, m2) -> { | |
List<String> p1 = parameters(m1); | |
List<String> p2 = parameters(m2); | |
int s1 = p1.size(); | |
int s2 = p2.size(); | |
int c = s1 - s2; | |
if (c != 0) | |
return c; | |
for (int i = 0; i < s1; i++) { | |
c = p1.get(i).compareTo(p2.get(i)); | |
if (c != 0) | |
if (increasedContravariance(p1, p2)) | |
return 0; | |
else | |
return c; | |
} | |
return 0; | |
}); | |
private final Comparator<ClassNode> CLASS_COMP = Comparator.comparing(c -> c.name); | |
private boolean degreeNorMore(List<String> p, int degree) { | |
if (p.size() < degree) | |
return false; | |
return nCopies(p.size() - (degree - 1), p.get(p.size() - 1)).equals(p.subList((degree - 1), p.size())); | |
} | |
private Iterator<MethodNode> methods(ClassNode n) { | |
return Seq.seq(n.methods) | |
.filter(m -> (m.access & Opcodes.ACC_PUBLIC) != 0) | |
.filter(m -> Seq.seq(m.visibleAnnotations).noneMatch(a -> "Lorg/jooq/Internal;".equals(a.desc))) | |
.filter(m -> !degreeNorMore(parameters(m), MAX_DEGREE)) | |
.sorted(METHOD_COMP) | |
.iterator(); | |
} | |
private Iterator<ClassNode> classes(Map<String, ClassNode> cache) { | |
return cache.values() | |
.stream() | |
.sorted(CLASS_COMP) | |
.iterator(); | |
} | |
public static void main(String[] args) throws Exception { | |
String repository = "C:/users/lukas/.m2/repository"; | |
Version[] versions = new Version[VERSIONS.length]; | |
for (int i = 0; i < versions.length; i++) | |
versions[i] = new Version(repository, VERSIONS[i][GROUP_ID], VERSIONS[i][MINOR], VERSIONS[i][PATCH], VERSIONS[i][JAVADOC]); | |
System.out.println("API diff overview generation"); | |
try (PrintStream out = new PrintStream(new FileOutputStream(new File("jooq.org/api-diff/index.php")))) { | |
out.println( | |
"<?php\n" | |
+ "// The following content has been generated by ApiDiff.java\n" | |
+ "// Please do not edit this content manually\n" | |
+ "require '../frame.php';\n" | |
+ "function getH1() {\n" | |
+ " return 'API diff between all jOOQ versions';\n" | |
+ "}\n" | |
+ "function getActiveMenu() {\n" | |
+ " return 'learn';\n" | |
+ "}\n" | |
+ "function printTheme() {\n" | |
+ " noTheme();\n" | |
+ "}\n" | |
+ "function printContent() {\n" | |
+ " global $root;\n" | |
+ "?>\n" | |
+ "<div class='row col col-100 col-white headline'>\n" | |
+ "<h1><?=getH1()?></h1>\n" | |
+ "</div>\n" | |
+ "<style>\n" | |
+ "<?php require 'styles.css'; ?>\n" | |
+ "</style>\n" | |
+ "<div class='row col col-100 col-white'>\n" | |
+ "<table border='0' cellpadding='0' cellspacing='0' class='api-diff api-diff-overview'>\n" | |
+ "<tr><th>From</th>" + | |
Stream.of(VERSIONS) | |
.filter(v -> "true".equals(v[API_DIFF])) | |
.skip(1) | |
.map(v -> "<th>To " + v[MINOR] + "</th>") | |
.collect(joining()) | |
+ "</tr>\n"); | |
for (int i1 = 0; i1 < VERSIONS.length; i1++) { | |
int i = i1; | |
if ("true".equals(VERSIONS[i1][API_DIFF]) | |
&& Seq.of(VERSIONS) | |
.zipWithIndex() | |
.filter(v -> "true".equals(v.v1[API_DIFF])) | |
.skip(1) | |
.anyMatch(v -> i < v.v2) | |
) { | |
out.println("<tr><td>" + VERSIONS[i1][MINOR] + "</td>" + | |
Seq.of(VERSIONS) | |
.zipWithIndex() | |
.filter(v -> "true".equals(v.v1[API_DIFF])) | |
.skip(1) | |
.map(v -> "<td>" + (i < v.v2 ? ("<a href='<?=$root?>/api-diff/" + VERSIONS[i][MINOR] + "-" + v.v1[MINOR] + "'>Diff</a>") : "") + "</td>") | |
.collect(joining()) | |
+ "</tr>\n"); | |
} | |
} | |
out.println( | |
"</table>\n" | |
+ "</div>"); | |
out.println("<?php } ?>"); | |
} | |
for (int i1 = 0; i1 < VERSIONS.length; i1++) | |
for (int i2 = i1 + 1; i2 < VERSIONS.length; i2++) | |
if ("true".equals(VERSIONS[i1][API_DIFF]) && "true".equals(VERSIONS[i2][API_DIFF])) | |
new ApiDiff(repository, versions[i1], versions[i2]).run(); | |
} | |
static class Version { | |
static final Pattern P_TUPLE_TYPE = Pattern.compile("^org/jooq/.*(1\\d|2\\d|[2-9])$"); | |
final String repository; | |
final String groupId; | |
final String minor; | |
final String version; | |
final String javadoc; | |
final File jarFile; | |
final Map<String, ClassNode> classes; | |
final Map<String, ClassNode> classesBySourceName; | |
boolean init; | |
Boolean proAnnotationAvailable; | |
Version(String repository, String groupId, String minor, String version, String javadoc) { | |
this.repository = repository; | |
this.groupId = groupId; | |
this.minor = minor; | |
this.version = version; | |
this.javadoc = javadoc; | |
this.jarFile = lookupJarFile(); | |
this.classes = new HashMap<>(); | |
this.classesBySourceName = new HashMap<>(); | |
} | |
private File lookupJarFile() { | |
return new File(repository + "/" + groupId.replace(".", "/") + "/jooq/" + version + "/jooq-" + version + ".jar"); | |
} | |
private void init() throws IOException { | |
if (!init) { | |
init = true; | |
try (JarFile j = new JarFile(jarFile)) { | |
j.stream() | |
.filter(e -> e.getName().endsWith(".class")) | |
.map(e -> new ClassReader(readEntry(j, e))) | |
.map(c -> { | |
ClassNode node = new ClassNode(); | |
c.accept(node, 0); | |
return node; | |
}) | |
.filter(c -> (c.access & Opcodes.ACC_PUBLIC) != 0) | |
.filter(c -> Seq.seq(c.visibleAnnotations).noneMatch(a -> "Lorg/jooq/Internal;".equals(a.desc))) | |
.filter(c -> !P_TUPLE_TYPE.matcher(c.name).matches()) | |
.forEach(c -> { | |
classes.put(c.name, c); | |
classesBySourceName.put(sourceName(c.name), c); | |
}); | |
} | |
} | |
} | |
private byte[] readEntry(JarFile jar, JarEntry e) { | |
try (InputStream is = jar.getInputStream(e)) { | |
return IOUtils.toByteArray(is); | |
} | |
catch (IOException ex) { | |
throw new RuntimeException(ex); | |
} | |
} | |
private boolean proAnnotationAvailable() { | |
if (proAnnotationAvailable == null) | |
proAnnotationAvailable = classesBySourceName.containsKey("org.jooq.Pro"); | |
return proAnnotationAvailable; | |
} | |
@Override | |
public String toString() { | |
return version; | |
} | |
} | |
enum Modification { | |
added, | |
removed, | |
pulledup("pulled up"), | |
pusheddown("pushed down"), | |
contravariance("contravariance"), | |
covariance("covariance"), | |
deprecated, | |
; | |
final String label; | |
private Modification() { | |
this(null); | |
} | |
private Modification(String label) { | |
this.label = label == null ? name() : label; | |
} | |
@Override | |
public String toString() { | |
return label; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment