| /******************************************************************************* |
| * Copyright (c) 2005, 2011 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Ben Konrath <ben@bagu.org> - initial implementation |
| * Red Hat Incorporated - improvements based on comments from JDT developers |
| * IBM Corporation - Code review and integration |
| * IBM Corporation - Fix for 340181 |
| *******************************************************************************/ |
| package org.eclipse.jdt.core.formatter; |
| |
| import java.io.BufferedInputStream; |
| import java.io.BufferedWriter; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Map; |
| import java.util.Properties; |
| |
| import org.eclipse.equinox.app.IApplication; |
| import org.eclipse.equinox.app.IApplicationContext; |
| import org.eclipse.jdt.core.ToolFactory; |
| import org.eclipse.jdt.internal.core.util.Util; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.Document; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.text.edits.TextEdit; |
| |
| /** |
| * Implements an Eclipse Application for org.eclipse.jdt.core.JavaCodeFormatter. |
| * |
| * <p>On MacOS, when invoked using the Eclipse executable, the "user.dir" property is set to the folder |
| * in which the eclipse.ini file is located. This makes it harder to use relative paths to point to the |
| * files to be formatted or the configuration file to use to set the code formatter's options.</p> |
| * |
| * <p>There are a couple improvements that could be made: 1. Make a list of all the |
| * files first so that a file does not get formatted twice. 2. Use a text based |
| * progress monitor for output.</p> |
| * |
| * @author Ben Konrath <bkonrath@redhat.com> |
| * @since 3.2 |
| * @noinstantiate This class is not intended to be instantiated by clients. |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class CodeFormatterApplication implements IApplication { |
| |
| /** |
| * Deals with the messages in the properties file (cut n' pasted from a |
| * generated class). |
| */ |
| private final static class Messages extends NLS { |
| private static final String BUNDLE_NAME = "org.eclipse.jdt.core.formatter.messages";//$NON-NLS-1$ |
| |
| public static String CommandLineConfigFile; |
| |
| public static String CommandLineDone; |
| |
| public static String CommandLineErrorConfig; |
| |
| public static String CommandLineErrorFileTryFullPath; |
| |
| public static String CommandLineErrorFile; |
| |
| public static String CommandLineErrorFileDir; |
| |
| public static String CommandLineErrorQuietVerbose; |
| |
| public static String CommandLineErrorNoConfigFile; |
| |
| public static String CommandLineFormatting; |
| |
| public static String CommandLineStart; |
| |
| public static String CommandLineUsage; |
| |
| public static String ConfigFileNotFoundErrorTryFullPath; |
| |
| public static String ConfigFileReadingError; |
| |
| public static String FormatProblem; |
| |
| public static String CaughtException; |
| |
| public static String ExceptionSkip; |
| |
| static { |
| NLS.initializeMessages(BUNDLE_NAME, Messages.class); |
| } |
| |
| /** |
| * Bind the given message's substitution locations with the given string |
| * values. |
| * |
| * @param message |
| * the message to be manipulated |
| * @return the manipulated String |
| */ |
| public static String bind(String message) { |
| return bind(message, null); |
| } |
| |
| /** |
| * Bind the given message's substitution locations with the given string |
| * values. |
| * |
| * @param message |
| * the message to be manipulated |
| * @param binding |
| * the object to be inserted into the message |
| * @return the manipulated String |
| */ |
| public static String bind(String message, Object binding) { |
| return bind(message, new Object[] { |
| binding |
| }); |
| } |
| |
| /** |
| * Bind the given message's substitution locations with the given string |
| * values. |
| * |
| * @param message |
| * the message to be manipulated |
| * @param binding1 |
| * An object to be inserted into the message |
| * @param binding2 |
| * A second object to be inserted into the message |
| * @return the manipulated String |
| */ |
| public static String bind(String message, Object binding1, Object binding2) { |
| return bind(message, new Object[] { |
| binding1, binding2 |
| }); |
| } |
| |
| /** |
| * Bind the given message's substitution locations with the given string |
| * values. |
| * |
| * @param message |
| * the message to be manipulated |
| * @param bindings |
| * An array of objects to be inserted into the message |
| * @return the manipulated String |
| */ |
| public static String bind(String message, Object[] bindings) { |
| return MessageFormat.format(message, bindings); |
| } |
| } |
| |
| 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 options = null; |
| |
| private static final String PDE_LAUNCH = "-pdelaunch"; //$NON-NLS-1$ |
| |
| private boolean quiet = false; |
| |
| private boolean verbose = false; |
| |
| /** |
| * Display the command line usage message. |
| */ |
| private void displayHelp() { |
| System.out.println(Messages.bind(Messages.CommandLineUsage)); |
| } |
| |
| private void displayHelp(String message) { |
| System.err.println(message); |
| System.out.println(); |
| displayHelp(); |
| } |
| |
| /** |
| * Recursively format the Java source code that is contained in the |
| * directory rooted at dir. |
| */ |
| private void formatDirTree(File dir, CodeFormatter codeFormatter) { |
| |
| File[] files = dir.listFiles(); |
| if (files == null) |
| return; |
| |
| for (int i = 0; i < files.length; i++) { |
| File file = files[i]; |
| if (file.isDirectory()) { |
| formatDirTree(file, codeFormatter); |
| } else if (Util.isJavaLikeFileName(file.getPath())) { |
| formatFile(file, codeFormatter); |
| } |
| } |
| } |
| |
| /** |
| * Format the given Java source file. |
| */ |
| private void formatFile(File file, CodeFormatter codeFormatter) { |
| IDocument doc = new Document(); |
| try { |
| // read the file |
| if (this.verbose) { |
| System.out.println(Messages.bind(Messages.CommandLineFormatting, file.getAbsolutePath())); |
| } |
| String contents = new String(org.eclipse.jdt.internal.compiler.util.Util.getFileCharContent(file, null)); |
| // format the file (the meat and potatoes) |
| doc.set(contents); |
| TextEdit edit = codeFormatter.format(CodeFormatter.K_COMPILATION_UNIT | CodeFormatter.F_INCLUDE_COMMENTS, contents, 0, contents.length(), 0, null); |
| if (edit != null) { |
| edit.apply(doc); |
| } else { |
| System.err.println(Messages.bind(Messages.FormatProblem, file.getAbsolutePath())); |
| return; |
| } |
| |
| // write the file |
| final BufferedWriter out = new BufferedWriter(new FileWriter(file)); |
| try { |
| out.write(doc.get()); |
| out.flush(); |
| } finally { |
| try { |
| out.close(); |
| } catch (IOException e) { |
| /* ignore */ |
| } |
| } |
| } catch (IOException e) { |
| String errorMessage = Messages.bind(Messages.CaughtException, "IOException", e.getLocalizedMessage()); //$NON-NLS-1$ |
| Util.log(e, errorMessage); |
| System.err.println(Messages.bind(Messages.ExceptionSkip ,errorMessage)); |
| } catch (BadLocationException e) { |
| String errorMessage = Messages.bind(Messages.CaughtException, "BadLocationException", e.getLocalizedMessage()); //$NON-NLS-1$ |
| Util.log(e, errorMessage); |
| System.err.println(Messages.bind(Messages.ExceptionSkip ,errorMessage)); |
| } |
| } |
| |
| private File[] processCommandLine(String[] argsArray) { |
| |
| ArrayList args = new ArrayList(); |
| for (int i = 0, max = argsArray.length; i < max; i++) { |
| args.add(argsArray[i]); |
| } |
| int index = 0; |
| final int argCount = argsArray.length; |
| |
| final int DEFAULT_MODE = 0; |
| final int CONFIG_MODE = 1; |
| |
| int mode = DEFAULT_MODE; |
| final int INITIAL_SIZE = 1; |
| int fileCounter = 0; |
| |
| File[] filesToFormat = new File[INITIAL_SIZE]; |
| |
| loop: while (index < argCount) { |
| String currentArg = argsArray[index++]; |
| |
| switch(mode) { |
| case DEFAULT_MODE : |
| if (PDE_LAUNCH.equals(currentArg)) { |
| continue loop; |
| } |
| if (ARG_HELP.equals(currentArg)) { |
| displayHelp(); |
| return null; |
| } |
| 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 |
| File file = new File(currentArg); |
| if (file.exists()) { |
| if (filesToFormat.length == fileCounter) { |
| System.arraycopy(filesToFormat, 0, (filesToFormat = new File[fileCounter * 2]), 0, fileCounter); |
| } |
| filesToFormat[fileCounter++] = file; |
| } else { |
| String canonicalPath; |
| try { |
| canonicalPath = file.getCanonicalPath(); |
| } catch(IOException e2) { |
| canonicalPath = file.getAbsolutePath(); |
| } |
| String errorMsg = file.isAbsolute()? |
| Messages.bind(Messages.CommandLineErrorFile, canonicalPath): |
| Messages.bind(Messages.CommandLineErrorFileTryFullPath, canonicalPath); |
| displayHelp(errorMsg); |
| return null; |
| } |
| break; |
| case CONFIG_MODE : |
| this.configName = currentArg; |
| this.options = readConfig(currentArg); |
| if (this.options == null) { |
| displayHelp(Messages.bind(Messages.CommandLineErrorConfig, currentArg)); |
| return null; |
| } |
| 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 (filesToFormat.length != fileCounter) { |
| System.arraycopy(filesToFormat, 0, (filesToFormat = new File[fileCounter]), 0, fileCounter); |
| } |
| return filesToFormat; |
| } |
| |
| /** |
| * Return a Java Properties file representing the options that are in the |
| * specified configuration file. |
| */ |
| private Properties readConfig(String filename) { |
| BufferedInputStream stream = null; |
| File configFile = new File(filename); |
| try { |
| stream = new BufferedInputStream(new FileInputStream(configFile)); |
| final Properties formatterOptions = new Properties(); |
| formatterOptions.load(stream); |
| return formatterOptions; |
| } catch (IOException e) { |
| String canonicalPath = null; |
| try { |
| canonicalPath = configFile.getCanonicalPath(); |
| } catch(IOException e2) { |
| canonicalPath = configFile.getAbsolutePath(); |
| } |
| 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); |
| } finally { |
| if (stream != null) { |
| try { |
| stream.close(); |
| } catch (IOException e) { |
| /* ignore */ |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Runs the Java code formatter application |
| */ |
| public Object start(IApplicationContext context) throws Exception { |
| File[] filesToFormat = processCommandLine((String[]) context.getArguments().get(IApplicationContext.APPLICATION_ARGS)); |
| |
| if (filesToFormat == 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)); |
| } |
| |
| final CodeFormatter codeFormatter = ToolFactory.createCodeFormatter(this.options); |
| // format the list of files and/or directories |
| for (int i = 0, max = filesToFormat.length; i < max; i++) { |
| final File file = filesToFormat[i]; |
| if (file.isDirectory()) { |
| formatDirTree(file, codeFormatter); |
| } else if (Util.isJavaLikeFileName(file.getPath())) { |
| formatFile(file, codeFormatter); |
| } |
| } |
| if (!this.quiet) { |
| System.out.println(Messages.bind(Messages.CommandLineDone)); |
| } |
| |
| return IApplication.EXIT_OK; |
| } |
| public void stop() { |
| // do nothing |
| } |
| } |