CodeCleanupApplication.java
package org.sandbox.jdt.core.cleanupapp;
/*-
* #%L
* Sandbox cleanup application
* %%
* Copyright (C) 2024 hammer
* %%
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is
* available at https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
* #L%
*/
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.internal.core.util.Util;
import org.eclipse.jdt.internal.corext.fix.CleanUpRefactoring;
import org.eclipse.jdt.internal.ui.JavaPlugin;
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.RefactoringStatus;
public class CodeCleanupApplication implements IApplication {
private static final File[] FILES = new File[0];
private static final String ARG_CONFIG = "-config"; //$NON-NLS-1$
private static final String ARG_HELP = "-help"; //$NON-NLS-1$
private static final String ARG_QUIET = "-quiet"; //$NON-NLS-1$
private static final String ARG_VERBOSE = "-verbose"; //$NON-NLS-1$
private String configName;
private Map<String, String> options = null;
private static final String PDE_LAUNCH = "-pdelaunch"; //$NON-NLS-1$
private boolean quiet = false;
private boolean verbose = false;
private static final int INITIALSIZE = 1;
private static final int DEFAULT_MODE = 0;
private static final int CONFIG_MODE = 1;
/**
* Clean up the given Java source file.
*/
private void cleanFile(final File file) {
try {
// Verbose output
if (this.verbose) {
System.out.println(Messages.bind(Messages.CommandLineCleaning, file.getAbsolutePath()));
}
// Convert file to workspace IFile
IPath filePath = Path.fromOSString(file.getAbsolutePath());
IFile iFile = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(filePath);
if (iFile == null || !iFile.exists()) {
if (!this.quiet) {
System.err.println(Messages.bind(Messages.FileOutsideWorkspace, file.getAbsolutePath()));
}
return;
}
// Get the compilation unit
ICompilationUnit cu = JavaCore.createCompilationUnitFrom(iFile);
if (cu == null) {
if (!this.quiet) {
System.err.println(Messages.bind(Messages.CleanupProblem, file.getAbsolutePath()));
}
return;
}
// Create and configure the cleanup refactoring
CleanUpRefactoring refactoring = new CleanUpRefactoring();
refactoring.addCompilationUnit(cu);
// Get all registered cleanups and configure with options if provided
ICleanUp[] cleanUps = JavaPlugin.getDefault().getCleanUpRegistry().createCleanUps();
if (this.options != null && cleanUps.length > 0) {
// Create CleanUpOptions from the provided options map
CleanUpOptions cleanUpOptions = new CleanUpOptions();
for (Map.Entry<String, String> entry : this.options.entrySet()) {
cleanUpOptions.setOption(entry.getKey(), entry.getValue());
}
// Set options on each cleanup
for (ICleanUp cleanUp : cleanUps) {
cleanUp.setOptions(cleanUpOptions);
refactoring.addCleanUp(cleanUp);
}
} else {
// Use default options from profile
refactoring.setUseOptionsFromProfile(true);
for (ICleanUp cleanUp : cleanUps) {
refactoring.addCleanUp(cleanUp);
}
}
// Check conditions
RefactoringStatus status = refactoring.checkAllConditions(new NullProgressMonitor());
if (status.hasFatalError()) {
if (!this.quiet) {
System.err.println(Messages.bind(Messages.CleanupFatalError, file.getAbsolutePath(), status.getMessageMatchingSeverity(RefactoringStatus.FATAL)));
}
return;
}
// Create and perform the change
Change change = refactoring.createChange(new NullProgressMonitor());
if (change != null) {
change.perform(new NullProgressMonitor());
cu.save(new NullProgressMonitor(), true);
iFile.refreshLocal(1, new NullProgressMonitor());
}
} catch (CoreException e) {
final String errorMessage = Messages.bind(Messages.CaughtException, "CoreException", e.getLocalizedMessage()); //$NON-NLS-1$
Util.log(e, errorMessage);
System.err.println(Messages.bind(Messages.ExceptionSkip, errorMessage));
} catch (Exception e) {
final String errorMessage = Messages.bind(Messages.CaughtException, e.getClass().getSimpleName(), e.getLocalizedMessage());
Util.log(e, errorMessage);
System.err.println(Messages.bind(Messages.ExceptionSkip, errorMessage));
}
}
private File[] processCommandLine(final String[] argsArray) {
int index = 0;
final int argCount = argsArray.length;
int mode = DEFAULT_MODE;
int fileCounter = 0;
File[] filesToCleanup = new File[INITIALSIZE];
loop: while (index < argCount) {
final String currentArg = argsArray[index++];
switch(mode) {
default:
break;
case DEFAULT_MODE :
if (PDE_LAUNCH.equals(currentArg)) {
continue loop;
}
if (ARG_HELP.equals(currentArg)) {
displayHelp();
return FILES;
}
if (ARG_VERBOSE.equals(currentArg)) {
this.verbose = true;
continue loop;
}
if (ARG_QUIET.equals(currentArg)) {
this.quiet = true;
continue loop;
}
if (ARG_CONFIG.equals(currentArg)) {
mode = CONFIG_MODE;
continue loop;
}
// the current arg should be a file or a directory name
final File file = new File(currentArg);
if (file.exists()) {
if (filesToCleanup.length == fileCounter) {
System.arraycopy(filesToCleanup, 0, filesToCleanup = new File[fileCounter * 2], 0, fileCounter);
}
filesToCleanup[fileCounter++] = file;
} else {
String canonicalPath;
try {
canonicalPath = file.getCanonicalPath();
} catch(IOException e2) {
canonicalPath = file.getAbsolutePath();
}
final String errorMsg = file.isAbsolute()?
Messages.bind(Messages.CommandLineErrorFile, canonicalPath):
Messages.bind(Messages.CommandLineErrorFileTryFullPath, canonicalPath);
displayHelp(errorMsg);
return FILES;
}
break;
case CONFIG_MODE :
this.configName = currentArg;
this.options = readConfig(currentArg);
if (this.options == null) {
displayHelp(Messages.bind(Messages.CommandLineErrorConfig, currentArg));
return FILES;
}
mode = DEFAULT_MODE;
continue loop;
}
}
if (mode == CONFIG_MODE || this.options == null) {
displayHelp(Messages.bind(Messages.CommandLineErrorNoConfigFile));
return null;
}
if (this.quiet && this.verbose) {
displayHelp(
Messages.bind(
Messages.CommandLineErrorQuietVerbose,
new String[] { ARG_QUIET, ARG_VERBOSE }
));
return null;
}
if (fileCounter == 0) {
displayHelp(Messages.bind(Messages.CommandLineErrorFileDir));
return null;
}
if (filesToCleanup.length != fileCounter) {
System.arraycopy(filesToCleanup, 0, filesToCleanup = new File[fileCounter], 0, fileCounter);
}
return filesToCleanup;
}
/**
* Return a Java Properties file representing the options that are in the
* specified configuration file.
*/
private static Map<String, String> readConfig(final String filename) {
final File configFile = new File(filename);
try (BufferedInputStream stream = new BufferedInputStream(new FileInputStream(configFile));){
final Properties formatterOptions = new Properties();
formatterOptions.load(stream);
// Convert Properties to Map<String, String>
Map<String, String> options = new HashMap<>();
for (String key : formatterOptions.stringPropertyNames()) {
options.put(key, formatterOptions.getProperty(key));
}
return options;
} catch (IOException e) {
String canonicalPath = null;
try {
canonicalPath = configFile.getCanonicalPath();
} catch(IOException e2) {
canonicalPath = configFile.getAbsolutePath();
}
final String errorMessage;
if (!configFile.exists() && !configFile.isAbsolute()) {
errorMessage = Messages.bind(Messages.ConfigFileNotFoundErrorTryFullPath, new Object[] {
canonicalPath,
System.getProperty("user.dir") //$NON-NLS-1$
});
} else {
errorMessage = Messages.bind(Messages.ConfigFileReadingError, canonicalPath);
}
Util.log(e, errorMessage);
System.err.println(errorMessage);
}
return null;
}
/**
* Runs the Java code cleanup application
*/
@Override
public Object start(final IApplicationContext context) throws Exception {
final File[] filesToCleanup = processCommandLine((String[]) context.getArguments().get(IApplicationContext.APPLICATION_ARGS));
if (filesToCleanup == null) {
return IApplication.EXIT_OK;
}
if (!this.quiet) {
if (this.configName != null) {
System.out.println(Messages.bind(Messages.CommandLineConfigFile, this.configName));
}
System.out.println(Messages.bind(Messages.CommandLineStart));
}
// clean up the list of files and/or directories
for (final File file : filesToCleanup) {
if (file.isDirectory()) {
cleanDirTree(file);
} else if (Util.isJavaLikeFileName(file.getPath())) {
cleanFile(file);
}
}
if (!this.quiet) {
System.out.println(Messages.bind(Messages.CommandLineDone));
}
return IApplication.EXIT_OK;
}
@Override
public void stop() {
// do nothing
}
/**
* Display the command line usage message.
*/
private static void displayHelp() {
System.out.println(Messages.bind(Messages.CommandLineUsage));
}
private static void displayHelp(final String message) {
System.err.println(message);
System.out.println();
displayHelp();
}
/**
* Recursively clean up the Java source code that is contained in the
* directory rooted at dir.
*/
private void cleanDirTree(final File dir) {
final File[] files = dir.listFiles();
if (files == null) {
return;
}
for (final File file : files) {
if (file.isDirectory()) {
cleanDirTree(file);
} else if (Util.isJavaLikeFileName(file.getPath())) {
cleanFile(file);
}
}
}
}