Skip to content

Feat/java add explicit imports #5318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.openrewrite.java;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Option;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaSourceFile;

@Value
@EqualsAndHashCode(callSuper = false)
public class AddExplicitImport extends Recipe {
@Option(displayName = "List of imports to add",
description = "The list of imports, this field is multiline.",
example = "foo.bar\nbiz.baz")
String imports;

@Override
public String getDisplayName() {
return "Add explicit imports";
}

@Override
public int maxCycles() {
return 1;
}
@Override
public String getDescription() {
return "This recipe adds an explicit import to a single Java file.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(true, new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext executionContext) {
JavaSourceFile javaSourceFile = getCursor().firstEnclosing(JavaSourceFile.class);
if (javaSourceFile != null) {
addExplicitImport(imports);
}
return super.visitCompilationUnit(cu, executionContext);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright 2021 the original author or authors.
* <p>
* Licensed 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.openrewrite.java;

import lombok.EqualsAndHashCode;
import org.jspecify.annotations.Nullable;
import org.openrewrite.Cursor;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.style.PreserveImportLayoutStyle;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JLeftPadded;
import org.openrewrite.java.tree.JRightPadded;
import org.openrewrite.java.tree.JavaSourceFile;
import org.openrewrite.java.tree.Javadoc;
import org.openrewrite.java.tree.Space;
import org.openrewrite.java.tree.TypeTree;
import org.openrewrite.marker.Markers;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static org.openrewrite.Tree.randomId;

@EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = true)
public class AddExplicitImportVisitor<P> extends AddImport<P> {
final String imports;
public AddExplicitImportVisitor(String imports) {
this.imports = imports;
}

@Override
public @Nullable J preVisit(J tree, P p) {
stopAfterPreVisit();
J j = tree;
if (tree instanceof JavaSourceFile) {
JavaSourceFile cu = (JavaSourceFile) tree;
final String[] split = imports.split("\n");
final List<String> receivedImports = Arrays.stream(split)
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(Collectors.toList());

final List<J.Import> jImports = new ArrayList<>();
final Space firstClassPrefix = cu.getClasses().get(0).getPrefix();
final List<JRightPadded<J.Import>> existingImports = new ArrayList<>(cu.getPadding().getImports());
for (String anImport : receivedImports) {
final JLeftPadded<Boolean> statik;
if (anImport.startsWith("static ")) {
statik = JLeftPadded.build(true).withBefore(Space.SINGLE_SPACE);
anImport = anImport.replace("static ", "");
} else {
statik = new JLeftPadded<>(Space.EMPTY, false, Markers.EMPTY);
}
final int lastDotIdx = anImport.lastIndexOf('.');
final String packageName = lastDotIdx != -1 ? anImport.substring(0, lastDotIdx) : null;
if (packageName == null) {
continue;
}

J.Import aJimport = new J.Import(randomId(),
Space.EMPTY,
Markers.EMPTY,
statik,
TypeTree.build(anImport).withPrefix(Space.SINGLE_SPACE),
null);

if ((jImports.isEmpty() || existingImports.isEmpty()) && !cu.getClasses().isEmpty() && cu.getPackageDeclaration() == null) {
aJimport = aJimport.withPrefix(firstClassPrefix
.withComments(ListUtils.map(firstClassPrefix.getComments(),
comment -> comment instanceof Javadoc ? null : comment))
.withWhitespace(""));
}
jImports.add(aJimport);
}

PreserveImportLayoutStyle layoutStyle = new PreserveImportLayoutStyle(cu.getPadding().getImports());
List<JRightPadded<J.Import>> newImports = layoutStyle.addImports(jImports);

// ImportLayoutStyle::addImport adds always `\n` as newlines. Checking if we need to fix them
newImports = checkCRLF(cu, newImports);

cu = cu.getPadding().withImports(newImports);

JavaSourceFile c = cu;
cu = cu.withClasses(ListUtils.mapFirst(cu.getClasses(), clazz -> {
J.ClassDeclaration cl = autoFormat(clazz, clazz.getName(), p, new Cursor(null, c));
return clazz.withPrefix(clazz.getPrefix().withWhitespace(cl.getPrefix().getWhitespace()));
}));

j = cu;
}
return j;
}
}
14 changes: 9 additions & 5 deletions rewrite-java/src/main/java/org/openrewrite/java/AddImport.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@
public class AddImport<P> extends JavaIsoVisitor<P> {

@Nullable
private final String packageName;
protected final String packageName;

private final String typeName;
protected final String typeName;

@EqualsAndHashCode.Include
private final String fullyQualifiedName;
protected final String fullyQualifiedName;

@EqualsAndHashCode.Include
@Nullable
Expand All @@ -77,6 +77,10 @@ public class AddImport<P> extends JavaIsoVisitor<P> {
@Nullable
private final String alias;

public AddImport() {
this("", null, false);
}

public AddImport(String type, @Nullable String member, boolean onlyIfReferenced) {
int lastDotIdx = type.lastIndexOf('.');
this.packageName = lastDotIdx != -1 ? type.substring(0, lastDotIdx) : null;
Expand Down Expand Up @@ -181,7 +185,7 @@ public AddImport(@Nullable String packageName, String typeName, @Nullable String
return j;
}

private List<JRightPadded<J.Import>> checkCRLF(JavaSourceFile cu, List<JRightPadded<J.Import>> newImports) {
protected List<JRightPadded<J.Import>> checkCRLF(JavaSourceFile cu, List<JRightPadded<J.Import>> newImports) {
GeneralFormatStyle generalFormatStyle = Optional.ofNullable(Style.from(GeneralFormatStyle.class, ((SourceFile) cu)))
.orElse(autodetectGeneralFormatStyle(cu));
if (generalFormatStyle.isUseCRLFNewLines()) {
Expand Down Expand Up @@ -212,7 +216,7 @@ private boolean isTypeReference(NameTree t) {
* @return true if the import is referenced by the class either explicitly or through a method reference.
*/
//Note that using anyMatch when a stream is empty ends up returning true, which is not the behavior needed here!
private boolean hasReference(JavaSourceFile compilationUnit) {
protected boolean hasReference(JavaSourceFile compilationUnit) {
if (member == null) {
//Non-static imports, we just look for field accesses.
for (NameTree t : FindTypes.find(compilationUnit, fullyQualifiedName)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ public void maybeAddImport(String fullyQualifiedName, boolean onlyIfReferenced)
maybeAddImport(fullyQualifiedName, null, onlyIfReferenced);
}

public void addExplicitImport(String fullyQualifiedName) {
JavaVisitor<P> visitor = service(ImportService.class).addExplicitImport(fullyQualifiedName);
if (!getAfterVisit().contains(visitor)) {
doAfterVisit(visitor);
}
}

public void maybeAddImport(String fullyQualifiedName, @Nullable String member, boolean onlyIfReferenced) {
int lastDotIdx = fullyQualifiedName.lastIndexOf('.');
String packageName = lastDotIdx != -1 ? fullyQualifiedName.substring(0, lastDotIdx) : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.openrewrite.ExecutionContext;
import org.openrewrite.Incubating;
import org.openrewrite.java.AddImport;
import org.openrewrite.java.AddExplicitImportVisitor;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.ShortenFullyQualifiedTypeReferences;
import org.openrewrite.java.tree.J;
Expand All @@ -34,6 +35,10 @@ public <P> JavaVisitor<P> addImportVisitor(@Nullable String packageName,
return new AddImport<>(packageName, typeName, member, alias, onlyIfReferenced);
}

public <P> JavaVisitor<P> addExplicitImport(String fullyQualifiedName) {
return new AddExplicitImportVisitor<>(fullyQualifiedName);
}

public <J2 extends J> JavaVisitor<ExecutionContext> shortenAllFullyQualifiedTypeReferences() {
return new ShortenFullyQualifiedTypeReferences().getVisitor();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed 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
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.openrewrite.java.style;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JRightPadded;
import org.openrewrite.java.tree.Space;
import org.openrewrite.marker.Markers;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
@Getter
public class PreserveImportLayoutStyle extends ImportLayoutStyle {

final List<JRightPadded<J.Import>> existingImports;

public PreserveImportLayoutStyle(List<JRightPadded<J.Import>> imports) {
super(0, 0, Collections.emptyList(), Collections.emptyList());
this.existingImports = imports;
}

public List<JRightPadded<J.Import>> addImports(List<J.Import> toAdd) {
List<JRightPadded<J.Import>> paddings = new ArrayList<>();
Space prefix = Space.format("\n");
if (existingImports.isEmpty()) {
prefix = Space.EMPTY;
}

boolean first = true;
for (J.Import anImport : toAdd) {
if (!first) {
prefix = Space.format("\n");
}
JRightPadded<J.Import> paddedImport = new JRightPadded<>(anImport, Space.EMPTY, Markers.EMPTY);
paddedImport = paddedImport.withElement(paddedImport.getElement().withPrefix(prefix));
paddings.add(paddedImport);
first = false;

}
final List<JRightPadded<J.Import>> newImports = new ArrayList<>(existingImports);
newImports.addAll(paddings);
return newImports;
}
}

Loading
Loading