diff --git a/pom.xml b/pom.xml
index f31487437f..e5101ee219 100644
--- a/pom.xml
+++ b/pom.xml
@@ -596,6 +596,7 @@
tycho-repository-plugin
tycho-eclipse-plugin
tycho-wrap-plugin
+ tycho-cleancode-plugin
diff --git a/tycho-cleancode-plugin/.settings/org.eclipse.jdt.core.prefs b/tycho-cleancode-plugin/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000000..eeac0e762f
--- /dev/null
+++ b/tycho-cleancode-plugin/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=17
diff --git a/tycho-cleancode-plugin/pom.xml b/tycho-cleancode-plugin/pom.xml
new file mode 100644
index 0000000000..74848cd3c8
--- /dev/null
+++ b/tycho-cleancode-plugin/pom.xml
@@ -0,0 +1,64 @@
+
+ 4.0.0
+
+ org.eclipse.tycho
+ tycho
+ 5.0.0-SNAPSHOT
+
+ tycho-cleancode-plugin
+ Tycho Eclipse Plugin
+ maven-plugin
+
+ ${minimal-maven-version}
+
+ Maven Plugins for performing automatic code clean options
+
+
+ org.apache.maven
+ maven-core
+
+
+ org.apache.maven
+ maven-plugin-api
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+
+
+ org.eclipse.tycho
+ tycho-eclipse-plugin
+ ${project.version}
+
+
+ org.eclipse.jdt
+ org.eclipse.jdt.ui
+ 3.33.200
+
+
+ org.eclipse.jdt
+ org.eclipse.jdt.core.manipulation
+ 1.21.300
+
+
+
+
+
+
+ org.codehaus.plexus
+ plexus-component-metadata
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+
+
+ tycho-cleancode
+
+
+
+
+
\ No newline at end of file
diff --git a/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUp.java b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUp.java
new file mode 100644
index 0000000000..6c85f26204
--- /dev/null
+++ b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUp.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.cleancode;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ProjectScope;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.internal.corext.fix.CleanUpPreferenceUtil;
+import org.eclipse.jdt.internal.corext.fix.CleanUpRefactoring;
+import org.eclipse.jdt.internal.ui.JavaPlugin;
+import org.eclipse.jdt.internal.ui.fix.MapCleanUpOptions;
+import org.eclipse.jdt.ui.cleanup.CleanUpOptions;
+import org.eclipse.jdt.ui.cleanup.ICleanUp;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.PerformChangeOperation;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.tycho.eclipsebuild.AbstractEclipseBuild;
+
+public class CleanUp extends AbstractEclipseBuild {
+
+ private Map customProfile;
+
+ CleanUp(Path projectDir, boolean debug, Map customProfile) {
+ super(projectDir, debug);
+ this.customProfile = customProfile;
+ }
+
+ @Override
+ protected CleanupResult createResult(IProject project) throws Exception {
+ CleanupResult result = new CleanupResult();
+ CleanUpOptions options = getOptions(project);
+ ICleanUp[] cleanups = getCleanups(result, options);
+ if (cleanups.length > 0) {
+ List units = getCompilationUnits(project);
+ final CleanUpRefactoring refactoring = new CleanUpRefactoring(project.getName());
+ for (ICompilationUnit cu : units) {
+ refactoring.addCompilationUnit(cu);
+ }
+ refactoring.setUseOptionsFromProfile(false);
+ for (ICleanUp cleanUp : cleanups) {
+ refactoring.addCleanUp(cleanUp);
+ }
+ final RefactoringStatus status = refactoring.checkAllConditions(this);
+ if (status.isOK()) {
+ Change change = refactoring.createChange(this);
+ change.initializeValidationData(this);
+ PerformChangeOperation performChangeOperation = new PerformChangeOperation(change);
+ performChangeOperation.run(this);
+ } else if (status.hasError()) {
+ throw new RuntimeException("Refactoring failed: " + status);
+ }
+ }
+ return result;
+ }
+
+ private List getCompilationUnits(IProject project) throws JavaModelException {
+ IJavaProject javaProject = JavaCore.create(project);
+ List units = new ArrayList();
+ IPackageFragmentRoot[] packages = javaProject.getPackageFragmentRoots();
+ for (IPackageFragmentRoot root : packages) {
+ if (root.getKind() == IPackageFragmentRoot.K_SOURCE) {
+ for (IJavaElement javaElement : root.getChildren()) {
+ if (javaElement.getElementType() == IJavaElement.PACKAGE_FRAGMENT) {
+ IPackageFragment pf = (IPackageFragment) javaElement;
+ ICompilationUnit[] compilationUnits = pf.getCompilationUnits();
+ for (ICompilationUnit compilationUnit : compilationUnits) {
+ units.add(compilationUnit);
+ }
+ }
+ }
+ }
+ }
+ return units;
+ }
+
+ private ICleanUp[] getCleanups(CleanupResult result, CleanUpOptions options) {
+ ICleanUp[] cleanUps = JavaPlugin.getDefault().getCleanUpRegistry().createCleanUps();
+ for (ICleanUp cleanUp : cleanUps) {
+ try {
+ cleanUp.setOptions(options);
+ String[] descriptions = cleanUp.getStepDescriptions();
+ if (descriptions != null) {
+ for (String description : descriptions) {
+ result.addCleanup(description);
+ }
+ }
+ } catch (Exception e) {
+ debug("Ignore cleanup '" + cleanUp + "' because of initialization error.", e);
+ }
+ }
+ return cleanUps;
+ }
+
+ private CleanUpOptions getOptions(IProject project) {
+ Map options;
+ if (customProfile == null || customProfile.isEmpty()) {
+ options = CleanUpPreferenceUtil.loadOptions(new ProjectScope(project));
+ } else {
+ options = customProfile;
+ }
+ debug("Cleanup Profile: " + options.entrySet().stream().map(e -> e.getKey() + " = " + e.getValue())
+ .collect(Collectors.joining(System.lineSeparator())));
+ return new MapCleanUpOptions(options);
+ }
+
+}
diff --git a/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUpMojo.java b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUpMojo.java
new file mode 100644
index 0000000000..6b26d59ca0
--- /dev/null
+++ b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUpMojo.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.cleancode;
+
+import java.util.Map;
+
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.eclipse.tycho.eclipsebuild.AbstractEclipseBuildMojo;
+
+/**
+ * This mojo allows to perform eclipse cleanup action
+ */
+@Mojo(name = "cleanup", defaultPhase = LifecyclePhase.PROCESS_SOURCES, threadSafe = true, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
+public class CleanUpMojo extends AbstractEclipseBuildMojo {
+
+ /**
+ * Defines key value pairs of a cleanup profile, if not defined will use the
+ * project defaults
+ */
+ @Parameter
+ private Map cleanUpProfile;
+
+ @Override
+ protected String[] getRequireBundles() {
+ return new String[] { "org.eclipse.jdt.ui" };
+ }
+
+ @Override
+ protected String getName() {
+ return "Perform Cleanup";
+ }
+
+ @Override
+ protected CleanUp createExecutable() {
+ return new CleanUp(project.getBasedir().toPath(), debug, cleanUpProfile);
+ }
+
+ @Override
+ protected void handleResult(CleanupResult result)
+ throws MojoFailureException {
+ Log log = getLog();
+ result.cleanups().forEach(cleanup -> {
+ log.info("Performed cleanup " + cleanup);
+ });
+ }
+
+}
diff --git a/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanupResult.java b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanupResult.java
new file mode 100644
index 0000000000..2982ca4edd
--- /dev/null
+++ b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanupResult.java
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Christoph Läubrich and others.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.tycho.cleancode;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.eclipse.tycho.eclipsebuild.EclipseBuildResult;
+
+public class CleanupResult extends EclipseBuildResult {
+
+ private List cleanups = new ArrayList<>();
+
+ public void addCleanup(String cleanup) {
+ cleanups.add(cleanup);
+ }
+
+ public Stream cleanups() {
+ return this.cleanups.stream();
+ }
+
+}
diff --git a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java
index 199607b938..46c449db14 100644
--- a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java
+++ b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java
@@ -1,7 +1,9 @@
package org.eclipse.tycho.eclipsebuild;
import java.io.IOException;
+import java.io.PrintWriter;
import java.io.Serializable;
+import java.io.StringWriter;
import java.nio.file.Path;
import java.util.concurrent.Callable;
@@ -83,6 +85,14 @@ protected void debug(String string) {
}
}
+ protected void debug(String string, Throwable t) {
+ if (debug) {
+ StringWriter writer = new StringWriter();
+ t.printStackTrace(new PrintWriter(writer));
+ debug(string + System.lineSeparator() + writer);
+ }
+ }
+
static String pathAsString(Path path) {
if (path != null) {
return path.toAbsolutePath().toString();