Compare commits

...

1 Commits

Author SHA1 Message Date
Stephan Schroevers
f47b6187af WIP: Model modernizer-maven-plugin 2022-08-20 16:37:13 +02:00
5 changed files with 665 additions and 5 deletions

View File

@@ -3,6 +3,7 @@
-XX:+UseParallelGC
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED

View File

@@ -52,7 +52,10 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto</groupId>
@@ -143,6 +146,12 @@
<artifactId>assertj-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<scope>runtime</scope>
<!-- XXX: Consider making optional? Ship only the definitions? -->
</dependency>
<dependency>
<groupId>org.immutables</groupId>
<artifactId>value-annotations</artifactId>

View File

@@ -0,0 +1,395 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableTable.toImmutableTable;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.REFACTORING;
import static com.google.errorprone.matchers.Matchers.allOf;
import static java.util.Objects.requireNonNull;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Value;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableTable;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Var;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.IdentifierTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MemberReferenceTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MemberSelectTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.NewClassTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.predicates.TypePredicates;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.util.JavacTask;
import com.sun.tools.javac.api.BasicJavacTask;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.jvm.Target;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
/**
* A {@link BugChecker} which flags the same legacy APIs as the <a
* href="https://github.com/gaul/modernizer-maven-plugin">Modernizer Maven Plugin</a>.
*
* <p>This checker is primarily useful for people who run Error Prone anyway; it obviates the need
* for an additional source code analysis pass using another Maven plugin.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Avoid constants and methods superceded by more recent equivalents",
linkType = NONE,
severity = SUGGESTION,
tags = REFACTORING)
public final class Modernizer extends BugChecker
implements AnnotationTreeMatcher,
IdentifierTreeMatcher,
MemberReferenceTreeMatcher,
MemberSelectTreeMatcher,
NewClassTreeMatcher {
private static final long serialVersionUID = 1L;
// XXX: Load lazily?
private final ImmutableTable<String, Matcher<ExpressionTree>, String> violations =
loadViolations();
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
// XXX: Use or drop
// XXX: See
// https://github.com/google/guava-beta-checker/commit/9b26aa980be7f70631921fd6695013547728eb1e;
// we may be on the right track without this.
return Description.NO_MATCH;
}
@Override
public Description matchIdentifier(IdentifierTree tree, VisitorState state) {
return match(tree.getName(), tree, state);
}
@Override
public Description matchMemberReference(MemberReferenceTree tree, VisitorState state) {
return match(tree.getName(), tree, state);
}
@Override
public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) {
return match(tree.getIdentifier(), tree, state);
}
@Override
public Description matchNewClass(NewClassTree tree, VisitorState state) {
return Optional.ofNullable(ASTHelpers.getSymbol(tree))
.map(Symbol::getEnclosingElement)
.map(Symbol::getQualifiedName)
.map(name -> match(name, tree, state))
.orElse(Description.NO_MATCH);
}
private Description match(Name identifier, ExpressionTree tree, VisitorState state) {
return this.violations.row(identifier.toString()).entrySet().stream()
.filter(e -> e.getKey().matches(tree, state))
.findFirst()
.map(e -> buildDescription(tree).setMessage(e.getValue()).build())
.orElse(Description.NO_MATCH);
}
private ImmutableTable<String, Matcher<ExpressionTree>, String> loadViolations() {
InputStream resource = getClass().getResourceAsStream("/modernizer.xml");
// XXX: Or silently skip?
checkState(resource != null, "Modernizer configuration not found on classpath");
try (resource) {
return Violations.loadFromConfig(resource);
} catch (IOException e) {
throw new UncheckedIOException("Failed to parse Modernizer configuration", e);
}
}
static final class Violations {
@JacksonXmlElementWrapper(useWrapping = false)
private final ImmutableList<Violation> violation;
@JsonCreator
private Violations(@JsonProperty("violation") List<Violation> violation) {
this.violation = ImmutableList.copyOf(violation);
}
static ImmutableTable<String, Matcher<ExpressionTree>, String> loadFromConfig(
InputStream config) throws IOException {
XmlMapper mapper = new XmlMapper();
mapper.setDefaultVisibility(Value.construct(PropertyAccessor.FIELD, Visibility.ANY));
return mapper.readValue(config, Violations.class).getViolations();
}
private ImmutableTable<String, Matcher<ExpressionTree>, String> getViolations() {
return violation.stream()
.filter(v -> v.getChecker().isPresent())
.collect(
toImmutableTable(
Violation::getIdentifier,
v -> v.getChecker().orElseThrow(),
Violation::getComment));
}
}
static final class Violation {
private static final Pattern NAME_PATTERN =
Pattern.compile(
"(?<type>[^.]+)(?:\\.(?<member>[^:]+):(?:\\((?<params>[^)]*)\\))?(?<return>[^()]+))?");
private final Optional<Target> target;
private final String identifier;
private final Matcher<ExpressionTree> matcher;
private final String comment;
private Violation(
Optional<Target> target,
String identifier,
Matcher<ExpressionTree> matcher,
String comment) {
this.target = target;
this.identifier = identifier;
this.matcher = matcher;
this.comment = comment;
}
String getIdentifier() {
return this.identifier;
}
Optional<Matcher<ExpressionTree>> getChecker() {
return target.map(t -> allOf(this.matcher, targetMatcher(t)));
}
String getComment() {
return this.comment;
}
// XXX: Modernizer also flags annotation declarations, presumably by type.
// XXX: `ExpressionTree` is wrong here. Depends on type.
@JsonCreator
static Violation create(
@JsonProperty("version") String version,
@JsonProperty("name") String signature,
@JsonProperty("comment") String comment) {
Optional<Target> target = Optional.ofNullable(Target.lookup(version));
java.util.regex.Matcher matcher = NAME_PATTERN.matcher(signature);
checkState(matcher.matches(), "Failed to parse signature '%s'", signature);
String type =
replaceSlahes(requireNonNull(matcher.group("type"), "Signature must contain type"));
String member = matcher.group("member");
if (member == null) {
// XXX: Should not implement this interface. Something like:
// violations.put(type, allOf(isSubtypeOf(type), versionRequirement), this.comment)
return new Violation(target, type, (t, s) -> false, comment);
}
String params = matcher.group("params");
if (params == null) {
return new Violation(target, member, isField(type), comment);
}
ImmutableList<Supplier<Type>> parameters = parseParams(params);
if ("\"<init>\"".equals(member)) {
return new Violation(target, type, isConstructor(type, parameters), comment);
}
// XXX: Should we disallow _extension_ of this method?
return new Violation(target, member, isMethod(type, parameters), comment);
}
private static Matcher<ExpressionTree> targetMatcher(Target target) {
return (tree, state) -> target.compareTo(getTargetVersion(state)) <= 0;
}
private static Target getTargetVersion(VisitorState state) {
return Target.instance(
Optional.ofNullable(state.context.get(JavacTask.class))
.filter(BasicJavacTask.class::isInstance)
.map(BasicJavacTask.class::cast)
.map(BasicJavacTask::getContext)
.orElse(state.context));
}
private static Matcher<ExpressionTree> isField(String onDescendantOf) {
return isMember(
ElementKind::isField, TypePredicates.isDescendantOf(onDescendantOf), ImmutableList.of());
}
private static Matcher<ExpressionTree> isConstructor(
String ofClass, ImmutableList<Supplier<Type>> withParameters) {
return isMember(
k -> k == ElementKind.CONSTRUCTOR, TypePredicates.isExactType(ofClass), withParameters);
}
private static Matcher<ExpressionTree> isMethod(
String onDescendantOf, ImmutableList<Supplier<Type>> withParameters) {
return isMember(
k -> k == ElementKind.METHOD,
TypePredicates.isDescendantOf(onDescendantOf),
withParameters);
}
private static Matcher<ExpressionTree> isMember(
Predicate<ElementKind> ofKind,
TypePredicate ownedBy,
ImmutableList<Supplier<Type>> withParameters) {
return (tree, state) ->
Optional.ofNullable(ASTHelpers.getSymbol(tree))
.filter(s -> ofKind.test(s.getKind()))
.filter(s -> isOwnedBy(s, ownedBy, state))
.filter(s -> hasSameParameters(s, withParameters, state))
.isPresent();
}
private static boolean isOwnedBy(Symbol symbol, TypePredicate expected, VisitorState state) {
Symbol owner = symbol.getEnclosingElement();
return owner != null && expected.apply(owner.asType(), state);
}
private static boolean hasSameParameters(
Symbol method, ImmutableList<Supplier<Type>> expected, VisitorState state) {
List<Type> actual = method.asType().getParameterTypes();
if (actual.size() != expected.size()) {
return false;
}
for (int i = 0; i < actual.size(); ++i) {
if (!ASTHelpers.isSameType(actual.get(i), expected.get(i).get(state), state)) {
return false;
}
}
return true;
}
private static ImmutableList<Supplier<Type>> parseParams(String params) {
ImmutableList.Builder<Supplier<Type>> types = ImmutableList.builder();
@Var int index = 0;
while (index < params.length()) {
index = parseType(params, index, types::add);
}
return types.build();
}
private static int parseType(String params, int index, Consumer<Supplier<Type>> sink) {
switch (params.charAt(index)) {
case '[':
return parseArrayType(params, index, sink);
case 'L':
return parseTypeReference(params, index, sink);
default:
return parsePrimitiveType(params, index, sink);
}
}
private static int parseArrayType(String params, int index, Consumer<Supplier<Type>> sink) {
int typeIndex = index + 1;
checkArgument(
params.length() > typeIndex && params.charAt(index) == '[',
"Cannot parse array type in parameter string '%s' at index %s",
params,
index);
return parseType(
params,
typeIndex,
type ->
sink.accept(s -> s.getType(type.get(s), /* isArray= */ true, ImmutableList.of())));
}
private static int parsePrimitiveType(String params, int index, Consumer<Supplier<Type>> sink) {
String primitive =
Optional.of(params)
.filter(p -> p.length() > index)
.flatMap(p -> fromPrimitiveAlias(p.charAt(index)))
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"Cannot parse primitive type in parameter string '%s' at index %s",
params, index)));
sink.accept(s -> s.getTypeFromString(primitive));
return index + 1;
}
private static Optional<String> fromPrimitiveAlias(char alias) {
switch (alias) {
case 'Z':
return Optional.of("boolean");
case 'B':
return Optional.of("byte");
case 'C':
return Optional.of("char");
case 'S':
return Optional.of("short");
case 'I':
return Optional.of("int");
case 'J':
return Optional.of("long");
case 'F':
return Optional.of("float");
case 'D':
return Optional.of("double");
default:
return Optional.empty();
}
}
private static int parseTypeReference(String params, int index, Consumer<Supplier<Type>> sink) {
int identifierIndex = index + 1;
if (params.length() > identifierIndex && params.charAt(index) == 'L') {
int delimiter = params.indexOf(';', identifierIndex);
if (delimiter > index) {
sink.accept(
s ->
s.getTypeFromString(replaceSlahes(params.substring(identifierIndex, delimiter))));
return delimiter + 1;
}
}
throw new IllegalArgumentException(
String.format(
"Cannot parse reference type in parameter string '%s' at index %s", params, index));
}
private static String replaceSlahes(String typeName) {
return typeName.replace('/', '.');
}
}
}

View File

@@ -0,0 +1,221 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class ModernizerTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(Modernizer.class, getClass());
// XXX: Also add calls that should not be flagged.
// XXX: Test extension, field references, instance methods, static methods.
// methods with primitives, primitive arrays, references, reference arrays
// zero, one two args.
// XXX: Also test constructors!
// XXX: Test that the appropriate "prefer" message is emitted.
// XXX: List the test cases in `ModernizerTest`?
@Test
void fieldIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static com.google.common.base.Charsets.ISO_8859_1;",
"",
"import com.google.common.base.Charsets;",
"import java.nio.charset.StandardCharsets;",
"",
"class A {",
" {",
" // BUG: Diagnostic contains: Prefer java.nio.charset.StandardCharsets",
" Object o1 = ISO_8859_1;",
" // BUG: Diagnostic contains: Prefer java.nio.charset.StandardCharsets",
" Object o2 = Charsets.ISO_8859_1;",
" // BUG: Diagnostic contains: Prefer java.nio.charset.StandardCharsets",
" Object o3 = com.google.common.base.Charsets.ISO_8859_1;",
"",
" Object o4 = StandardCharsets.ISO_8859_1;",
" Object o5 = java.nio.charset.StandardCharsets.ISO_8859_1;",
" }",
"}")
.doTest();
}
@Test
void nullaryMethodIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static com.google.common.base.Optional.absent;",
"",
"import com.google.common.base.Optional;",
"import java.util.function.Supplier;",
"",
"class A {",
" {",
" // BUG: Diagnostic contains: Prefer java.util.Optional",
" absent();",
" // BUG: Diagnostic contains: Prefer java.util.Optional",
" Optional.absent();",
" // BUG: Diagnostic contains: Prefer java.util.Optional",
" com.google.common.base.Optional.absent();",
" // BUG: Diagnostic contains: Prefer java.util.Optional",
" Supplier<?> s1 = Optional::absent;",
" // BUG: Diagnostic contains: Prefer java.util.Optional",
" Supplier<?> s2 = com.google.common.base.Optional::absent;",
"",
" java.util.Optional.empty();",
" Supplier<?> s3 = java.util.Optional::empty;",
"",
" Dummy.absent();",
" }",
"",
" static final class Dummy {",
" static Optional<?> absent() {",
" return null;",
" }",
" }",
"}")
.doTest();
}
@Test
void unaryMethodWithIntegerArgumentIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static com.google.common.collect.Lists.newArrayListWithCapacity;",
"",
"import com.google.common.collect.Lists;",
"import java.util.ArrayList;",
"import java.util.function.IntFunction;",
"",
"class A {",
" {",
" // BUG: Diagnostic contains: Prefer java.util.ArrayList<>(int)",
" newArrayListWithCapacity(0);",
" // BUG: Diagnostic contains: Prefer java.util.ArrayList<>(int)",
" Lists.newArrayListWithCapacity(1);",
" // BUG: Diagnostic contains: Prefer java.util.ArrayList<>(int)",
" com.google.common.collect.Lists.newArrayListWithCapacity(2);",
" // BUG: Diagnostic contains: Prefer java.util.ArrayList<>(int)",
" IntFunction<?> f1 = Lists::newArrayListWithCapacity;",
" // BUG: Diagnostic contains: Prefer java.util.ArrayList<>(int)",
" IntFunction<?> f2 = com.google.common.collect.Lists::newArrayListWithCapacity;",
"",
" new ArrayList<>(3);",
" IntFunction<?> f3 = ArrayList::new;",
" IntFunction<?> f4 = java.util.ArrayList::new;",
"",
" Dummy.newArrayListWithCapacity(4);",
" }",
"",
" static final class Dummy {",
" static ArrayList<?> newArrayListWithCapacity(int initialArraySize) {",
" return null;",
" }",
" }",
"}")
.doTest();
}
@Test
void binaryMethodWithObjectArgumentsIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static com.google.common.base.Objects.equal;",
"",
"import com.google.common.base.Objects;",
"import java.util.function.BiPredicate;",
"",
"class A {",
" {",
" // BUG: Diagnostic contains: Prefer java.util.Objects.equals(Object, Object)",
" equal(null, null);",
" // BUG: Diagnostic contains: Prefer java.util.Objects.equals(Object, Object)",
" Objects.equal(null, null);",
" // BUG: Diagnostic contains: Prefer java.util.Objects.equals(Object, Object)",
" com.google.common.base.Objects.equal(null, null);",
" // BUG: Diagnostic contains: Prefer java.util.Objects.equals(Object, Object)",
" BiPredicate<?, ?> p1 = Objects::equal;",
" // BUG: Diagnostic contains: Prefer java.util.Objects.equals(Object, Object)",
" BiPredicate<?, ?> p2 = com.google.common.base.Objects::equal;",
"",
" java.util.Objects.equals(null, null);",
" BiPredicate<?, ?> p3 = java.util.Objects::equals;",
"",
" Dummy.equal(null, null);",
" }",
"",
" static final class Dummy {",
" static boolean equal(Object a, Object b) {",
" return false;",
" }",
" }",
"}")
.doTest();
}
@Test
void varargsMethodWithObjectArgumentsIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import com.google.common.base.Objects;",
"import java.util.function.ToIntFunction;",
"",
"class A {",
" {",
" // BUG: Diagnostic contains: Prefer java.util.Objects.hash(Object...)",
" Objects.hashCode((Object) null);",
" // BUG: Diagnostic contains: Prefer java.util.Objects.hash(Object...)",
" com.google.common.base.Objects.hashCode(null, null);",
" // BUG: Diagnostic contains: Prefer java.util.Objects.hash(Object...)",
" ToIntFunction<?> f1 = Objects::hashCode;",
" // BUG: Diagnostic contains: Prefer java.util.Objects.hash(Object...)",
" ToIntFunction<?> f2 = com.google.common.base.Objects::hashCode;",
"",
" java.util.Objects.hash(null, null, null);",
" ToIntFunction<?> f3 = java.util.Objects::hash;",
"",
" Dummy.hashCode(null, null, null, null);",
" }",
"",
" static final class Dummy {",
" static int hashCode(Object... objects) {",
" return 0;",
" }",
" }",
"}")
.doTest();
}
@Test
void binaryConstructorWithByteArrayAndObjectArgumentsIdentification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import java.io.UnsupportedEncodingException;",
"import java.nio.charset.StandardCharsets;",
"",
"class A {",
" void m() throws UnsupportedEncodingException {",
" // BUG: Diagnostic contains: Prefer java.lang.String.<init>(byte[], java.nio.charset.Charset)",
" new String(new byte[0], \"\");",
" // BUG: Diagnostic contains: Prefer java.lang.String.<init>(byte[], java.nio.charset.Charset)",
" new java.lang.String(new byte[] {}, toString());",
"",
" new String(new byte[0], StandardCharsets.UTF_8);",
" new java.lang.String(new byte[0], StandardCharsets.UTF_8);",
"",
" new Dummy(new byte[0], \"\");",
" }",
"",
" static final class Dummy {",
" Dummy(byte bytes[], String charsetName) {}",
" }",
"}")
.doTest();
}
}

42
pom.xml
View File

@@ -88,6 +88,7 @@
-XX:+UseParallelGC
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
@@ -358,12 +359,14 @@
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<!-- Specified so that Renovate will file Maven upgrade PRs, which
subsequently will cause `maven-enforcer-plugin` to require that
developers build the project using the latest Maven release. -->
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<artifactId>maven-artifact</artifactId>
<version>${version.maven}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-model</artifactId>
<version>${version.maven}</version>
</dependency>
<dependency>
@@ -381,6 +384,29 @@
<artifactId>checker-qual</artifactId>
<version>3.24.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-classworlds</artifactId>
<version>2.6.0</version>
</dependency>
<!-- XXX: This one is declared only to appease the
`license-maven-plugin`. File a PR upstream to pull in a more recent
version. -->
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-container-default</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
@@ -1108,6 +1134,7 @@
non-distributed (i.e. Picnic-internal) deployable
artifacts (i.e. web services). -->
<includedLicense>Apache-2.0</includedLicense>
<includedLicense>BSD-2-Clause</includedLicense>
<includedLicense>BSD-3-Clause</includedLicense>
<includedLicense>CC0-1.0</includedLicense>
<includedLicense>CDDL-1.1</includedLicense>
@@ -1135,6 +1162,11 @@
| The Apache License, Version 2.0
| The Apache Software License, Version 2.0
</licenseMerge>
<licenseMerge>
<!-- -->
BSD-2-Clause
| The BSD License
</licenseMerge>
<licenseMerge>
<!-- -->
BSD-3-Clause
@@ -1742,6 +1774,8 @@
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>