package org.hybridlabs.source.formatter.m2;
/**
* Copyright 2008 hybrid labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hybridlabs.file.FileHelper;
import org.hybridlabs.source.beautifier.BeautifierWithConvention;
import org.hybridlabs.source.beautifier.CharacterSequence;
import org.hybridlabs.source.beautifier.JavaImportBeautifierImpl;
/**
* A utility to drive the code beautifier. Note the system properties that point
* to the various configuration parameters used by the utility.
*
* @author Jack Archer, Bob Fields
* @version 1.0, Feb. 2007
*/
public class FormatManager {
private String conventionFilePath = System
.getProperty("convention.file.path");
private String conventionFileName = System.getProperty(
"convention.file.name", "default-convention.xml");
private final String slash = System.getProperty("file.separator");
private static final Log LOG = LogFactory.getLog(FormatManager.class);
/**
* Run as ant task from BuildCT.xml. Formats imports and Jalopy. If args[1]
* is empty, source input directory files will be overwritten. System
* properties control behavior: convention.file.path is the path to the
* Jalopy convention file (normally org/hybridlabs under ${m2repo}
* convention.file.name is the name if the Jalopy convention file
* updateImports true = convert fully qualified classnames to imports
* updateJalopy true = run Jalopy formatting against output files
*
* @param args
* 1 - root directory of the source to be formatted 2- target
* directory to write formatted source to
*/
public static void main(String[] args) {
try {
// Format imports and Jalopy separately because file will be rolled
// back if
// Jalopy parsing fails.
boolean updateImports = Boolean.parseBoolean(System.getProperty(
"updateImports", "true"));
boolean updateJalopy = Boolean.parseBoolean(System.getProperty(
"updateJalopy", "true"));
LOG.info("Formatting " + args[0] + " updateImports="
+ updateImports + " updateJalopy=" + updateJalopy);
if (args.length > 1) {
if (updateImports) {
FormatManager.formatImportsAllFiles(args[0], args[1]);
}
if (updateJalopy) {
FormatManager.formatJalopyAllFiles(args[0], args[1]);
}
} else {
if (updateImports) {
FormatManager.formatImportsAllFiles(args[0]);
}
if (updateJalopy) {
FormatManager.formatJalopyAllFiles(args[0]);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Formats all Java files discovered in the supplied directory and
* subdirectories.
*
* @param sourceRootDirectory
* Directory for Java files to format and replace
* @throws FileNotFoundException
* If required files or directories do not exist
* @throws IOException
* If the creation of a new file or directory fails
*/
public static void formatJalopyAllFiles(String sourceRootDirectory)
throws FileNotFoundException, IOException {
formatAllFiles(sourceRootDirectory, sourceRootDirectory, false, true);
}
/**
* Formats all Java files discovered in the supplied directory and
* subdirectories.
*
* @param sourceRootDirectory
* Directory for Java files to format and replace
* @param targetRootDirectory
* Target Java source output directory (if null, use source
* directory)
* @throws FileNotFoundException
* If required files or directories do not exist
* @throws IOException
* If the creation of a new file or directory fails
*/
public static void formatImportsAllFiles(String sourceRootDirectory,
String targetRootDirectory) throws FileNotFoundException,
IOException {
formatAllFiles(sourceRootDirectory, targetRootDirectory, true, false);
}
/**
* Formats all Java files discovered in the supplied directory and
* subdirectories.
*
* @param sourceRootDirectory
* Directory for Java files to format and replace
* @param targetRootDirectory
* Target Java source output directory (if null, use source
* directory)
* @throws FileNotFoundException
* If required files or directories do not exist
* @throws IOException
* If the creation of a new file or directory fails
*/
public static void formatJalopyAllFiles(String sourceRootDirectory,
String targetRootDirectory) throws FileNotFoundException,
IOException {
formatAllFiles(sourceRootDirectory, targetRootDirectory, false, true);
}
/**
* Formats all Java files discovered in the supplied directory and
* subdirectories.
*
* @param sourceRootDirectory
* Directory for Java files to format and replace
* @throws FileNotFoundException
* If required files or directories do not exist
* @throws IOException
* If the creation of a new file or directory fails
*/
public static void formatImportsAllFiles(String sourceRootDirectory)
throws FileNotFoundException, IOException {
formatAllFiles(sourceRootDirectory, sourceRootDirectory, true, false);
}
/**
* Formats all Java files discovered in the supplied directory.
*
* @param sourceRootDirectory
* Directory for Java files to format
* @param targetRootDirectory
* Output directory for
* @param imports
* Change fully qualified classnames to imports in file
* @param jalopy
* Run jalopy against file
* @throws FileNotFoundException
* If required files or directories do not exist
* @throws IOException
* If the creation of a new file or directory fails
*/
public static void formatAllFiles(String sourceRootDirectory,
String targetRootDirectory, boolean imports, boolean jalopy)
throws FileNotFoundException, IOException {
String message = "Formatting ";
if (imports) {
message += "imports";
if (jalopy) {
message += " and jalopy";
}
} else if (jalopy) {
message += "jalopy";
}
message += ": ";
LOG.info(message + " under " + targetRootDirectory);
JavaImportBeautifierImpl beautifier = new JavaImportBeautifierImpl();
FormatManager manager = new FormatManager();
manager.setConvention(beautifier);
beautifier.setOrganizeImports(imports);
// There are problems with the Jalopy parser for the generated files:
// Unexpected char '<'
beautifier.setFormat(jalopy);
List<File> sourceFiles = manager.getAllJavaFiles(sourceRootDirectory);
// Now reformat and save the targeted files to the target directory
int listSize = sourceFiles.size();
for (int i = 0; i < listSize; i++) {
File sourceFile = (File) sourceFiles.get(i);
LOG.info(message + sourceFile.getName() + " under "
+ sourceFile.getParent());
CharacterSequence sequence = new CharacterSequence(
FileHelper.loadStringBuffer(sourceFile));
String original = sequence.getString();
try {
beautifier.beautify(sequence);
// Only overwrite the file if the beautifier changes it, or if a
// different directory
// Sometimes Jalopy throws an error and returns a zero length
// file.
if (sequence.length() > 1
&& (!original.equals(sequence.getString()))) {
File targetFile = new File((manager.getTargetDirectory(
sourceFile, sourceRootDirectory,
targetRootDirectory)), sourceFile.getName());
// File targetDirectory = new
// File((manager.getTargetDirectory(
// sourceFile, targetRootDirectory)), sourceFile.getName());
FileOutputStream outputSource = new FileOutputStream(
targetFile);
outputSource.write(sequence.getString().getBytes());
outputSource.flush();
outputSource.close();
}
} catch (RuntimeException e) {
LOG.error("Exception formatting file: " + sourceFile.getName(),
e);
// System.out.println("Exception formatting file: " +
// sourceFile.getName());
// e.printStackTrace();
}
}
LOG.info("Finished Formatting all files under " + sourceRootDirectory);
}
/**
* Formats a single Java file.
*
* @param sourceFile
* Java file to format
* @param targetRootDirectory
* Output directory for
* @param imports
* Change fully qualified classnames to imports in file
* @param jalopy
* Run jalopy against file
* @throws FileNotFoundException
* If required files or directories do not exist
* @throws IOException
* If the creation of a new file or directory fails
*/
public static void formatFile(File sourceFile, String targetRootDirectory,
boolean imports, boolean jalopy) throws FileNotFoundException,
IOException {
JavaImportBeautifierImpl beautifier = new JavaImportBeautifierImpl();
FormatManager manager = new FormatManager();
manager.setConvention(beautifier);
beautifier.setOrganizeImports(imports);
// There are problems with the Jalopy parser for the generated files:
// Unexpected char '<'
beautifier.setFormat(jalopy);
LOG.info("Formatting file: " + sourceFile.getName());
CharacterSequence sequence = new CharacterSequence(
FileHelper.loadStringBuffer(sourceFile));
try {
beautifier.beautify(sequence);
// Only overwrite the file if the beautifier changes it, or if a
// different directory
// Sometimes Jalopy throws an error and returns a zero length file.
if (sequence.length() > 1) {
File targetFile = null;
if (targetRootDirectory == null
|| "".equals(targetRootDirectory)) {
targetFile = sourceFile;
} else {
targetFile = new File(targetRootDirectory,
sourceFile.getName());
targetFile = new File((manager.getTargetDirectory(
sourceFile, sourceFile.getParent(),
targetRootDirectory)), sourceFile.getName());
}
// File targetDirectory = new File((manager.getTargetDirectory(
// sourceFile, targetRootDirectory)), sourceFile.getName());
FileOutputStream outputSource = new FileOutputStream(targetFile);
outputSource.write(sequence.getString().getBytes());
outputSource.flush();
outputSource.close();
}
} catch (RuntimeException e) {
LOG.error("Exception formatting file: " + sourceFile.getName(), e);
// System.out.println("Exception formatting file: " +
// sourceFile.getName());
// e.printStackTrace();
}
LOG.info("Finished Formatting all files");
}
/**
* Locates and sets Jalopy formatting convention file
*
* @param beautifier
* Instance of the beautifier class, not yet set to with external
* Jalopy formatting conventions
* @throws FileNotFoundException
* If the convention file cannot be located
*/
private void setConvention(BeautifierWithConvention beautifier) {
String fileName = this.conventionFilePath + this.slash
+ this.conventionFileName;
File conventionFile = new File(fileName);
if (conventionFile.exists()) {
beautifier.setConventionFilePath(fileName);
} else {
this.conventionFilePath = "/default-convention.xml";
beautifier.setConventionFilePath("/default-convention.xml");
}
}
/**
* Fetches a target directory, creating it if it doesn't exist
*
* @param sourceFile
* Filename to create
* @param sourceRootDirectory
* sourceFile directory
* @param targetRootDirectory
* target directory root to create and return
* @return the target output directory for the specified re-formatted source
* File
*/
private File getTargetDirectory(File sourceFile,
String sourceRootDirectory, String targetRootDirectory) {
String path = sourceFile.getParent();
// Find the part of the source file below the source directory, append
// to target directory
int index = path.indexOf(sourceRootDirectory);
if (index == -1) {
if (sourceRootDirectory.indexOf("/") > -1) {
// Pathname translation - replace doesn't work with //
sourceRootDirectory = replace(sourceRootDirectory, "/", "\\");
index = path.indexOf(sourceRootDirectory);
} else {
sourceRootDirectory = replace(sourceRootDirectory, "\\", "/");
index = path.indexOf(sourceRootDirectory);
}
}
String sourcePath = path
.substring(index + sourceRootDirectory.length());
if (targetRootDirectory == null || targetRootDirectory.equals("")) {
targetRootDirectory = sourceRootDirectory;
}
File targetPath = new File(targetRootDirectory + sourcePath);
targetPath.mkdirs();
return targetPath;
}
/**
* The String / stringBuffer replace command does not work with // !
*
* @param target
* @param toReplace
* @param replaceBy
* @return String with replaced tokens
*/
public static String replace(String target, String toReplace,
String replaceBy) {
// StringBuffer result = new StringBuffer(target);
int i = 0;
// int j = 0;
int toRepLength = toReplace.length();
// int lengthDecrease = toRepLength - replaceBy.length();
while ((i = target.indexOf(toReplace, i)) != -1) {
target = target.substring(0, i)
+ replaceBy
+ new StringBuffer(target.substring(i + toRepLength,
target.length()));
// result.replace(i - j, toRepLength, replaceBy);
// i += toRepLength;
// j += lengthDecrease;
}
return target;
}
/**
* Traverses to the end of a directory tree, starting at indicated source
* directory, and collects all found Java source files.
*
* @param rootDirName
* The full path name of root of the directory tree to search for
* Java source files
* @return List of Java source file discovered in the indicated directory
* tree, returned as Files
* @throws FileNotFoundException
* if the root directory does not exist
*/
private List<File> getAllJavaFiles(String rootDirName)
throws FileNotFoundException {
List<File> javaFiles = new ArrayList<File>();
List<File> sourceDirs = new ArrayList<File>();
File rootDir = asDirectory(rootDirName);
// Traverse the directory tree from the starting source directory,
// collect all the subdirectories
List<File> subDirs = new ArrayList<File>();
subDirs.add(rootDir);
int listSize = 0;
while (subDirs.size() > 0) {
List<File> tempDirs = new ArrayList<File>();
listSize = subDirs.size();
for (int i = 0; i < listSize; i++) {
File subDir = (File) subDirs.get(i);
sourceDirs.add(subDir);
tempDirs.addAll(getDirectories(subDir));
}
subDirs = tempDirs;
}
listSize = sourceDirs.size();
for (int i = 0; i < listSize; i++) {
File sourceDir = (File) sourceDirs.get(i);
javaFiles.addAll(getJavaFiles(sourceDir));
}
return javaFiles;
}
/**
* Collects the Java files in the indicated directory
*
* @param dir
* The directory to search for Java source files
* @return List of discovered Java source files as 'File' instances
*/
private List<File> getJavaFiles(File dir) {
List<File> files = new ArrayList<File>();
// Capture the Java source in the source directory
JavaSourceFilter javaFilter = new JavaSourceFilter();
String[] fileNames = dir.list(javaFilter);
int listSize = fileNames.length;
for (int i = 0; i < listSize; i++) {
String fileName = fileNames[i];
File file = new File(dir.getAbsolutePath(), fileName);
// Directories might end in .java also
if (file.isFile()) {
files.add(file);
}
}
return files;
}
/**
* Collects the subdirectories for the indicated directory
*
* @param dir
* The directory to find subdirectories for
* @return List of discovered subdirectories as 'File' instances
*/
private List<File> getDirectories(File dir) {
List<File> dirs = new ArrayList<File>();
// Capture the subdirectories the source directory
DirectoryFilter dirFilter = new DirectoryFilter();
String[] dirNameArray = dir.list(dirFilter);
int listSize = dirNameArray.length;
for (int i = 0; i < listSize; i++) {
String dirName = dirNameArray[i];
File subDir = new File(dir.getAbsolutePath(), dirName);
dirs.add(subDir);
}
return dirs;
}
/**
* Verifies the incoming path name exists and is a directory; if not throws
* exception
*
* @param dirPath
* Path to check
* @return The directory specified in the incoming text as a File instance
* @throws FileNotFoundException
* if the incoming path name does not exist or is not a
* directory
*/
private File asDirectory(String dirPath) throws FileNotFoundException {
File dir = new File(dirPath);
if (!dir.isDirectory()) {
StringBuffer msg = new StringBuffer(300);
msg.append("Path ");
msg.append(dirPath);
msg.append(" does not exist or is not a directory.");
throw new FileNotFoundException(msg.toString());
}
return new File(dirPath);
}
/**
* This Filter identifies Java source code files
*/
private class JavaSourceFilter implements FilenameFilter {
/**
* Checks for files ending with '.java', identifying them as Java source
*
* @param dir
* the path name of the file to be filtered
* @param name
* the name of the file (or last element of a directory path)
* of the file to be filtered
* @return True If the file is a Java source file (has '.java'
* extension)
*/
public boolean accept(File dir, String name) {
return name.endsWith(".java");
}
}
/**
* This Filter identifies the directories with a list of files
*/
private class DirectoryFilter implements FilenameFilter {
/**
* Filters incoming file to see if it is a directory
*
* @param dir
* Absolute path of the file being filtered
* @param name
* he name of the file (or last element of a directory path)
* of the file to be filtered
* @return True is a File is a directory
*/
public boolean accept(File dir, String name) {
File temp = new File(dir.getAbsoluteFile(), name);
return temp.isDirectory();
}
}
}