Как я могу программно применить действие «организовать импорт» в пакетном приложении Eclipse?JAVA

Программисты JAVA общаются здесь
Ответить
Anonymous
 Как я могу программно применить действие «организовать импорт» в пакетном приложении Eclipse?

Сообщение Anonymous »

Я создаю пакетное приложение Eclipse, которое применяет очистку исходного кода (эквивалент контекстного меню «Источник» -> «Очистить») из файла профиля. Это XML-файл, в котором перечислены правила и указано, активированы они или нет. Его можно экспортировать из окна -> Настройки, а затем из Java -> Стиль кода -> Очистить.
Кажется, он дает хорошие результаты, но одна из очисток, «Организовать импорт», завершается с ошибкой.
Его можно включить и выключить в этой строке файла профиля:


Дерево проекта:
C:\USERS\xxxxx\GIT\REFACTORING-CLI
│ .classpath
│ .gitignore
│ .project
│ build.properties
│ LICENSE
│ plugin.xml
│ pom.xml

├───META-INF
│ MANIFEST.MF

└───src
└───main
└───java
└───io
└───github
└───xxxxxxxxx
└───refactoring
└───cli
Activator.java
CleanupRunner.java
HeadlessCleanupApp.java

Вот корневой файл pom.xml:



4.0.0

io.github.xxxxxxxxx
io.github.xxxxxxxxx.refactoring.cli
1.0.0
eclipse-plugin

5.0.0
https://download.eclipse.org/releases/2025-09
21
21
UTF-8




eclipse-release
${eclipse.release.repo}
p2






org.eclipse.tycho
tycho-maven-plugin
${tycho.version}





org.eclipse.tycho
tycho-p2-director-plugin
${tycho.version}






Вот файл плагина.xml:









Вот файл build.properties: `
bin.includes = META-INF/,\
.,\
plugin.xml
source.. = src/
output.. = bin/

Вот файл META-INF/MANIFEST.MF:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Refactoring CLI
Bundle-SymbolicName: io.github.xxxxxxxxx.refactoring.cli;singleton:=true
Bundle-Version: 1.0.0
Bundle-Activator: io.github.xxxxxxxxx.refactoring.cli.Activator
Bundle-RequiredExecutionEnvironment: JavaSE-21
Automatic-Module-Name: io.github.xxxxxxxxx.refactoring.cli
Bundle-ActivationPolicy: lazy
Require-Bundle:
org.eclipse.core.runtime,
org.eclipse.core.resources,
org.eclipse.jdt.core,
org.eclipse.jdt.ui,
org.eclipse.jdt.core.manipulation,
org.eclipse.ltk.core.refactoring,
org.eclipse.text,
org.eclipse.jface,
org.eclipse.equinox.app

Вот точка входа приложения HeadlessCleanupApp:
package io.github.xxxxxxxxx.refactoring.cli;

import org.eclipse.equinox.app.*;
import java.nio.file.*;
import java.util.*;

public class HeadlessCleanupApp implements IApplication {

@Override
public Object start(IApplicationContext context) throws Exception {
String[] args = (String[]) context.getArguments().get(IApplicationContext.APPLICATION_ARGS);

if (args == null || args.length == 0) {
System.err.println("Missing arguments. Usage:");
System.err.println(" --source --profile [--classpath ]");
return Integer.valueOf(1);
}

String sourceLevel = null;
String profilePath = null;
String projectRootPath = null;
List extraClasspath = new ArrayList();

int i = 0;
while (i < args.length) {
String arg = args;
if ("--source".equals(arg) && i + 1 < args.length) {
sourceLevel = args[++i];
} else if ("--profile".equals(arg) && i + 1 < args.length) {
profilePath = args[++i];
} else if ("--classpath".equals(arg) && i + 1 < args.length) {
String[] cpEntries = args[++i].split(System.getProperty("path.separator"));
for (String entry : cpEntries) {
extraClasspath.add(entry);
}
} else {
projectRootPath = arg;
}
i++;
}

if (sourceLevel == null || profilePath == null || projectRootPath == null) {
System.err.println("Missing required parameters.");
return Integer.valueOf(1);
}

Path projectRoot = Paths.get(projectRootPath);
Path profileFile = Paths.get(profilePath);

CleanupRunner runner =
new CleanupRunner(projectRoot, profileFile, sourceLevel, extraClasspath);

runner.run();

return Integer.valueOf(0);
}

@Override
public void stop() {
// no-op
}
}

Классом, применяющим очистку, является CleanupRunner следующим образом:
package io.github.xxxxxxxxx.refactoring.cli;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;

import javax.xml.parsers.*;

import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.ProgressMonitorWrapper;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.dom.*;
import org.eclipse.jdt.internal.corext.fix.*;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.fix.MapCleanUpOptions;
import org.eclipse.jdt.ui.cleanup.ICleanUp;
import org.eclipse.ltk.core.refactoring.*;
import org.eclipse.text.edits.TextEdit;
import org.w3c.dom.*;

public class CleanupRunner {

private final Path projectRoot;
private final Path profileFile;
private final String sourceLevel;
private final List extraClasspath;

public CleanupRunner(Path projectRoot, Path profileFile, String sourceLevel, List extraClasspath) {
this.projectRoot = projectRoot;
this.profileFile = profileFile;
this.sourceLevel = sourceLevel;
this.extraClasspath = extraClasspath;
}

public List run() throws Exception {

LoggingMonitor monitor = new LoggingMonitor();

System.out.println("=== Starting cleanup ===");
System.out.println("Project root: " + projectRoot);
System.out.println("Profile file: " + profileFile);
System.out.println("Source level: " + sourceLevel);

IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot wsRoot = workspace.getRoot();

System.out.println("Creating temporary workspace project...");
IProject project = wsRoot.getProject("refactoring-cli-project");
if (!project.exists()) {
project.create(monitor);
}
project.open(monitor);

addJavaNature(project);
System.out.println("Java nature enabled.");

System.out.println("Detecting source folders...");
Set sourceFolders = detectSourceFolders(projectRoot);
System.out.println("Detected " + sourceFolders.size() + " source folders.");

System.out.println("Linking source folders...");
Map linkedFolders = linkSourceFolders(project, sourceFolders);

project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
System.out.println("Workspace refreshed.");

IJavaProject javaProject = JavaCore.create(project);

setEncoding(project);
System.out.println("Encoding set to UTF-8.");

System.out.println("Configuring classpath...");
configureClasspath(javaProject, linkedFolders);

System.out.println("Configuring compiler options...");
configureCompilerOptions(javaProject);

System.out.println("Collecting compilation units...");
List units = collectCompilationUnits(javaProject);
System.out.println("Found " + units.size() + " compilation units.");

if (units.isEmpty()) {
System.out.println("Nothing to clean.");
return new ArrayList();
}

System.out.println("Loading cleanup settings...");
Map cleanupSettings = loadCleanupSettingsFromProfile(profileFile);

System.out.println("Loading available cleanups...");
CleanUpRegistry registry = JavaPlugin.getDefault().getCleanUpRegistry();
ICleanUp[] cleanUps = registry.createCleanUps(null);
System.out.println("Loaded " + cleanUps.length + " cleanup modules.");

MapCleanUpOptions options = new MapCleanUpOptions(cleanupSettings);

for (ICleanUp cleanUp : cleanUps) {
cleanUp.setOptions(options);
}

System.out.println("Preparing refactoring...");
CleanUpRefactoring refactoring = new CleanUpRefactoring();

for (ICompilationUnit unit : units) {
refactoring.addCompilationUnit(unit);
}

for (ICleanUp cleanUp : cleanUps) {
refactoring.addCleanUp(cleanUp);
}

System.out.println("Checking initial conditions...");
RefactoringStatus initStatus = refactoring.checkInitialConditions(monitor);
System.out.println("Initial condition status: " + initStatus);

if (initStatus.hasFatalError()) {
System.err.println("Fatal error during initial conditions.");
return new ArrayList();
}

System.out.println("Checking final conditions...");
RefactoringStatus finalStatus = refactoring.checkFinalConditions(monitor);
System.out.println("Final condition status: " + finalStatus);

if (finalStatus.hasFatalError()) {
System.err.println("Fatal error during final conditions.");
return new ArrayList();
}

System.out.println("Creating change...");
Change change = refactoring.createChange(monitor);
if (change == null) {
System.out.println("No changes generated.");
return new ArrayList();
}

System.out.println("Collecting changed files...");
List changed = collectChangedFiles(change);
System.out.println("Pending changes: " + changed.size());

for (Path p : changed) {
System.out.println("Will modify: " + p);
}

System.out.println("Initializing change...");
change.initializeValidationData(monitor);

System.out.println("Validating change...");
RefactoringStatus status = change.isValid(monitor);
System.out.println("Validation result: " + status);

if (status.hasFatalError()) {
System.err.println("Change validation failed.");
return changed;
}

System.out.println("Applying change...");
change.perform(monitor);

System.out.println("Saving workspace...");
ResourcesPlugin.getWorkspace().save(true, monitor);

System.out.println("=== Cleanup complete ===");
System.out.println("Modified " + changed.size() + " files.");

return changed;
}

private void addJavaNature(IProject project) throws CoreException {
IProjectDescription desc = project.getDescription();
String[] natures = desc.getNatureIds();
boolean hasJavaNature = false;
for (String n : natures) {
if (JavaCore.NATURE_ID.equals(n)) {
hasJavaNature = true;
break;
}
}
if (!hasJavaNature) {
String[] newNatures = new String[natures.length + 1];
System.arraycopy(natures, 0, newNatures, 0, natures.length);
newNatures[natures.length] = JavaCore.NATURE_ID;
desc.setNatureIds(newNatures);
project.setDescription(desc, null);
}
}

private Set detectSourceFolders(Path root) throws IOException {
Set sourceFolders = new LinkedHashSet();

Files.walkFileTree(root, new FileVisitor() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (!file.toString().endsWith(".java")) {
return FileVisitResult.CONTINUE;
}

Path parent = file.getParent();
if (parent == null) {
return FileVisitResult.CONTINUE;
}

for (Path src : sourceFolders) {
if (parent.startsWith(src)) {
return FileVisitResult.CONTINUE;
}
}

String packageName = readPackageName(file);
Path sourceFolder = inferSourceFolder(file, packageName);

if (sourceFolder != null && Files.isDirectory(sourceFolder)) {
sourceFolders.add(sourceFolder.normalize());
}

return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
throw new UncheckedIOException(exc);
}

@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
if (exc != null) {
throw new UncheckedIOException(exc);
}
return FileVisitResult.CONTINUE;
}
});

return sourceFolders;
}

private String readPackageName(Path file) throws IOException {
char[] source = Files.readString(file, StandardCharsets.UTF_8).toCharArray();
ASTParser parser = ASTParser.newParser(AST.getJLSLatest());
parser.setSource(source);
parser.setKind(ASTParser.K_COMPILATION_UNIT);

CompilationUnit unit = (CompilationUnit) parser.createAST(null);

if (unit.getPackage() != null) {
return unit.getPackage().getName().getFullyQualifiedName();
}

return "";
}

private Path inferSourceFolder(Path file, String packageName) {
Path parent = file.getParent();
if (parent == null) {
return null;
}

if (packageName == null || packageName.isEmpty()) {
return parent;
}

String[] segments = packageName.split("\\.");
Path pkgPath = parent;

for (int i = segments.length - 1; i >= 0; i--) {
if (pkgPath == null || pkgPath.getFileName() == null || !pkgPath.getFileName().toString().equals(segments)) {
break;
}
pkgPath = pkgPath.getParent();
}

return pkgPath;
}

private Map linkSourceFolders(IProject project, Set folders) throws CoreException {
LinkedHashMap links = new LinkedHashMap();
int index = 0;
for (Path folder : folders) {
String linkName = "src_" + index;
index = index + 1;
IFolder linked = project.getFolder(linkName);
if (!linked.exists()) {
linked.createLink(new org.eclipse.core.runtime.Path(folder.toString()), IResource.REPLACE, null);
}
links.put(new org.eclipse.core.runtime.Path(folder.toString()), linked);
}
return links;
}

private void configureClasspath(IJavaProject javaProject, Map linkedFolders) throws CoreException {
List entries = new ArrayList();

for (IFolder folder : linkedFolders.values()) {
entries.add(JavaCore.newSourceEntry(folder.getFullPath()));
}

entries.add(JavaCore.newContainerEntry(
new org.eclipse.core.runtime.Path("org.eclipse.jdt.launching.JRE_CONTAINER")));

for (String cp : extraClasspath) {
org.eclipse.core.runtime.Path path = new org.eclipse.core.runtime.Path(cp);
entries.add(JavaCore.newLibraryEntry(path, null, null));
}

javaProject.setRawClasspath(entries.toArray(new IClasspathEntry[0]), null);
}

private void configureCompilerOptions(IJavaProject javaProject) {
Map options = javaProject.getOptions(false);
options.put(JavaCore.COMPILER_SOURCE, sourceLevel);
options.put(JavaCore.COMPILER_COMPLIANCE, sourceLevel);
options.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, sourceLevel);
javaProject.setOptions(options);
}

private void setEncoding(IProject project) throws CoreException {
project.setDefaultCharset(StandardCharsets.UTF_8.name(), null);
}

private List collectCompilationUnits(IJavaProject javaProject) throws CoreException {
List result = new ArrayList();
IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();
for (IPackageFragmentRoot root : roots) {
if (root.getKind() == IPackageFragmentRoot.K_SOURCE) {
IJavaElement[] children = root.getChildren();
for (IJavaElement el : children) {
if (el instanceof IPackageFragment pkg) {
ICompilationUnit[] units = pkg.getCompilationUnits();
Collections.addAll(result, units);
}
}
}
}
return result;
}

private Map loadCleanupSettingsFromProfile(Path profile) throws IOException {
Map settings = new LinkedHashMap();

try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(true);

DocumentBuilder builder = factory.newDocumentBuilder();
try (InputStream in = Files.newInputStream(profile)) {
Document document = builder.parse(in);

NodeList nodes = document.getElementsByTagName("setting");
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (org.w3c.dom.Element) node;

String id = element.getAttribute("id");
String value = element.getAttribute("value");

if (id != null && !id.isEmpty()) {
settings.put(id, value);
}
}
}
}
} catch (Exception e) {
throw new IOException("Failed to parse cleanup profile XML: " + profile, e);
}

return settings;
}

private List collectChangedFiles(Change change) throws CoreException {
List result = new ArrayList();
if (change instanceof CompositeChange composite) {
Change[] children = composite.getChildren();
for (Change child : children) {
result.addAll(collectChangedFiles(child));
}
} else if (change instanceof TextFileChange tfc) {
TextEdit edit = tfc.getEdit();
if (edit != null && edit.hasChildren()) {
IFile file = tfc.getFile();
result.add(Paths.get(file.getLocation().toOSString()));
}
}
return result;
}

private static class LoggingMonitor extends ProgressMonitorWrapper {

public LoggingMonitor() {
super(new NullProgressMonitor());
}

@Override
public void beginTask(String name, int totalWork) {
System.out.println("BEGIN: " + name + " (" + totalWork + ")");
}

@Override
public void worked(int work) {
System.out.println("WORK: " + work);
}

@Override
public void done() {
System.out.println("DONE");
}
}

}

Есть минимальный класс активатора:
package io.github.xxxxxxxxx.refactoring.cli;

import org.eclipse.core.runtime.Plugin;

public class Activator extends Plugin {
}

В Eclipse RCP в графическом интерфейсе MANIFEST я выбираю «Запустить приложение Eclipse в режиме отладки».
Изображение

Альтернативно, после сборки с помощью mvn package и помещая jar в папку dropins Eclipse, я запускаю:
eclipsec -nosplash -clean -data C:\Users\xxxxx\.refactoring-workspace -application io.github.xxxxxxxxx.refactoring.cli.app --source 21 --profile C:\Users\xxxxx\Downloads\source_cleanup_profile.xml C:\Users\xxxxx\git\codebase_to_cleanup

Вот журнал из C:\Users\xxxxx\.refactoring-workspace\.metadata\.log :
!SESSION 2025-11-23 11:01:57.007 -----------------------------------------------
eclipse.buildId=4.37.0.20250905-1455
java.version=21.0.9
java.vendor=Eclipse Adoptium
BootLoader constants: OS=win32, ARCH=x86_64, WS=win32, NL=fr_FR

!ENTRY org.eclipse.osgi 4 0 2025-11-23 11:01:57.264
!MESSAGE The -clean (osgi.clean) option was not successful. Unable to clean the storage area: C:\Users\xxxxx\Downloads\eclipse-rcp-2025-09-R-win32-x86_64\eclipse\configuration\org.eclipse.osgi

!ENTRY ch.qos.logback.classic 1 0 2025-11-23 11:02:04.343
!MESSAGE Activated before the state location was initialized. Retry after the state location is initialized.

!ENTRY org.eclipse.lemminx.uber-jar 4 0 2025-11-23 11:02:04.704
!MESSAGE bundle org.eclipse.lemminx.uber-jar:0.31.0 (312) Component descriptor entry 'OSGI-INF/*.xml' not found

!ENTRY org.eclipse.core.resources 2 10035 2025-11-23 11:02:08.875
!MESSAGE The workspace exited with unsaved changes in the previous session; refreshing workspace to recover changes.

!ENTRY ch.qos.logback.classic 1 0 2025-11-23 11:02:10.164
!MESSAGE Logback config file: C:\Users\xxxxx\.refactoring-workspace\.metadata\.plugins\org.eclipse.m2e.logback\logback.2.7.100.20250418-1315.xml

!ENTRY org.eclipse.osgi 4 0 2025-11-23 11:02:18.608
!MESSAGE Application error
!STACK 1
java.lang.RuntimeException: Error on /refactoring-cli-project/src_0/DecompilerPreferenceAction.java
at org.eclipse.jdt.core.dom.CompilationUnitResolver.resolve(CompilationUnitResolver.java:999)
at org.eclipse.jdt.core.dom.CompilationUnitResolver.resolve(CompilationUnitResolver.java:731)
at org.eclipse.jdt.core.dom.CompilationUnitResolver$ECJCompilationUnitResolver.resolve(CompilationUnitResolver.java:103)
at org.eclipse.jdt.core.dom.ASTParser.createASTs(ASTParser.java:1004)
at org.eclipse.jdt.internal.corext.dom.ASTBatchParser.createASTs(ASTBatchParser.java:83)
at org.eclipse.jdt.internal.corext.fix.CleanUpRefactoring$CleanUpFixpointIterator.next(CleanUpRefactoring.java:410)
at org.eclipse.jdt.internal.corext.fix.CleanUpRefactoring.cleanUpProject(CleanUpRefactoring.java:706)
at org.eclipse.jdt.internal.corext.fix.CleanUpRefactoring.checkFinalConditions(CleanUpRefactoring.java:666)
at io.github.xxxxxxxxx.refactoring.cli.CleanupRunner.run(CleanupRunner.java:174)
at io.github.xxxxxxxxx.refactoring.cli.HeadlessCleanupApp.start(HeadlessCleanupApp.java:58)
at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:219)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:149)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:115)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:467)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:298)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:627)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:575)
at org.eclipse.equinox.launcher.Main.run(Main.java:1431)
Caused by: java.lang.IllegalStateException: Workbench has not been created yet.
at org.eclipse.ui.PlatformUI.getWorkbench(PlatformUI.java:119)
at org.eclipse.jdt.internal.corext.fix.ImportsFix.runUsingProgressService(ImportsFix.java:85)
at org.eclipse.jdt.internal.corext.fix.ImportsFix.createCleanUp(ImportsFix.java:63)
at org.eclipse.jdt.internal.ui.fix.ImportsCleanUp.createFix(ImportsCleanUp.java:62)
at org.eclipse.jdt.internal.corext.fix.CleanUpRefactoring.calculateChange(CleanUpRefactoring.java:796)
at org.eclipse.jdt.internal.corext.fix.CleanUpRefactoring$CleanUpASTRequestor.calculateSolutions(CleanUpRefactoring.java:303)
at org.eclipse.jdt.internal.corext.fix.CleanUpRefactoring$CleanUpASTRequestor.acceptAST(CleanUpRefactoring.java:281)
at org.eclipse.jdt.core.dom.CompilationUnitResolver.resolve(CompilationUnitResolver.java:997)
... 19 more

!ENTRY org.eclipse.core.resources 2 10035 2025-11-23 11:02:19.594
!MESSAGE The workspace will exit with unsaved changes in this session.

Очищается следующий класс (он является частью разветвленного проекта ECD):
package org.sf.feeling.decompiler.actions;

import org.eclipse.jface.action.Action;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.sf.feeling.decompiler.editor.JavaDecompilerClassFileEditor;
import org.sf.feeling.decompiler.i18n.Messages;
import org.sf.feeling.decompiler.util.UIUtil;

public class DecompilerPreferenceAction extends Action {

public DecompilerPreferenceAction() {
super(Messages.getString("JavaDecompilerActionBarContributor.Action.Preferences")); //$NON-NLS-1$
}

@Override
public void run() {
JavaDecompilerClassFileEditor editor = UIUtil.getActiveDecompilerEditor();

String showId = "org.sf.feeling.decompiler.Main"; //$NON-NLS-1$

if (editor != null) {
PreferencesUtil.createPreferenceDialogOn(Display.getDefault().getActiveShell(), showId, // $NON-NLS-1$
editor.collectContextMenuPreferencePages(), null).open();
} else {
PreferencesUtil.createPreferenceDialogOn(Display.getDefault().getActiveShell(), showId, // $NON-NLS-1$
new String[] { showId // $NON-NLS-1$
}, null).open();
}
}

@Override
public boolean isEnabled() {
return true;
}
}


Подробнее здесь: https://stackoverflow.com/questions/798 ... se-batch-a
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «JAVA»