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
  • Loading branch information
cescoffier committed Jun 11, 2019
1 parent e76ae7c commit 5b3803a
Show file tree
Hide file tree
Showing 9 changed files with 433 additions and 57 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 @@ -46,6 +46,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 @@ -246,4 +247,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 @@ -231,6 +234,7 @@
},
{
"name": "RESTEasy",
"shortName": "jax-rs",
"labels": [
"resteasy",
"jaxrs",
Expand Down Expand Up @@ -401,6 +405,7 @@
},
{
"name": "SmallRye Reactive Messaging - Kafka Connector",
"shortName": "kafka",
"labels": [
"kafka",
"reactive-kafka"
Expand Down Expand Up @@ -432,6 +437,7 @@
},
{
"name": "Swagger UI",
"shortName": "swagger",
"labels": [
"swagger-ui"
],
Expand All @@ -441,6 +447,7 @@
},
{
"name": "Undertow",
"shortName": "servlet",
"labels": [
"undertow",
"servlet"
Expand All @@ -450,6 +457,7 @@
},
{
"name": "Undertow WebSockets",
"shortName": "websockets",
"labels": [
"undertow-websockets",
"undertow-websocket",
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 5b3803a

Please sign in to comment.