Skip to content

Commit

Permalink
Implementation of the new extension selection algorithm as proposed in
Browse files Browse the repository at this point in the history
…quarkusio#2737.

Impact of the new selection algorithm on the guides.

Most of it is purely cosmetic and allow using shorter names. Some tweaks are required to avoid ambiguity.

Do not use dash in bean validation.

Remove the swagger-ui short name. Swagger may mean something else.
  • Loading branch information
cescoffier committed Jun 19, 2019
1 parent 5d03b07 commit 1891574
Show file tree
Hide file tree
Showing 18 changed files with 451 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ public CommandResult execute(CommandInvocation commandInvocation) throws Command
File pomFile = new File(pom.getAbsolutePath());
AddExtensions project = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()),
pomFile.getName());
project.addExtensions(Collections.singleton(extension));
AddExtensionResult result = project.addExtensions(Collections.singleton(extension));
if (!result.succeeded()) {
throw new CommandException("Unable to add an extension matching " + extension);
}
} catch (IOException e) {
e.printStackTrace();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class Extension {

private String simplifiedArtifactId;
private static final Pattern QUARKUS_PREFIX = Pattern.compile("^quarkus-");
private String shortName;

public Extension() {
// Use by mapper.
Expand Down Expand Up @@ -230,4 +231,13 @@ public boolean equals(Object obj) {
public String getGuide() {
return guide;
}

public String getShortName() {
return shortName;
}

public Extension setShortName(String shortName) {
this.shortName = shortName;
return this;
}
}
8 changes: 8 additions & 0 deletions devtools/common/src/main/filtered/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
{
"name": "Arc",
"shortName": "CDI",
"labels": [
"arc",
"cdi",
Expand Down Expand Up @@ -69,6 +70,7 @@
},
{
"name": "Hibernate ORM",
"shortName": "JPA",
"labels": [
"hibernate-orm",
"jpa",
Expand Down Expand Up @@ -105,6 +107,7 @@
},
{
"name": "Hibernate Validator",
"shortName": "bean validation",
"labels": [
"hibernate-validator",
"bean-validation",
Expand Down Expand Up @@ -242,6 +245,7 @@
},
{
"name": "RESTEasy",
"shortName": "jax-rs",
"labels": [
"resteasy",
"jaxrs",
Expand Down Expand Up @@ -412,6 +416,7 @@
},
{
"name": "SmallRye Reactive Messaging - Kafka Connector",
"shortName": "kafka",
"labels": [
"kafka",
"reactive-kafka"
Expand Down Expand Up @@ -462,6 +467,7 @@
},
{
"name": "Undertow",
"shortName": "servlet",
"labels": [
"undertow",
"servlet"
Expand All @@ -471,6 +477,7 @@
},
{
"name": "Undertow WebSockets",
"shortName": "websockets",
"labels": [
"undertow-websockets",
"undertow-websocket",
Expand All @@ -485,6 +492,7 @@
},
{
"name": "Eclipse Vert.x",
"shortName": "vert.x",
"labels": [
"eclipse-vert.x",
"vertx",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.quarkus.cli.commands;

public class AddExtensionResult {

private final boolean updated;
private final boolean succeeded;

public AddExtensionResult(boolean updated, boolean succeeded) {
this.updated = updated;
this.succeeded = succeeded;
}

public boolean isUpdated() {
return updated;
}

public boolean succeeded() {
return succeeded;
}
}
160 changes: 113 additions & 47 deletions devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
Expand All @@ -18,9 +19,10 @@
import io.quarkus.maven.utilities.MojoUtils;

public class AddExtensions {
private static String OK = "\u2705";
private static String NOK = "\u274c";
private static String NOOP = "\uD83D\uDC4D";
private static final String OK = "\u2705";
private static final String NOK = "\u274c";
private static final String NOOP = "\uD83D\uDC4D";

private Model model;
private String pom;
private ProjectWriter writer;
Expand All @@ -31,57 +33,96 @@ public AddExtensions(final ProjectWriter writer, final String pom) throws IOExce
this.pom = pom;
}

public boolean addExtensions(final Set<String> extensions) throws IOException {
/**
* Selection algorithm.
*
* @param query the query
* @param extensions the extension list
* @return the list of matching candidates and whether or not a match has been found.
*/
static SelectionResult select(String query, List<Extension> extensions) {
String q = query.trim().toLowerCase();

// Try exact matches
Set<Extension> matchesNameOrArtifactId = extensions.stream().filter(extension -> extension.getName().equalsIgnoreCase(q)
|| matchesArtifactId(extension.getArtifactId(), q)).collect(Collectors.toSet());

if (matchesNameOrArtifactId.size() == 1) {
return new SelectionResult(matchesNameOrArtifactId, true);
}

// Try short names
Set<Extension> matchesShortName = extensions.stream().filter(extension -> matchesShortName(extension, q))
.collect(Collectors.toSet());

if (matchesShortName.size() == 1) {
return new SelectionResult(matchesShortName, true);
}

// find by labels
List<Extension> matchesLabels = extensions.stream()
.filter(extension -> extension.labels().contains(q)).collect(Collectors.toList());

Set<Extension> candidates = new LinkedHashSet<>();
candidates.addAll(matchesNameOrArtifactId);
candidates.addAll(matchesShortName);
candidates.addAll(matchesLabels);
return new SelectionResult(candidates, false);
}

private static boolean matchesShortName(Extension extension, String q) {
return q.equalsIgnoreCase(extension.getShortName());
}

private static boolean matchesArtifactId(String artifactId, String q) {
return (artifactId.equalsIgnoreCase(q) ||
artifactId.equalsIgnoreCase("quarkus-" + q) ||
artifactId.equalsIgnoreCase("quarkus-smallrye-" + q));
}

public AddExtensionResult addExtensions(final Set<String> extensions) throws IOException {
if (extensions == null || extensions.isEmpty()) {
return false;
return new AddExtensionResult(false, true);
}

boolean updated = false;
boolean success = true;
List<Dependency> dependenciesFromBom = getDependenciesFromBom();

for (String dependency : extensions) {
List<Extension> matches = MojoUtils.loadExtensions().stream()
.filter(d -> {
boolean hasTag = d.labels().contains(dependency.trim().toLowerCase());
boolean machName = d.getName()
.toLowerCase()
.contains(dependency.trim().toLowerCase());
boolean matchArtifactId = d.getArtifactId()
.toLowerCase()
.contains(dependency.trim().toLowerCase());
return hasTag || machName || matchArtifactId;
})
.collect(Collectors.toList());

if (matches.size() > 1) {
for (String query : extensions) {
List<Extension> registry = MojoUtils.loadExtensions();

SelectionResult result = select(query, registry);

if (!result.matches()) {
StringBuilder sb = new StringBuilder();
sb.append(NOK)
.append(" Multiple extensions matching '" + dependency + "'");

matches.stream()
.forEach(extension -> sb.append(System.lineSeparator()).append(" * ")
.append(extension.managementKey()));
sb.append(System.lineSeparator()).append(" Be more specific e.g using the exact name or the full gav.");
System.out.println(sb);
} else if (matches.size() == 1) {
final Extension extension = matches.get(0);

if (!MojoUtils.hasDependency(model, extension.getGroupId(), extension.getArtifactId())) {
System.out.println(OK + " Adding extension " + extension.managementKey());
model.addDependency(extension
.toDependency(containsBOM(model) &&
isDefinedInBom(dependenciesFromBom, extension)));
updated = true;
// We have 3 cases, we can still have a single candidate, but the match is on label
// or we have several candidates, or none
Set<Extension> candidates = result.getExtensions();
if (candidates.isEmpty() && query.contains(":")) {
updated = addExtensionAsGAV(query) || updated;
} else if (candidates.isEmpty()) {
// No matches at all.
print(NOK + " Cannot find a dependency matching '" + query + "', maybe a typo?");
success = false;
} else if (candidates.size() == 1) {
sb.append(NOK).append(" One extension matching '").append(query).append("'");
sb.append(System.lineSeparator()).append(" * ").append(candidates.iterator().next().managementKey());
sb.append(System.lineSeparator()).append(" Use the exact name or the full GAV to add the extension");
print(sb.toString());
success = false;
} else {
System.out.println(NOOP + " Skipping extension " + extension.managementKey() + ": already present");
sb.append(NOK).append(" Multiple extensions matching '").append(query).append("'");
result.getExtensions()
.forEach(extension -> sb.append(System.lineSeparator()).append(" * ")
.append(extension.managementKey()));
sb.append(System.lineSeparator()).append(" Be more specific e.g using the exact name or the full GAV.");
print(sb.toString());
success = false;
}
} else if (dependency.contains(":")) {
Dependency parsed = MojoUtils.parse(dependency);
System.out.println(OK + " Adding dependency " + parsed.getManagementKey());
model.addDependency(parsed);
updated = true;
} else {
System.out.println(NOK + " Cannot find a dependency matching '" + dependency + "', maybe a typo?");
} else { // Matches.
final Extension extension = result.getMatch();
updated = addDependency(dependenciesFromBom, extension) || updated;
}
}

Expand All @@ -91,7 +132,32 @@ public boolean addExtensions(final Set<String> extensions) throws IOException {
writer.write(pom, pomOutputStream.toString("UTF-8"));
}

return updated;
return new AddExtensionResult(updated, success);
}

private boolean addDependency(List<Dependency> dependenciesFromBom, Extension extension) {
if (!MojoUtils.hasDependency(model, extension.getGroupId(), extension.getArtifactId())) {
print(OK + " Adding extension " + extension.managementKey());
model.addDependency(extension
.toDependency(containsBOM(model) &&
isDefinedInBom(dependenciesFromBom, extension)));
return true;
} else {
// Don't set success to false as it's should be idempotent.
print(NOOP + " Skipping extension " + extension.managementKey() + ": already present");
return false;
}
}

private boolean addExtensionAsGAV(String query) {
Dependency parsed = MojoUtils.parse(query.trim().toLowerCase());
print(OK + " Adding dependency " + parsed.getManagementKey());
model.addDependency(parsed);
return true;
}

private void print(String message) {
System.out.println(message);
}

private List<Dependency> getDependenciesFromBom() {
Expand All @@ -100,7 +166,7 @@ private List<Dependency> getDependenciesFromBom() {
.getDependencyManagement()
.getDependencies();
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
throw new IllegalStateException("Unable to read the BOM file: " + e.getMessage(), e);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.quarkus.cli.commands;

import java.util.Set;

import io.quarkus.dependencies.Extension;

public class SelectionResult {

private final Set<Extension> extensions;
private final boolean matches;

public SelectionResult(Set<Extension> extensions, boolean matches) {
this.extensions = extensions;
this.matches = matches;
}

public Set<Extension> getExtensions() {
return extensions;
}

public boolean matches() {
return matches;
}

public Extension getMatch() {
if (matches) {
if (extensions.isEmpty() || extensions.size() > 1) {
throw new IllegalStateException("Invalid selection result");
}
return extensions.iterator().next();
}
return null;
}
}
Loading

0 comments on commit 1891574

Please sign in to comment.