diff --git a/java/maven.hints/src/org/netbeans/modules/maven/hints/layer.xml b/java/maven.hints/src/org/netbeans/modules/maven/hints/layer.xml
index ad64474723e2..812b81420ac4 100644
--- a/java/maven.hints/src/org/netbeans/modules/maven/hints/layer.xml
+++ b/java/maven.hints/src/org/netbeans/modules/maven/hints/layer.xml
@@ -65,6 +65,9 @@
+
+
+
diff --git a/java/maven.hints/src/org/netbeans/modules/maven/hints/pom/PomModelUtils.java b/java/maven.hints/src/org/netbeans/modules/maven/hints/pom/PomModelUtils.java
index bcc6ab6da2ae..400f7d9e6395 100644
--- a/java/maven.hints/src/org/netbeans/modules/maven/hints/pom/PomModelUtils.java
+++ b/java/maven.hints/src/org/netbeans/modules/maven/hints/pom/PomModelUtils.java
@@ -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;
@@ -151,7 +153,7 @@ static void runMavenValidation(final POMModel model, final List findHints(final @NonNull POMModel model, final Project project) {
- final List err = new ArrayList();
+ final List err = new ArrayList<>();
//before checkModelValid because of #216093
runMavenValidation(model, err);
if (!checkModelValid(model)) {
@@ -230,7 +232,7 @@ static List 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();
+ problems = new ArrayList<>();
List results = x.getResults();
if (results != null) { //one code point throwing ProjectBuildingException contains results,
for (ProjectBuildingResult result : results) {
@@ -244,7 +246,7 @@ static List runMavenValidationImpl(final File pom, Document doc) {
}
}
}
- List toRet = new LinkedList();
+ List toRet = new LinkedList<>();
for (ModelProblem problem : problems) {
if(ModelUtils.checkByCLIMavenValidationLevel(problem)) {
toRet.add(problem);
@@ -272,5 +274,46 @@ static List 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: ${java.version}
.
+ */
+ 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;
+ }
+
}
diff --git a/java/maven.hints/src/org/netbeans/modules/maven/hints/pom/UpdateDependencyHint.java b/java/maven.hints/src/org/netbeans/modules/maven/hints/pom/UpdateDependencyHint.java
new file mode 100644
index 000000000000..d0a7f59dc207
--- /dev/null
+++ b/java/maven.hints/src/org/netbeans/modules/maven/hints/pom/UpdateDependencyHint.java
@@ -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 getErrorsForDocument(POMModel model, Project prj) {
+
+ cancelled = false;
+
+ Map hints = new HashMap<>();
+
+ List 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 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 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 = 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;
+ }
+
+}
diff --git a/java/maven.hints/src/org/netbeans/modules/maven/hints/pom/UseReleaseOptionHint.java b/java/maven.hints/src/org/netbeans/modules/maven/hints/pom/UseReleaseOptionHint.java
index 737b5bb39bab..b51a3b913936 100644
--- a/java/maven.hints/src/org/netbeans/modules/maven/hints/pom/UseReleaseOptionHint.java
+++ b/java/maven.hints/src/org/netbeans/modules/maven/hints/pom/UseReleaseOptionHint.java
@@ -25,6 +25,7 @@
import java.util.prefs.Preferences;
import javax.swing.JComponent;
import javax.xml.namespace.QName;
+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;
@@ -61,6 +62,8 @@ public class UseReleaseOptionHint implements POMErrorFixProvider {
private static final String SOURCE_TAG = "source";
private static final String RELEASE_TAG = "release";
+ private static final ComparableVersion MIN_VERSION = new ComparableVersion("3.6");
+
private static final Configuration config = new Configuration(UseReleaseOptionHint.class.getName(),
TIT_UseReleaseVersionHint(), DESC_UseReleaseVersionHint(), true, Configuration.HintSeverity.WARNING);
@@ -113,15 +116,15 @@ private List createHintsForParent(String prefix, POMComponent
try {
String sourceText = parent.getChildElementText(POMQName.createQName(prefix+SOURCE_TAG, true));
- if (isProperty(sourceText)) {
+ if (PomModelUtils.isPropertyExpression(sourceText)) {
release = sourceText;
- sourceText = getProperty(sourceText, parent.getModel());
+ sourceText = PomModelUtils.getProperty(parent.getModel(), sourceText);
}
String targetText = parent.getChildElementText(POMQName.createQName(prefix+TARGET_TAG, true));
- if (isProperty(targetText)) {
+ if (PomModelUtils.isPropertyExpression(targetText)) {
release = targetText;
- targetText = getProperty(targetText, parent.getModel());
+ targetText = PomModelUtils.getProperty(parent.getModel(), targetText);
}
source = Integer.parseInt(sourceText);
@@ -157,35 +160,11 @@ private ErrorDescription createHintForComponent(String prefix, POMComponent comp
* maven-compiler-plugin version must be >= 3.6
*/
private boolean isPluginCompatible(Plugin plugin) {
- String string = plugin.getVersion();
- if (string == null) {
- return false;
- }
- String[] version = string.split("-")[0].split("\\.");
- try {
- int major = version.length > 0 ? Integer.parseInt(version[0]) : 0;
- int minor = version.length > 1 ? Integer.parseInt(version[1]) : 0;
- if (major < 3 || (major == 3 && minor < 6)) {
- return false;
- }
- } catch (NumberFormatException ignored) {
+ String version = plugin.getVersion();
+ if (version == null || version.isEmpty()) {
return false;
}
- return true;
- }
-
- private static boolean isProperty(String property) {
- return property != null && property.startsWith("$");
- }
-
- private static String getProperty(String prop, POMModel model) {
- if (prop.length() > 3) {
- Properties properties = model.getProject().getProperties();
- if (properties != null) {
- return properties.getProperty(prop.substring(2, prop.length()-1));
- }
- }
- return null;
+ return new ComparableVersion(version).compareTo(MIN_VERSION) >= 0;
}
private static class ConvertToReleaseOptionFix implements Fix {