Skip to content

Commit

Permalink
adds a basic maven dependency updater hint.
Browse files Browse the repository at this point in the history
 - marks artifacts which have a newer version available
 - provides a fix to bump the version
 - supports maven properties
  • Loading branch information
mbien committed Dec 21, 2022
1 parent a959587 commit bf34dff
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@
<file name="org-netbeans-modules-maven-hints-pom-UseReleaseOptionHint.instance">
<attr name="position" intvalue="1200"/>
</file>
<file name="org-netbeans-modules-maven-hints-pom-UpdateDependencyHint.instance">
<attr name="position" intvalue="1300"/>
</file>
</folder>


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@
import org.netbeans.modules.maven.hints.pom.spi.POMErrorFixBase;
import org.netbeans.modules.maven.hints.pom.spi.POMErrorFixProvider;
import org.netbeans.modules.maven.indexer.api.RepositoryPreferences;
import org.netbeans.modules.maven.model.pom.POMComponent;
import org.netbeans.modules.maven.model.pom.POMModel;
import org.netbeans.modules.maven.model.pom.Properties;
import org.netbeans.modules.xml.xam.Model;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.editor.hints.ErrorDescriptionFactory;
Expand Down Expand Up @@ -151,7 +153,7 @@ static void runMavenValidation(final POMModel model, final List<ErrorDescription
}

public static List<ErrorDescription> findHints(final @NonNull POMModel model, final Project project) {
final List<ErrorDescription> err = new ArrayList<ErrorDescription>();
final List<ErrorDescription> err = new ArrayList<>();
//before checkModelValid because of #216093
runMavenValidation(model, err);
if (!checkModelValid(model)) {
Expand Down Expand Up @@ -230,7 +232,7 @@ static List<ModelProblem> runMavenValidationImpl(final File pom, Document doc) {
try {
problems = embedder.lookupComponent(ProjectBuilder.class).build(new M2S(pom, doc), req).getProblems();
} catch (ProjectBuildingException x) {
problems = new ArrayList<ModelProblem>();
problems = new ArrayList<>();
List<ProjectBuildingResult> results = x.getResults();
if (results != null) { //one code point throwing ProjectBuildingException contains results,
for (ProjectBuildingResult result : results) {
Expand All @@ -244,7 +246,7 @@ static List<ModelProblem> runMavenValidationImpl(final File pom, Document doc) {
}
}
}
List<ModelProblem> toRet = new LinkedList<ModelProblem>();
List<ModelProblem> toRet = new LinkedList<>();
for (ModelProblem problem : problems) {
if(ModelUtils.checkByCLIMavenValidationLevel(problem)) {
toRet.add(problem);
Expand Down Expand Up @@ -272,5 +274,46 @@ static <T extends POMErrorFixBase> List<T> hintProviders(Project project, Class<
});
return providers;
}


/**
* Returns true if the given text could be a maven property.
*/
static boolean isPropertyExpression(String expression) {
return expression != null && expression.startsWith("${") && expression.endsWith("}");
}

/**
* Returns the property name of a property expression.
*/
static String getPropertyName(String expression) {
if (isPropertyExpression(expression)) {
return expression.substring(2, expression.length() - 1);
}
return expression;
}

/**
* Returns the value of the maven property or null.
* @param expression the property text, for example: <code>${java.version}</code>.
*/
static String getProperty(POMModel model, String expression) {
Properties properties = model.getProject().getProperties();
if (properties != null) {
return properties.getProperty(getPropertyName(expression));
}
return null;
}

/**
* Returns the first child component with the given name or null.
*/
static POMComponent getFirstChild(POMComponent parent, String name) {
for (POMComponent child : parent.getChildren()) {
if (name.equals(child.getPeer().getNodeName())) {
return child;
}
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.netbeans.modules.maven.hints.pom;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.prefs.Preferences;
import javax.swing.JComponent;
import javax.swing.text.Document;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.netbeans.api.project.Project;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.modules.maven.hints.pom.spi.Configuration;
import org.netbeans.modules.maven.hints.pom.spi.POMErrorFixProvider;
import org.netbeans.modules.maven.indexer.api.NBVersionInfo;
import org.netbeans.modules.maven.indexer.api.RepositoryQueries;
import org.netbeans.modules.maven.model.pom.Build;
import org.netbeans.modules.maven.model.pom.Dependency;
import org.netbeans.modules.maven.model.pom.DependencyManagement;
import org.netbeans.modules.maven.model.pom.POMComponent;
import org.netbeans.modules.maven.model.pom.POMExtensibilityElement;
import org.netbeans.modules.maven.model.pom.POMModel;
import org.netbeans.modules.maven.model.pom.PluginManagement;
import org.netbeans.modules.maven.model.pom.Properties;
import org.netbeans.modules.maven.model.pom.VersionablePOMComponent;
import org.netbeans.spi.editor.hints.ChangeInfo;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.editor.hints.ErrorDescriptionFactory;
import org.netbeans.spi.editor.hints.Fix;
import org.netbeans.spi.editor.hints.Severity;
import org.openide.util.NbBundle;

import static org.netbeans.modules.maven.hints.pom.Bundle.*;

/**
* Marks the artifact if there is a newer version available and provides a fix
* which updates the version.
* @author mbien
*/
@NbBundle.Messages({
"TIT_UpdateDependencyHint=Mark artifact update opportunities.",
"DESC_UpdateDependencyHint=Marks the artifact if there is a newer version available.",
"HINT_UpdateDependencyHint=New version available: ",
"FIX_UpdateDependencyHint=update to: "})
public class UpdateDependencyHint implements POMErrorFixProvider {

private static final Configuration config = new Configuration(UpdateDependencyHint.class.getName(),
TIT_UpdateDependencyHint(), DESC_UpdateDependencyHint(), true, Configuration.HintSeverity.WARNING);

private volatile boolean cancelled;


@Override
public List<ErrorDescription> getErrorsForDocument(POMModel model, Project prj) {

cancelled = false;

Map<POMComponent, ErrorDescription> hints = new HashMap<>();

List<Dependency> deps = model.getProject().getDependencies();
if (deps != null) {
addHintsTo(deps, hints);
}

DependencyManagement depman = model.getProject().getDependencyManagement();
if (depman != null && depman.getDependencies() != null) {
addHintsTo(depman.getDependencies(), hints);
}

Build build = model.getProject().getBuild();
if (build != null) {
if (build.getPlugins() != null) {
addHintsTo(build.getPlugins(), hints);
}

PluginManagement plugman = build.getPluginManagement();
if (plugman != null && plugman.getPlugins() != null) {
addHintsTo(plugman.getPlugins(), hints);
}
}

return new ArrayList<>(hints.values());
}

private void addHintsTo(List<? extends VersionablePOMComponent> components, Map<POMComponent, ErrorDescription> hints) {

for (VersionablePOMComponent comp : components) {

if (cancelled) {
return;
}

String groupId = comp.getGroupId();
String artifactId = comp.getArtifactId();

if (groupId != null && artifactId != null && !groupId.isEmpty() && !artifactId.isEmpty()) {

boolean property = false;
String version = comp.getVersion();
if (PomModelUtils.isPropertyExpression(version)) {
version = PomModelUtils.getProperty(comp.getModel(), version);
property = true;
}

if (version != null) {

// don't upgrade numerical versions to timestamps or non-numerical versions (other way around is ok)
boolean allow_qualifier = !isNumerical(version);
boolean allow_timestamp = !noTimestamp(version);

Optional<ComparableVersion> latest = RepositoryQueries.getVersionsResult(groupId, artifactId, null)
.getResults().stream()
.map(NBVersionInfo::getVersion)
.filter((v) -> !v.isEmpty())
.filter((v) -> allow_qualifier || !Character.isDigit(v.charAt(0)) || isNumerical(v))
.filter((v) -> allow_timestamp || !Character.isDigit(v.charAt(0)) || noTimestamp(v))
.map(ComparableVersion::new)
.max(ComparableVersion::compareTo);

if (latest.isPresent() && latest.get().compareTo(new ComparableVersion(version)) > 0) {
POMComponent version_comp = null;
if (property) {
Properties props = comp.getModel().getProject().getProperties();
if (props != null) {
version_comp = PomModelUtils.getFirstChild(props, PomModelUtils.getPropertyName(comp.getVersion()));
}
} else {
version_comp = PomModelUtils.getFirstChild(comp, "version");
}
if (version_comp instanceof POMExtensibilityElement) {
ErrorDescription previous = hints.get(version_comp);
if (previous == null || compare(((UpdateVersionFix) previous.getFixes().getFixes().get(0)).version, version) > 0) {
hints.put(version_comp, createHintForComponent((POMExtensibilityElement) version_comp, latest.get().toString()));
}
}
}
}
}
}
}

private static boolean isNumerical(String v) {
for (char c : v.toCharArray()) {
if (!(Character.isDigit(c) || c == '.')) {
return false;
}
}
return true;
}

private boolean noTimestamp(String v) {
char[] chars = v.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (!(Character.isDigit(chars[i]))) {
return i == 0 || Integer.parseInt(v.substring(0, i)) < 10_000;
}
}
return Integer.parseInt(v) < 10_000;
}

private static int compare(String version1, String version2) {
return new ComparableVersion(version1).compareTo(new ComparableVersion(version2));
}

private ErrorDescription createHintForComponent(POMExtensibilityElement comp, String version) {
Document doc = comp.getModel().getBaseDocument();
int line = NbEditorUtilities.getLine(doc, comp.findPosition(), false).getLineNumber() + 1;
List<Fix> fix = Collections.singletonList(new UpdateVersionFix(comp, version));
return ErrorDescriptionFactory.createErrorDescription(Severity.HINT, HINT_UpdateDependencyHint() + version, fix, doc, line);
}

private static class UpdateVersionFix implements Fix {

private final POMExtensibilityElement version_comp;
private final String version;

private UpdateVersionFix(POMExtensibilityElement component, String version) {
this.version_comp = component;
this.version = version;
}

@Override
public String getText() {
return FIX_UpdateDependencyHint() + version;
}

@Override
public ChangeInfo implement() throws Exception {
PomModelUtils.implementInTransaction(version_comp.getModel(), () -> {
version_comp.setElementText(version);
});
return new ChangeInfo();
}

}

@Override
public void cancel() {
cancelled = true;
}

@Override
public Configuration getConfiguration() {
return config;
}

@Override
public String getSavedValue(JComponent customizer, String key) {
return null;
}

@Override
public JComponent getCustomizer(Preferences preferences) {
return null;
}

}
Loading

0 comments on commit bf34dff

Please sign in to comment.