blob: 5ba09b8f3a6a8c0995b1432235d66b5f7a57f1e9 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2018 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
// 08/29/2016 Jody Grassel
// - 500441: Eclipselink core has System.getProperty() calls that are not potentially executed under doPriv()
package org.eclipse.persistence.tools;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.Objects;
import java.util.Properties;
import java.util.Vector;
/**
* This class performs package renaming. It demonstrates the following:
*
* a) Reading the properties file to be a reference for changing the package
* name from your source code.
*
* b) Traverse source root directory for creating a corresponding output directory
* and finding the java source file(s) to be changing the package name.
*
* c) Search and replace the old TopLink package name(s) with new one(s) according to the reference.
*
* You will be able to see the logging message at the command line window
* where the PackageRenamer is running.
*
*/
public class PackageRenamer {
int numberOfTotalFile = 0;
int numberOfChangedFile = 0;
// contains the option of Log file.
boolean specifyLogFile = false;
java.io.PrintWriter outLog = null;
// contains the Log File
String logFileString = null;
java.io.File logFile;
// contains the source-root-directory
java.io.File sourceRootDirFile;
// contains the destination-root-directory
java.io.File destinationRootDir;
// contains the properties file path
String sourceProperties;
// contains a reference for renaming
Properties properties = null;
String propertiesFileName;
boolean VERBOSE = true;
protected static final String SYSTEM_OUT = "System.out";
BufferedReader reader = null;
String[] UNSUPPORTED_EXTENSIONS = { "jar", "zip", "ear", "war", "dll", "class", "exe" };
int BUFSIZ = 1024 * 4;
final static String CR;
static {
// bug 2756643
CR = PrivilegedAccessHelper.shouldUsePrivilegedAccess() ?
AccessController.doPrivileged(new PrivilegedAction<>() {
@Override
public String run() {
return System.getProperty("line.separator");
}
})
: System.getProperty("line.separator");
}
/**
* The constructor of a PackageRenamer class.
*/
public PackageRenamer() {
this(getDefaultPropertiesFileName());
}
public PackageRenamer(String propertiesFileName) {
System.out.println("");
System.out.println("TopLink Package Renamer");
System.out.println("-----------------------");
System.out.println(bannerText());
sourceRootDirFile = existingDirectoryFromPrompt();
System.out.println("");
destinationRootDir = promptForDestinationDirectory();
System.out.println("");
this.propertiesFileName = propertiesFileName;
outLog = streamForNonExistentFilePrompt();
properties = readChangesFile(propertiesFileName);
}
public PackageRenamer(String[] args) {
this.propertiesFileName = args[0];
sourceRootDirFile = buildAndCheckExistingDirFile(args[1]);
destinationRootDir = buildAndCheckDestinationFile(args[2]);
if (args.length == 4) {
outLog = buildAndCheckLogWriter(args[3]);
} else {
outLog = buildAndCheckLogWriter(SYSTEM_OUT);
}
properties = readChangesFile(args[0]);
logln(bannerText());
}
protected String bannerText() {
StringBuilder stringBuilder = new StringBuilder(CR.length()*3+66+42);
stringBuilder.append(CR);
stringBuilder.append("NOTE: The package renamer is meant to be run on plain text files. ");
stringBuilder.append(CR);
stringBuilder.append("A rename will NOT be done on binary files.");
stringBuilder.append(CR);
return stringBuilder.toString();
}
/**
* Do a binary copy of the file byte buffer by byte buffer.
*
* @param inFile
* The file to copy
* @param outFile
* The destination file
* @throws FileNotFoundException
* if either of the two files does not exist
* @throws IOException
* if any other IO related error occurs
*/
public void binaryCopy(File inFile, File outFile) throws FileNotFoundException, IOException {
byte[] buf = new byte[BUFSIZ];
try (FileInputStream in = new FileInputStream(inFile);
FileOutputStream out = new FileOutputStream(outFile)) {
int nBytesRead;
while ((nBytesRead = in.read(buf)) != -1) {
out.write(buf, 0, nBytesRead);
}
}
}
protected boolean bufferContainsNullChar(byte[] buffer, int bufferLength) {
for (int i = 0; i < bufferLength; i++) {
if (buffer[i] == 0) {
return true;
}
}
return false;
}
/**
* INTERNAL Creates a destination directory File object under the path
* passed, verifying correctness.
*
* @param aDirString
* The path to the directory File object to create
* @return The destination directory File object
*/
public File buildAndCheckDestinationFile(String aDirString) {
if (aDirString == null) {
throw new PackageRenamerException("Invalid destination directory entered.");
}
File aDirFile = new File(aDirString);
if (aDirFile.exists()) {
File[] dirContent = aDirFile.listFiles();
if (dirContent != null && dirContent.length != 0) {
throw new PackageRenamerException("Output Directory:" + CR + " '" + aDirString + "'" + CR + "exists and is not empty.");
}
}
if (!aDirFile.isAbsolute()) {
throw new PackageRenamerException("A relative destination directory was entered:" + CR + " '" + aDirString + "'" + CR + "The directory must be absolute.");
}
// Check if the destination directory is within the source directory. This would create an infinite loop.
if (directoryIsSubdirectory(sourceRootDirFile, aDirFile)) {
throw new PackageRenamerException("Invalid destination directory entered:" + CR + " '" + aDirString + "'" + CR + "It cannot be a sub-directory of the source directory.");
}
return aDirFile;
}
public File buildAndCheckExistingDirFile(String aDirString) {
if (aDirString == null) {
throw new PackageRenamerException("Invalid source directory entered.");
}
File aDirFile = new File(aDirString);
if (!aDirFile.exists() || !aDirFile.isDirectory()) {
throw new PackageRenamerException("Input Directory:" + CR + " '" + aDirString + "'" + CR + "does not exist or is not a directory.");
}
if (!aDirFile.isAbsolute()) {
throw new PackageRenamerException("A relative source directory was entered:" + CR + " '" + aDirString + "'" + CR + "The directory must be absolute.");
}
return aDirFile;
}
public PrintWriter buildAndCheckLogWriter(String logFileString) {
if (logFileString == null) {
throw new PackageRenamerException("Invalid log file name entered.");
}
try {
if (logFileString.equals(SYSTEM_OUT)) {
return new PrintWriter(System.out);
} else {
File aLogFile = new File(logFileString);
if (aLogFile.exists()) {
throw new PackageRenamerException("Specified log file cannot be created:" + CR + " '" + logFileString + "'");
}
FileWriter writerLog = new FileWriter(logFileString);
return new java.io.PrintWriter(writerLog);
}
} catch (IOException ioException) {
throw new PackageRenamerException("Unhandled IOException occurred while configuring log file: '" + logFileString + "', " + ioException.getMessage());
}
}
protected void cleanup() {
//Closing the Log file in case it is being open.
if (outLog != null) {
outLog.close();
}
}
/**
* This method creates an output directory for post-rename file(s).
*
* @param aDirectory
* The output directory to create
*/
public void createDestinationDirectory(File aDirectory) {
if (!aDirectory.exists()) {
if (!aDirectory.mkdirs()) {
throw new PackageRenamerException("Error while creating directory:" + CR + " '" + aDirectory + "'");
}
} else {
throw new PackageRenamerException("Error directory: '" + aDirectory + "' already exists but shouldn't.");
}
}
/**
* Return true if directory2 is contained within directory1. Both
* directories must be absolute.
*
* @param directory1
* The higher level directory
* @param directory2
* The lower level directory
* @return TRUE if directory2 is a subdirectory of directory1
*/
public static boolean directoryIsSubdirectory(File directory1, File directory2) {
// System.out.println(directory1 + " contains " + directory2);
if (directory2 == null) {
return false;
} else if (directory1.equals(directory2)) {
return true;
} else {
return directoryIsSubdirectory(directory1, directory2.getParentFile());
}
}
public File existingDirectoryFromPrompt() {
System.out.print("Enter the path of the directory which contains the files to rename:" + CR + "> ");
String aLine = null;
try {
aLine = getReader().readLine();
} catch (IOException exception) {
throw new PackageRenamerException("Error while reading the source directory: " + exception.getMessage());
}
return buildAndCheckExistingDirFile(aLine);
}
public static String getDefaultPropertiesFileName() {
String currentDirectory = PrivilegedAccessHelper.shouldUsePrivilegedAccess() ?
AccessController.doPrivileged(new PrivilegedAction<>() {
@Override
public String run() {
return System.getProperty("user.dir");
}
})
: System.getProperty("user.dir");
return currentDirectory + File.separator + "packageRename.properties";
}
public synchronized BufferedReader getReader() {
if (reader == null) {
reader = new BufferedReader(new InputStreamReader(System.in));
}
return reader;
}
/**
* Return true if the PackageRenamer should work on the given file
* extension.
*
* @param extension
* The file extension to check for being supported
* @return TRUE if the extension is supported
*/
public boolean isExtensionSupported(String extension) {
/* This was cut out because the binary check recognize these files and just copy them byte by byte.
for (int i=0; i<UNSUPPORTED_EXTENSIONS.length; i++) {
if ( UNSUPPORTED_EXTENSIONS[i].equalsIgnoreCase(extension)) {
return false;
}
}
*/
return true;
}
/**
* @param str
* The String to log
*/
public void logln(String str) {
outLog.println(str);
outLog.flush();
}
/**
* Main method to run the PackageRenamer
*
* @param args
* Command line arguments
*/
public static void main(String[] args) {
// Check the accuracy of the arguments
PackageRenamer instance = null;
try {
// Creates an instance of an PackageRenamer with some arguments.
if (args.length == 0) {
instance = new PackageRenamer();
instance.run();
} else if (args.length == 1) {
instance = new PackageRenamer(args[0]);
instance.run();
} else if ((args.length == 3) || (args.length == 4)) {
instance = new PackageRenamer(args);
instance.run();
} else {
usage();
System.exit(-1);
}
} catch (PackageRenamerException exception) {
usage();
System.err.println("**************************************************************************");
System.err.println("Error during package rename. PACKAGE RENAME FAILED.");
System.err.println(exception.getMessage());
System.err.println("**************************************************************************");
System.exit(-1);
} catch (Throwable unknowException) {
System.err.println("Unhandled exception was thrown during rename:");
unknowException.printStackTrace();
System.exit(-1);
}
instance.logln("");
instance.logln("PACKAGE RENAME WAS SUCCESSFUL");
instance.cleanup();
}
/**
* Returns the extension of the given file.
*
* @param aFile
* The file of which to retrieve the extension
* @return The file extension or an empty string if none was found.
*/
public String parseFileExtension(File aFile) {
int index = aFile.getName().lastIndexOf('.');
if (index == -1) {
return "";
} else {
return aFile.getName().substring(index + 1);
}
}
/**
* INTERNAL Prompt from System.in for an empty or non-existent directory to
* use as the destination directory.
*
* @return The destination directory File object
*/
protected File promptForDestinationDirectory() {
System.out.print("Enter the path of the directory to which files are to be copied:" + CR + "> ");
String aLine = null;
try {
aLine = getReader().readLine();
} catch (IOException exception) {
throw new PackageRenamerException("Error while reading the destination directory specified: " + exception.getMessage());
}
return buildAndCheckDestinationFile(aLine);
}
/**
* This readChangesFile() method reads the given properties file to be a
* reference for renaming TopLink package name.
*
* @param filename
* The input file to use for the renaming
* @return The Properties object containing the renaming information
*/
public Properties readChangesFile(String filename) {
Properties props = new Properties();
InputStream in = null;
try {
in = new FileInputStream(filename);
props.load(in);
} catch (FileNotFoundException fileNotFoundException) {
throw new PackageRenamerException("Properties file was not found:" + CR + " '" + filename + "'");
} catch (IOException ioException) {
throw new PackageRenamerException("IO error occurred while reading the properties file:'" + filename + "'" + ioException.getMessage());
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
// ignore
}
}
}
logln("Using properties file: " + filename);
return props;
}
/**
* This run() method performs,
* reading the properties file into properties variable to be a reference for changing package name.
* creating an destination-root-direetory.
* and, calling traverseSourceDirectory() method.
*/
public void run() {
// Start loging.
logln("LOG MESSAGES FROM packageRenamer");
logln("" + new Date());
logln("");
logln("INPUT: -----------------> " + sourceRootDirFile.toString());
logln("OUTPUT: ----------------> " + destinationRootDir.toString());
// logln("PROPERTIES FILE: -------> "+sourceProperties);
// logln("LOG FILE: --------------> "+writerLog);
logln("");
// Root output directory.
logln("Verifying root output directory...");
if (!(destinationRootDir.exists())) {
// Create root directory for output first.
logln("");
logln("Creating root output directory...");
createDestinationDirectory(destinationRootDir);
logln("");
}
logln("Verifying root output directory...DONE");
logln("");
// Listing the changed file(s)
logln("List of changed file(s): ");
logln("");
traverseSourceDirectory(sourceRootDirFile);
logln("");
logln("Total Changed File(s): ------> " + numberOfChangedFile);
logln("Total File(s): ------> " + numberOfTotalFile);
logln("");
}
protected PrintWriter streamForNonExistentFilePrompt() {
System.out.print("Enter the absolute path of the log file [Hit Enter for SYSTEM.OUT]:" + CR + "> ");
String aLine = null;
try {
aLine = getReader().readLine();
} catch (IOException exception) {
throw new PackageRenamerException("Error while reading the name of the log file: " + exception.getMessage());
}
if ((aLine != null) && (aLine.length() == 0)) {
return buildAndCheckLogWriter(SYSTEM_OUT);
} else {
return buildAndCheckLogWriter(aLine);
}
}
/**
* This runSearchAndReplacePackageName() reads a pre-rename source file all
* into string variable and replacing the old package names with the new
* ones according to the properties file.
*
* @param sourceFile
* The source file to process
*/
public void runSearchAndReplacePackageName(java.io.File sourceFile) {
String stringContainAllFile = "";
String sourceFileName = sourceFile.toString();
String sourceFileNameWithoutRoot = sourceFile.toString().substring(sourceRootDirFile.toString().length() + 1);
// Rename the file name if required.
sourceFileNameWithoutRoot = returnNewFileNameIfRequired(sourceFileNameWithoutRoot);
String destinationFileName = destinationRootDir.toString() + File.separator + sourceFileNameWithoutRoot;
// Reading file into string.
// stringContainAllFile = readAllStringsFromFile(sourceFileName);
FileInputStream fis = null;
try {
fis = new FileInputStream(new java.io.File(sourceFileName));
byte[] buf = new byte[BUFSIZ];
StringBuilder strBuf = new StringBuilder((int)new java.io.File(sourceFileName).length());
int i = 0;
while ((i = fis.read(buf)) != -1) {
if (bufferContainsNullChar(buf, i)) {
// This is a binary file, just copy it byte by byte to the new location. Do not do any renaming.
fis.close();
binaryCopy(sourceFile, new File(destinationFileName));
return;
}
String str = new String(buf, 0, i);
strBuf.append(str);
}
fis.close();
stringContainAllFile = new String(strBuf);
} catch (IOException ioException) {
throw new PackageRenamerException("Unexpected exception was thrown during file manipulation." + ioException.getMessage());
} finally {
if (fis != null) {
try {
fis.close();
} catch (Exception e) {
// ignore
}
}
}
// Sorting key package name.
Vector<Object> aVector = new Vector<>();
for (Enumeration<Object> e = properties.keys(); e.hasMoreElements();) {
aVector.addElement(e.nextElement());
}
String[] aStringArrayOfSortedKeyPackageName = new String[aVector.size()];
aVector.copyInto(aStringArrayOfSortedKeyPackageName);
Arrays.sort(aStringArrayOfSortedKeyPackageName);
// Starting to rename.
boolean alreadyPrint = false;
int index = aStringArrayOfSortedKeyPackageName.length;
for (Enumeration<Object> enumtr = properties.keys(); enumtr.hasMoreElements();) {
enumtr.nextElement();
String key = aStringArrayOfSortedKeyPackageName[index - 1];
String value = (String)properties.get(key);
index -= 1;
// Printing the changed file.
int found = stringContainAllFile.indexOf(key);
if ((found != -1) && (!alreadyPrint)) {
alreadyPrint = true;
logln((numberOfChangedFile + 1) + ". " + destinationFileName);
numberOfChangedFile++;
}
// replacing the old package name.
stringContainAllFile = replace(stringContainAllFile, key, value);
}
// Writing output file.
try {
FileWriter writer = new FileWriter(destinationFileName);
java.io.PrintWriter out = new java.io.PrintWriter(writer);
out.print(stringContainAllFile);
out.close();
} catch (FileNotFoundException fileNotFoundException) {
throw new PackageRenamerException("Could not find file to write:" + CR + " '" + destinationFileName + "'" + CR + fileNotFoundException.getMessage());
} catch (IOException ioException) {
throw new PackageRenamerException("Unexpected exception was thrown while writing the file: '" + destinationFileName + "', " + ioException.getMessage());
}
}
/**
* Do a search and replace in a string.
*
* @param str
* The original String
* @param oldChars
* The character pattern to replace
* @param newChars
* The character pattern to replace the existing with
* @return the modified String
*/
public static String replace(String str, String oldChars, String newChars) {
int len;
int pos;
int lastPos;
len = newChars.length();
pos = str.indexOf(oldChars);
lastPos = pos;
while (pos > -1) {
String firstPart;
String lastPart;
firstPart = str.substring(0, pos);
lastPart = str.substring(pos + oldChars.length(), str.length());
str = firstPart + newChars + lastPart;
lastPos = pos + len;
pos = str.indexOf(oldChars, lastPos);
}
return (str);
}
/**
* Renames a file based on the properties passed.
*
* @param aSourceFileNameWithoutRoot
* The original filename
* @return The new filename, regardless of whether is has been changed
*/
public String returnNewFileNameIfRequired(String aSourceFileNameWithoutRoot) {
for (Enumeration<Object> enumtr = properties.keys(); enumtr.hasMoreElements();) {
String key = (String)enumtr.nextElement();
if (aSourceFileNameWithoutRoot.indexOf(key) != -1) {
// replacing the old package name.
aSourceFileNameWithoutRoot = replace(aSourceFileNameWithoutRoot, key, (String)properties.get(key));
}
}
// just simply return whether it has been changed.
return aSourceFileNameWithoutRoot;
}
/**
* This traverseSourceDirectory() traverse source-root-directory, creating
* an corresponding output directory, and calling another method for
* replacing old TopLink package name.
*
* @param aDirectoryString
* The source root directory to traverse
*/
public void traverseSourceDirectory(java.io.File aDirectoryString) {
Objects.requireNonNull(aDirectoryString);
java.io.File[] filesAndDirectories = aDirectoryString.listFiles();
if (filesAndDirectories == null) {
//no content, nothing to work with
return;
}
for (int i = 0; i < filesAndDirectories.length; i++) {
java.io.File fileOrDirectory = filesAndDirectories[i];
if (fileOrDirectory.isDirectory()) {
String sourceDirectoryName = fileOrDirectory.toString();
String destinationDirectoryName = destinationRootDir.toString() + sourceDirectoryName.substring(sourceRootDirFile.toString().length(), sourceDirectoryName.length());
createDestinationDirectory(new java.io.File(destinationDirectoryName));
traverseSourceDirectory(fileOrDirectory);
} else {
// it is a file.
numberOfTotalFile++;
// Check that it does not have an unsupported file extension
String fileExtension = parseFileExtension(fileOrDirectory);
if (isExtensionSupported(fileExtension)) {
runSearchAndReplacePackageName(fileOrDirectory);
}
}
}
}
public static void usage() {
System.out.println("");
System.out.println("TopLink Package Renamer");
System.out.println("-----------------------");
System.out.println("");
System.out.println("The package renamer should be run once on user source code, configuration");
System.out.println("files, and Mapping Workbench project files that have references to");
System.out.println("pre-Oracle 9iAS TopLink 9.0.3 API packages. The package renamer works on");
System.out.println("plain text files and should NOT be run on binary files such as JAR files.");
System.out.println("");
System.out.println("The package renamer supports two command line usages. A call which specifies");
System.out.println("all the required arguments, and a call which takes only one parameter. In");
System.out.println("this last case, the user is prompted for the missing arguments.");
System.out.println("");
System.out.println("Usage:");
System.out.println("");
System.out.println("java org.eclipse.persistence.tools.PackageRenamer <properties-file>");
System.out.println("");
System.out.println("OR");
System.out.println("");
System.out.println("java org.eclipse.persistence.tools.PackageRenamer <properties-file> <source-root-directory> <destination-root-directory> [ <log-file> ]");
System.out.println("");
System.out.println("where:");
System.out.println("\t<properties-file> - File containing a list of old and new package");
System.out.println("\tnames.");
System.out.println("");
System.out.println("\t<source-root-directory> - Absolute path name of the directory which");
System.out.println("\tcontains all the file to be converted. The <source-root-directory>");
System.out.println("\twill be searched recursively for files to convert. This directory");
System.out.println("\tshould contain only the plain text files to be converted.");
System.out.println("");
System.out.println("\t<destination-root-directory> - Absolute path name of the directory");
System.out.println("\twhere the converted directory structure will be copied. All files");
System.out.println("\twill be copied to the new directory structure whether changes were");
System.out.println("\tmade or not. This directory must either not exist or be empty.");
System.out.println("");
System.out.println("\t<log-file> - The logging of the renaming process will be written");
System.out.println("\tto the <log-file>. If no <log-file> is specified then logging");
System.out.println("\twill be written to standard output.");
System.out.println("");
}
// Made static final for performance reasons.
public static final class PackageRenamerException extends RuntimeException {
public PackageRenamerException(String aMessage) {
super(aMessage);
}
}
}