Skip to content

Commit

Permalink
Implement maven pom javac release option hint (JEP 247).
Browse files Browse the repository at this point in the history
 - converts matching source/target options to the release option
 - supports properties and the standard maven-compiler-plugin
  • Loading branch information
mbien committed Oct 17, 2022
1 parent 1092e96 commit 2680d68
Show file tree
Hide file tree
Showing 3 changed files with 373 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@
<file name="org-netbeans-modules-maven-hints-pom-CompilerPluginVersionError.instance">
<attr name="position" intvalue="1100"/>
</file>
<file name="org-netbeans-modules-maven-hints-pom-UseReleaseOptionHint.instance">
<attr name="position" intvalue="1200"/>
</file>
</folder>


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
/*
* 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.List;
import java.util.prefs.Preferences;
import javax.swing.JComponent;
import javax.xml.namespace.QName;
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.model.pom.Build;
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.POMQName;
import org.netbeans.modules.maven.model.pom.Plugin;
import org.netbeans.modules.maven.model.pom.PluginExecution;
import org.netbeans.modules.maven.model.pom.Properties;
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.text.Line;
import org.openide.util.NbBundle;

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

/**
* Converts source/target javac options to the release option where possible (JEP 247).
* @author mbien
*/
@NbBundle.Messages({
"TIT_UseReleaseVersionHint=Convert matching source/target javac options to the release option.",
"DESC_UseReleaseVersionHint=Matching source/target options can be replaced with a single release option (JEP 247). This enforces API compatibility with the chosen Java release.",
"FIX_UseReleaseVersionHint=Convert to release option for strict compatibility checks."})
public class UseReleaseOptionHint implements POMErrorFixProvider {

private static final String TARGET_TAG = "target";
private static final String SOURCE_TAG = "source";
private static final String RELEASE_TAG = "release";

private static final Configuration config = new Configuration(UseReleaseOptionHint.class.getName(),
TIT_UseReleaseVersionHint(), DESC_UseReleaseVersionHint(), true, Configuration.HintSeverity.WARNING);

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

List<ErrorDescription> hints = new ArrayList<>();

Properties properties = model.getProject().getProperties();
if (properties != null) {
hints.addAll(createHintsForParent("maven.compiler.", properties));
}

Build build = model.getProject().getBuild();
if (build != null) {
for (Plugin plugin : build.getPlugins()) {
if (plugin.getArtifactId().equals("maven-compiler-plugin")) {
if (plugin.getConfiguration() != null) {
hints.addAll(createHintsForParent("", plugin.getConfiguration()));
}
if (plugin.getExecutions() != null) {
for (PluginExecution execution : plugin.getExecutions()) {
if (execution.getConfiguration() != null) {
hints.addAll(createHintsForParent("", execution.getConfiguration()));
}
}
}
break;
}
}
}

return hints;
}

private List<ErrorDescription> createHintsForParent(String prefix, POMComponent parent) {

int source;
int target;

// property name or an int value
String release = null;

try {
String sourceText = parent.getChildElementText(POMQName.createQName(prefix+SOURCE_TAG, true));
if (isPropertyName(sourceText)) {
release = sourceText;
sourceText = getProperty(sourceText, parent.getModel());
}

String targetText = parent.getChildElementText(POMQName.createQName(prefix+TARGET_TAG, true));
if (isPropertyName(targetText)) {
release = targetText;
targetText = getProperty(targetText, parent.getModel());
}

source = Integer.parseInt(sourceText);
target = Integer.parseInt(targetText);
if (release == null) {
release = String.valueOf(target);
}
} catch (NumberFormatException ignored) {
return Collections.emptyList();
}

if (source == target && source >= 9) {
List<ErrorDescription> hints = new ArrayList<>();
for (POMComponent prop : parent.getChildren()) {
String name = prop.getPeer().getNodeName();
if (name.equals(prefix+SOURCE_TAG) || name.equals(prefix+TARGET_TAG)) {
hints.add(createHintForComponent(prefix, prop, parent.getModel(), release));
}
}
return hints;
}
return Collections.emptyList();
}

private ErrorDescription createHintForComponent(String prefix, POMComponent component, POMModel model, String release) {
Line line = NbEditorUtilities.getLine(model.getBaseDocument(), component.findPosition(), false);
List<Fix> fix = Collections.singletonList(new ConvertToReleaseOptionFix(prefix, release, component));
return ErrorDescriptionFactory.createErrorDescription(Severity.HINT, FIX_UseReleaseVersionHint(), fix, model.getBaseDocument(), line.getLineNumber()+1);
}

private static boolean isPropertyName(String name) {
return name != null && name.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;
}

private static class ConvertToReleaseOptionFix implements Fix {

private final String prefix;
private final String release;
private final POMComponent component;

private ConvertToReleaseOptionFix(String prefix, String release, POMComponent component) {
this.prefix = prefix;
this.component = component;
this.release = release;
}

@Override
public ChangeInfo implement() throws Exception {
ChangeInfo info = new ChangeInfo();
POMModel model = component.getModel();
PomModelUtils.implementInTransaction(model, () -> {
POMComponent parent = component.getParent();
for (POMComponent child : parent.getChildren()) {
String name = child.getPeer().getNodeName();
if (name.equals(prefix+SOURCE_TAG) || name.equals(prefix+TARGET_TAG)) {
parent.removeExtensibilityElement((POMExtensibilityElement) child);
}
}
POMExtensibilityElement element = model.getFactory().createPOMExtensibilityElement(QName.valueOf(prefix+RELEASE_TAG));
element.setElementText(release);
parent.addExtensibilityElement(element);
});
return info;
}

@Override
public String getText() {
return FIX_UseReleaseVersionHint();
}

}

@Override
public void cancel() {}

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

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

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* 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.List;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.junit.NbTestCase;
import org.netbeans.modules.maven.model.Utilities;
import org.netbeans.modules.maven.model.pom.POMModel;
import org.netbeans.modules.maven.model.pom.POMModelFactory;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.test.TestFileUtils;

import static junit.framework.TestCase.assertEquals;

/**
*
* @author mbien
*/
public class UseReleaseOptionHintTest extends NbTestCase {

private FileObject work;

public UseReleaseOptionHintTest(String name) {
super(name);
}

@Override
protected void setUp() throws Exception {
clearWorkDir();
work = FileUtil.toFileObject(getWorkDir());
}

public void testProperties() throws Exception {
FileObject pom = TestFileUtils.writeFile(work, "pom.xml",
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
" <modelVersion>4.0.0</modelVersion>\n" +
" <groupId>test</groupId>\n" +
" <artifactId>mavenproject1</artifactId>\n" +
" <version>1.0-SNAPSHOT</version>\n" +
" <packaging>jar</packaging>\n" +
" <properties>\n" +
" <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n" +
" <exec.mainClass>test.mavenproject1.Mavenproject1</exec.mainClass>\n" +
" <maven.compiler.source>11</maven.compiler.source>\n" +
" <maven.compiler.target>11</maven.compiler.target>\n" +
" </properties>\n" +
"</project>");

POMModel model = POMModelFactory.getDefault().getModel(Utilities.createModelSource(pom));
Project project = ProjectManager.getDefault().findProject(pom.getParent());

List<ErrorDescription> hints = new UseReleaseOptionHint().getErrorsForDocument(model, project);
assertEquals(2, hints.size());
}

public void testPropertiesNegative() throws Exception {
FileObject pom = TestFileUtils.writeFile(work, "pom.xml",
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
" <modelVersion>4.0.0</modelVersion>\n" +
" <groupId>test</groupId>\n" +
" <artifactId>mavenproject1</artifactId>\n" +
" <version>1.0-SNAPSHOT</version>\n" +
" <packaging>jar</packaging>\n" +
" <properties>\n" +
" <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n" +
" <exec.mainClass>test.mavenproject1.Mavenproject1</exec.mainClass>\n" +
" <maven.compiler.source>8</maven.compiler.source>\n" +
" <maven.compiler.target>8</maven.compiler.target>\n" +
" </properties>\n" +
"</project>");

POMModel model = POMModelFactory.getDefault().getModel(Utilities.createModelSource(pom));
Project project = ProjectManager.getDefault().findProject(pom.getParent());

List<ErrorDescription> hints = new UseReleaseOptionHint().getErrorsForDocument(model, project);
assertEquals(0, hints.size());
}

public void testCompilerPlugin() throws Exception {
FileObject pom = TestFileUtils.writeFile(work, "pom.xml",
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n" +
" <modelVersion>4.0.0</modelVersion>\n" +
" <groupId>test</groupId>\n" +
" <artifactId>mavenproject1</artifactId>\n" +
" <version>1.0-SNAPSHOT</version>\n" +
" <packaging>jar</packaging>\n" +
" <properties>\n" +
" <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n" +
" <exec.mainClass>test.mavenproject1.Mavenproject1</exec.mainClass>\n" +
" <prop>11</prop>\n" +
" </properties>\n" +
" <build>\n" +
" <plugins>\n" +
" <plugin>\n" +
" <groupId>org.apache.maven.plugins</groupId>\n" +
" <artifactId>maven-compiler-plugin</artifactId>\n" +
" <version>3.10.1</version>\n" +
" <configuration>\n" +
" <source>11</source>\n" +
" <target>11</target>\n" +
" </configuration>" +
" <executions>\n" +
" <execution>\n" +
" <id>default-compile</id>\n" +
" <configuration>\n" +
" <source>${prop}</source>\n" +
" <target>${prop}</target>\n" +
" </configuration>\n" +
" </execution>\n" +
" <execution>\n" +
" <id>default-testCompile</id>\n" +
" <configuration>\n" +
" <source>17</source>\n" +
" <target>17</target>\n" +
" </configuration>\n" +
" </execution>\n" +
" </executions>\n" +
" </plugin>\n" +
" </plugins>\n" +
" </build>\n" +
"</project>");

POMModel model = POMModelFactory.getDefault().getModel(Utilities.createModelSource(pom));
Project project = ProjectManager.getDefault().findProject(pom.getParent());

List<ErrorDescription> hints = new UseReleaseOptionHint().getErrorsForDocument(model, project);
assertEquals(6, hints.size());
}

}

0 comments on commit 2680d68

Please sign in to comment.