Skip to content
This repository was archived by the owner on May 14, 2025. It is now read-only.

Refactor implementation and design smells #221

Open
wants to merge 7 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
21 changes: 2 additions & 19 deletions src/main/java/org/springframework/cli/command/RoleCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,15 @@
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cli.SpringCliException;
import org.springframework.cli.roles.RoleService;
import org.springframework.cli.util.TableUtils;
import org.springframework.cli.util.TerminalMessage;
import org.springframework.shell.command.annotation.Command;
import org.springframework.shell.command.annotation.Option;
import org.springframework.shell.table.ArrayTableModel;
import org.springframework.shell.table.BorderStyle;
import org.springframework.shell.table.Table;
import org.springframework.shell.table.TableBuilder;
import org.springframework.shell.table.TableModel;
import org.springframework.util.StringUtils;

@Command(command = "role", group = "Role")
Expand Down Expand Up @@ -129,19 +124,7 @@ public void roleGet(@Option(description = "Property key", required = true) Strin
public Table roleList() {
File directory = this.roleService.getRolesVarPath();
List<String> rolesNames = this.roleService.getRoleNames(directory);
Stream<String[]> header = Stream.<String[]>of(new String[] { "Name" });
Stream<String[]> rows;
if (rolesNames != null) {
rows = rolesNames.stream().map(tr -> new String[] { tr });
}
else {
rows = Stream.empty();
}
List<String[]> allRows = rows.collect(Collectors.toList());
String[][] data = Stream.concat(header, allRows.stream()).toArray(String[][]::new);
TableModel model = new ArrayTableModel(data);
TableBuilder tableBuilder = new TableBuilder(model);
return tableBuilder.addFullBorder(BorderStyle.fancy_light).build();
return TableUtils.buildTable(rolesNames, "Name");
}

public RoleService getRoleService() {
Expand Down
54 changes: 27 additions & 27 deletions src/main/java/org/springframework/cli/merger/ProjectMerger.java
Original file line number Diff line number Diff line change
Expand Up @@ -172,34 +172,27 @@ public void merge() {
}

private void mergeSpringBootApplicationClassAnnotations() throws IOException {

logger.debug("Looking for @SpringBootApplication in directory " + this.toMergeProjectPath.toFile());
Optional<File> springBootApplicationFile = RootPackageFinder
.findSpringBootApplicationFile(this.toMergeProjectPath.toFile());

if (springBootApplicationFile.isPresent()) {
CollectAnnotationAndImportInformationRecipe collectAnnotationAndImportInformationRecipe = new CollectAnnotationAndImportInformationRecipe();
Consumer<Throwable> onError = e -> {
logger.error("error in javaParser execution", e);
};
Consumer<Throwable> onError = e -> logger.error("error in javaParser execution", e);
List<SourceFile> mergeCompilationUnits = parseJavaFile(springBootApplicationFile.get().toPath(), onError);

CollectAnnotationAndImportInformationRecipe annotationImportRecipe = new CollectAnnotationAndImportInformationRecipe();
InMemoryExecutionContext executionContext = new InMemoryExecutionContext(onError);
List<Path> paths = new ArrayList<>();
paths.add(springBootApplicationFile.get().toPath());
JavaParser javaParser = new Java17Parser.Builder().build();
List<SourceFile> compilationUnits = javaParser.parse(paths, null, executionContext).toList();
collectAnnotationAndImportInformationRecipe.run(new InMemoryLargeSourceSet(compilationUnits),
executionContext);
runRecipeOnFile(annotationImportRecipe, mergeCompilationUnits, executionContext);

List<Annotation> declaredAnnotations = collectAnnotationAndImportInformationRecipe.getDeclaredAnnotations();
List<String> declaredImports = collectAnnotationAndImportInformationRecipe.getDeclaredImports();
List<Annotation> declaredAnnotations = annotationImportRecipe.getDeclaredAnnotations();
List<String> declaredImports = annotationImportRecipe.getDeclaredImports();

Map<String, String> annotationImportMap = new HashMap<>();
for (Annotation declaredAnnotation : declaredAnnotations) {
if (declaredAnnotation.toString().startsWith("@SpringBootApplication")) {
continue;
}
for (String declaredImport : declaredImports) {
// get the import statement that matches the annotation
if (declaredImport.contains(declaredAnnotation.getSimpleName())) {
annotationImportMap.put(declaredAnnotation.toString(), declaredImport);
}
Expand All @@ -210,20 +203,18 @@ private void mergeSpringBootApplicationClassAnnotations() throws IOException {
Optional<File> currentSpringBootApplicationFile = RootPackageFinder
.findSpringBootApplicationFile(this.currentProjectPath.toFile());
if (currentSpringBootApplicationFile.isPresent()) {
List<SourceFile> currentCompilationUnits = parseJavaFile(
currentSpringBootApplicationFile.get().toPath(), onError);
executionContext = new InMemoryExecutionContext(onError);
paths = new ArrayList<>();
paths.add(currentSpringBootApplicationFile.get().toPath());
javaParser = new Java17Parser.Builder().build();
compilationUnits = javaParser.parse(paths, null, executionContext).toList();

for (Entry<String, String> annotationImportEntry : annotationImportMap.entrySet()) {
String annotation = annotationImportEntry.getKey();
String importStatement = annotationImportEntry.getValue();

AddImport addImport = new AddImport(importStatement, null, false);
AddImportRecipe addImportRecipe = new AddImportRecipe(addImport);
List<Result> results = addImportRecipe
.run(new InMemoryLargeSourceSet(compilationUnits), executionContext)
.getChangeset()
.getAllResults();
List<Result> results = runRecipeOnFile(addImportRecipe, currentCompilationUnits, executionContext);

updateSpringApplicationClass(currentSpringBootApplicationFile.get().toPath(), results);

AttributedStringBuilder sb = new AttributedStringBuilder();
Expand All @@ -232,15 +223,24 @@ private void mergeSpringBootApplicationClassAnnotations() throws IOException {
terminalMessage.print(sb.toAttributedString());

injectAnnotation(currentSpringBootApplicationFile.get().toPath(), annotation);
// AddAnnotationToClassRecipe addAnnotationToClassRecipe = new
// AddAnnotationToClassRecipe(annotation);
// results = addAnnotationToClassRecipe.run(compilationUnits);
// updateSpringApplicationClass(currentSpringBootApplicationFile.get().toPath(),
// results);
}
}
}
}

private List<SourceFile> parseJavaFile(Path filePath, Consumer<Throwable> onError) {
InMemoryExecutionContext executionContext = new InMemoryExecutionContext(onError);
List<Path> paths = new ArrayList<>();
paths.add(filePath);
JavaParser javaParser = new Java17Parser.Builder().build();
return javaParser.parse(paths, null, executionContext).toList();
}

private List<Result> runRecipeOnFile(Recipe recipe, List<SourceFile> compilationUnits,
InMemoryExecutionContext executionContext) {
return recipe.run(new InMemoryLargeSourceSet(compilationUnits), executionContext)
.getChangeset()
.getAllResults();
}

private void injectAnnotation(Path pathToFile, String annotation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ public void add(String description, String path, boolean preview, boolean rewrit
TerminalMessage terminalMessage) {
Path projectPath = getProjectPath(path);

ProjectNameHeuristicAiService projectNameHeuristic = new ProjectNameHeuristicAiService(terminalMessage);
ProjectNameHeuristicAiService projectNameHeuristic = new ProjectNameHeuristicAiService();
logger.debug("Deriving main Spring project required...");
ProjectName projectName = projectNameHeuristic.deriveProjectName(description);
logger.debug("Done. The code will primarily use " + projectName.getSpringProjectName());

if (rewrite) {
DescriptionRewriteAiService descriptionRewriteAiService = new DescriptionRewriteAiService(terminalMessage);
DescriptionRewriteAiService descriptionRewriteAiService = new DescriptionRewriteAiService();
description = descriptionRewriteAiService.rewrite(description);
}
terminalMessage.print("");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ private ProcessArtifactResult<Void> processArtifacts(List<ProjectArtifact> proje
ProjectArtifactType artifactType = projectArtifact.getArtifactType();
switch (artifactType) {
case SOURCE_CODE:
writeSourceCode(projectArtifact, projectPath);
writeCode(projectArtifact, projectPath, false);
break;
case TEST_CODE:
writeTestCode(projectArtifact, projectPath);
writeCode(projectArtifact, projectPath, true);
break;
case MAVEN_DEPENDENCIES:
writeMavenDependencies(projectArtifact, projectPath, terminalMessage);
Expand All @@ -110,29 +110,24 @@ private ProcessArtifactResult<Void> processArtifacts(List<ProjectArtifact> proje
return processArtifactResult;
}

private void writeSourceCode(ProjectArtifact projectArtifact, Path projectPath) throws IOException {
private void writeCode(ProjectArtifact projectArtifact, Path projectPath, boolean isTest) throws IOException {
String packageName = this.calculatePackageForArtifact(projectArtifact);
ClassNameExtractor classNameExtractor = new ClassNameExtractor();
Optional<String> className = classNameExtractor.extractClassName(projectArtifact.getText());
if (className.isPresent()) {
Path output = createSourceFile(projectPath, packageName, className.get() + ".java");
Path output = createCodeFile(projectPath, packageName, className.get() + ".java", isTest);
try (Writer writer = new BufferedWriter(new FileWriter(output.toFile()))) {
writer.write(projectArtifact.getText());
}
}
}

private void writeTestCode(ProjectArtifact projectArtifact, Path projectPath) throws IOException {
// TODO parameterize better to reduce code duplication
String packageName = this.calculatePackageForArtifact(projectArtifact);
ClassNameExtractor classNameExtractor = new ClassNameExtractor();
Optional<String> className = classNameExtractor.extractClassName(projectArtifact.getText());
if (className.isPresent()) {
Path output = createTestFile(projectPath, packageName, className.get() + ".java");
try (Writer writer = new BufferedWriter(new FileWriter(output.toFile()))) {
writer.write(projectArtifact.getText());
}
}
private Path createCodeFile(Path projectPath, String packageName, String fileName, boolean isTest)
throws IOException {
Path sourceFile = isTest ? resolveTestFile(projectPath, packageName, fileName)
: resolveSourceFile(projectPath, packageName, fileName);
createFile(sourceFile);
return sourceFile;
}

private void writeMavenDependencies(ProjectArtifact projectArtifact, Path projectPath,
Expand Down Expand Up @@ -276,18 +271,6 @@ private static String extractFilenameFromComment(String content) {
return null;
}

private Path createSourceFile(Path projectPath, String packageName, String fileName) throws IOException {
Path sourceFile = resolveSourceFile(projectPath, packageName, fileName);
createFile(sourceFile);
return sourceFile;
}

private Path createTestFile(Path projectPath, String packageName, String fileName) throws IOException {
Path sourceFile = resolveTestFile(projectPath, packageName, fileName);
createFile(sourceFile);
return sourceFile;
}

public Path resolveSourceFile(Path projectPath, String packageName, String fileName) {
Path sourceDirectory = projectPath.resolve("src").resolve("main").resolve("java");
return resolvePackage(sourceDirectory, packageName).resolve(fileName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
import org.springframework.cli.merger.ai.PromptRequest;
import org.springframework.cli.runtime.engine.templating.HandlebarsTemplateEngine;
import org.springframework.cli.util.PropertyFileUtils;
import org.springframework.cli.util.TerminalMessage;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StreamUtils;

Expand All @@ -45,12 +44,6 @@ public abstract class AbstractOpenAiService implements org.springframework.cli.m

private com.theokanning.openai.service.OpenAiService openAiService;

private final TerminalMessage terminalMessage;

public AbstractOpenAiService(TerminalMessage terminalMessage) {
this.terminalMessage = terminalMessage;
}

protected ChatCompletionRequest getChatCompletionRequest(PromptRequest promptRequest) {
createOpenAiService();
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
Expand Down Expand Up @@ -79,10 +72,6 @@ public HandlebarsTemplateEngine getHandlebarsTemplateEngine() {
return handlebarsTemplateEngine;
}

public TerminalMessage getTerminalMessage() {
return terminalMessage;
}

protected Map<String, String> getContext(String description) {
Map<String, String> context = new HashMap<>();
context.put("description", description);
Expand All @@ -97,12 +86,11 @@ protected String getPrompt(Map<String, String> context, String promptType) {
return getHandlebarsTemplateEngine().process(promptRaw, context);
}
catch (FileNotFoundException ex) {
throw new SpringCliException("Resource file note found:" + resourceFileName);
throw new SpringCliException("Resource file not found:" + resourceFileName);
}
catch (IOException ex) {
throw new SpringCliException("Could read file " + resourceFileName, ex);
throw new SpringCliException("Could not read file " + resourceFileName, ex);
}

}

protected PromptRequest createPromptRequest(Map<String, String> context, String promptFamilyName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,9 @@
import com.theokanning.openai.completion.chat.ChatCompletionRequest;

import org.springframework.cli.merger.ai.PromptRequest;
import org.springframework.cli.util.TerminalMessage;

public class DescriptionRewriteAiService extends AbstractOpenAiService {

public DescriptionRewriteAiService(TerminalMessage terminalMessage) {
super(terminalMessage);
}

public String rewrite(String description) {
Map<String, String> context = getContext(description);
return generate(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@

public class GenerateCodeAiService extends AbstractOpenAiService {

private final TerminalMessage terminalMessage;

public GenerateCodeAiService(TerminalMessage terminalMessage) {
super(terminalMessage);
this.terminalMessage = terminalMessage;
}

public String generate(Map<String, String> context) {
PromptRequest promptRequest = createPromptRequest(context, "ai-add");
String completionEstimate = getCompletionEstimate();
getTerminalMessage()
terminalMessage
.print("Generating code. This will take a few minutes ..." + " Check back around " + completionEstimate);
ChatCompletionRequest chatCompletionRequest = getChatCompletionRequest(promptRequest);
String response = getResponse(chatCompletionRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,10 @@
import org.springframework.cli.SpringCliException;
import org.springframework.cli.merger.ai.ProjectName;
import org.springframework.cli.merger.ai.PromptRequest;
import org.springframework.cli.util.TerminalMessage;
import org.springframework.util.StringUtils;

public class ProjectNameHeuristicAiService extends AbstractOpenAiService {

public ProjectNameHeuristicAiService(TerminalMessage terminalMessage) {
super(terminalMessage);
}

public ProjectName deriveProjectName(String description) {
Map<String, String> context = getContext(description);
String response = generate(context);
Expand Down Expand Up @@ -67,7 +62,6 @@ private ProjectName createProjectName(String response) {
shortName = "ai";
}
return new ProjectName(shortName.toLowerCase(), springProjectName);

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand All @@ -30,6 +31,7 @@
import org.slf4j.LoggerFactory;

import org.springframework.cli.SpringCliException;
import org.springframework.cli.util.IoUtils;

/**
* Scans a directory, by default .spring/commands in the current working directory, and
Expand All @@ -42,7 +44,7 @@
*/
public class CommandScanner {

private final Logger logger = LoggerFactory.getLogger(CommandScanner.class);
private static final Logger logger = LoggerFactory.getLogger(CommandScanner.class);

private Path pathToScan;

Expand Down Expand Up @@ -122,4 +124,12 @@ private Command getCommandObject(File directory) {
return command;
}

public static CommandScanResults scanCommands() {
Path cwd = IoUtils.getWorkingDirectory().toAbsolutePath();
Path pathToUse = Paths.get(cwd.toString(), ".spring", "commands");
logger.debug("Looking for user-defined commands in directory " + pathToUse);
CommandScanner scanner = new CommandScanner(pathToUse);
return scanner.scan();
}

}
Loading