com.tangosol.dev.tools.CommandLineTool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of coherence Show documentation
Show all versions of coherence Show documentation
Oracle Coherence Community Edition
/*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.tangosol.dev.tools;
import com.tangosol.util.Base;
import com.tangosol.util.NullImplementation;
import com.tangosol.util.SimpleEnumerator;
import com.tangosol.util.StringTable;
import com.tangosol.util.ListMap;
import java.io.File;
import java.io.FileFilter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
/**
* A class on which to base command line tools.
*
* @author cp 07/22/98
*/
public abstract class CommandLineTool
extends Base
{
// ---- property accessors ----------------------------------------------
public String[] getArguments()
{
return m_asArg;
}
public void setArguments(String[] asArg)
{
m_asArg = asArg;
}
// ----- console support ------------------------------------------------
/**
* Get console input: Character.
*
* @param s the text to print
*
* @return the character pressed
*/
public static char in(String s)
{
if (s != null && s.length() > 0)
{
System.out.print(s);
System.out.flush();
}
InputStreamReader reader = new InputStreamReader(System.in);
int ch;
try
{
while ((ch = reader.read()) < 0)
{
}
}
catch (IOException e)
{
ch = 0x0;
}
return (char)ch;
}
/**
* Get console input: String.
*
* @param s the text to print
*
* @return the string entered (null if an IOException occurs)
*/
public static String inputString(String s)
{
if (s != null && s.length() > 0)
{
System.out.print(s);
System.out.flush();
}
StringBuffer sb = new StringBuffer();
try
{
InputStreamReader reader = new InputStreamReader(System.in);
do
{
sb.append(new BufferedReader(reader).readLine());
}
while (System.in.available() > 0);
return sb.toString();
}
catch (IOException e)
{
return null;
}
}
// ----- agrument handling ----------------------------------------------
/**
* Parses the array of arguments into a map.
*
* Assume that a java tool starts by command line having
* the following syntax:
* cmd-line ::== (command space)* (argument space)*
* command ::== "-" cmd-name ("=" | ":" | space) (cmd-value)?
* cmd-name ::== word
* cmd-value ::== word ("," word)*
* argument ::== word ("," word)*
* space ::== (" ")+
*
* When java starts an application the arguments in the command line
* are placed into a string array by breaking at spaces.
* The purpose of this method is to place the command line
* into a ListMap where each would represent
* an entry in this map with values equal to (null if not
* present) and each represented with an entry that has
* the key equal to an Integer object holding on the 0-based argument number
* and the value equal to the argument itself.
*
* @param asArg an array of arguments from "public static main(String[])"
* @param asCommand an array of valid commands (if null, anything is allowed)
* @param fCaseSens if true, uses the commands the way they are typed in;
* if false, converts all the commands to lowercase.
*
* @throws IllegalArgumentException if the syntax is unexpected or an invalid
* command has been encountered; a caller is supposed to output the
* "Usage: ... " message if this exception is thrown.
*/
public static ListMap parseArguments(String[] asArg, String[] asCommand, boolean fCaseSens)
{
ListMap map = new ListMap();
String sCommand = null;
int iArg = -1;
for (int i = 0; i < asArg.length; i++)
{
String sArg = asArg[i];
if (sArg.charAt(0) == '-')
{
// encountered a new command
if (sCommand != null)
{
// the previous command had no value
addArgToResultsMap(sCommand, null, map);
}
sCommand = sArg.substring(1);
if (sCommand.length() == 0)
{
throw new IllegalArgumentException("An empty command");
}
int of = sCommand.indexOf('=');
if (of < 0)
{
of = sCommand.indexOf(':');
}
if (of > 0)
{
String sValue = sCommand.substring(of + 1);
sCommand = validateCommand(sCommand.substring(0, of),
asCommand, fCaseSens);
addArgToResultsMap(sCommand, sValue, map);
sCommand = null;
}
else
{
sCommand = validateCommand(sCommand, asCommand, fCaseSens);
}
}
else
{
if (sCommand == null)
{
// encountered an argument
map.put(Integer.valueOf(++iArg), sArg);
}
else
{
// encountered an cmd-value
addArgToResultsMap(sCommand, sArg, map);
sCommand = null;
}
}
}
if (sCommand != null)
{
// the last arg was an command without a value
map.put(sCommand, null);
}
return map;
}
/**
* Add the provided argument and its value to the results map inflating the
* value to be a List of values if the map already contains the argument.
*
* @param sArg the parsed command line argument
* @param oValue the parsed value for the argument
* @param map the results map to add the argument and value to
*/
protected static void addArgToResultsMap(String sArg, Object oValue, Map map)
{
Object oValuePrev = map.get(sArg);
if (oValuePrev == null)
{
map.put(sArg, oValue);
return;
}
List listVals;
if (oValuePrev instanceof List)
{
listVals = (List) oValuePrev;
}
else
{
map.put(sArg, listVals = new LinkedList());
listVals.add(oValuePrev);
}
listVals.add(oValue);
}
/**
* @see #parseArguments(String[], String[], boolean)
*/
public static ListMap parseArguments(String[] asArg)
{
return parseArguments(asArg, null, false);
}
/**
* Search the supplied argument set for known switches, and extract them.
* Each switch which is found will be placed in the returned List and
* removed from the argument collection.
*
* @param colArg argument collection to parse, and remove switch from
* @param asValidSwitch switches to look for
*
* @return list of found switches
*/
public static List extractSwitches(Collection colArg,
String[] asValidSwitch)
{
List lResult = new LinkedList();
for (int i = 0, c = asValidSwitch.length; i < c; ++i)
{
String sSwitch = "-" + asValidSwitch[i];
for (Iterator iter = colArg.iterator(); iter.hasNext(); )
{
String sArg = (String) iter.next();
if (sArg.equals(sSwitch))
{
lResult.add(asValidSwitch[i]);
iter.remove();
}
}
}
return lResult;
}
/**
* Process a command from the command line arguments.
* This method is used to process required commands, and will throw
* an exception if the command was not specified.
*
* @param mapCommands the map of command line arguments
* @param oArg the argument to process
*
* @return the value
*
* @throws UnsupportedOperationException if a no value is available
*/
public static Object processCommand(Map mapCommands, Object oArg)
throws UnsupportedOperationException
{
Object value = mapCommands.get(oArg);
if (value == null)
{
if (oArg instanceof String)
{
throw new UnsupportedOperationException("-" + oArg
+ " must be specified.");
}
else
{
throw new UnsupportedOperationException("argument " + oArg
+ " must be specified.");
}
}
return value;
}
/**
* Process a command from the command line arguments.
* This method is used to process optional commands, and the
* default will be returned if command was not explicitly specified.
*
* @param mapCommands the map of command line arguments
* @param oArg the argument to process
* @param oDefault Specifies a default value
*
* @return the value, or oDefault if unspecified
*/
public static Object processCommand(Map mapCommands, Object oArg, Object oDefault)
throws UnsupportedOperationException
{
Object value = mapCommands.get(oArg);
return (value == null ? oDefault : value);
}
/**
* Process an optional command from the command line arguments, where the
* value is to be interpreted as an integer.
*
* @param mapCommands the map of command line arguments
* @param oArg the argument to process
* @param iDefault Specifies an default value
*
* @return the value, or iDefault if unspecified
*/
public static int processIntCommand(Map mapCommands, Object oArg, int iDefault)
throws UnsupportedOperationException
{
Object value = mapCommands.get(oArg);
return (value == null ? iDefault : Integer.parseInt((String) value));
}
/**
* Process a required command from the command line arguments, where the
* value is to be interpreted as an integer.
*
* @param mapCommands the map of command line arguments
* @param oArg the argument to process
*
* @return the value
* @throws UnsupportedOperationException if a no value is available
*/
public static int processIntCommand(Map mapCommands, Object oArg)
throws UnsupportedOperationException
{
Object value = processCommand(mapCommands, oArg);
return Integer.parseInt((String) value);
}
/**
* Process an optional command from the command line arguments, where the
* value is to be interpreted as an long.
*
* @param mapCommands the map of command line arguments
* @param oArg the argument to process
* @param lDefault Specifies an default value
*
* @return the value, or iDefault if unspecified
*/
public static long processLongCommand(Map mapCommands, Object oArg, long lDefault)
throws UnsupportedOperationException
{
Object value = mapCommands.get(oArg);
return (value == null ? lDefault : Long.parseLong((String) value));
}
/**
* Validate the command
*/
protected static String validateCommand(String sCommand, String[] asCommand, boolean fCaseSens)
{
if (!fCaseSens)
{
sCommand = sCommand.toLowerCase();
}
if (asCommand == null)
{
return sCommand;
}
for (int i = 0; i < asCommand.length; i++)
{
if (asCommand[i].equals(sCommand))
{
return sCommand;
}
}
throw new IllegalArgumentException("Illegal command: -" + sCommand);
}
/**
* Print to console: The passed arguments.
*/
public static void showArgs(String[] asArgs)
{
out();
int cArgs = asArgs.length;
out(cArgs + " command line arguments:");
for (int i = 0; i < cArgs; ++i)
{
out("[" + i + "]=\"" + asArgs[i] + "\"");
}
out();
}
// ----- filter support -------------------------------------------------
/**
* Select files based on a filter specification.
*
* @param sFileSpec a file specification using the wild-cards "?" and "*"
* @param fRecurse true if sub-directories should be searched
*
* @return an enumeration of the selected files
*/
public static Enumeration applyFilter(String sFileSpec, boolean fRecurse)
{
// determine what the file filter is:
// (1) the path or name of a file (e.g. "Replace.java")
// (2) a path (implied *.*)
// (3) a path and a filter (e.g. "\*.java")
// (4) a filter (e.g. "*.java")
String sFilter = "*";
String sDir = sFileSpec;
File dir;
// file spec may be a complete dir specification
dir = new File(sDir).getAbsoluteFile();
if (!dir.isDirectory())
{
// parse the file specification into a dir and a filter
int of = sFileSpec.lastIndexOf(File.separatorChar);
if (of < 0)
{
sDir = "";
sFilter = sFileSpec;
}
else
{
sDir = sFileSpec.substring(0, ++of);
sFilter = sFileSpec.substring(of);
}
// test the parsed directory name by itself
dir = new File(sDir).getAbsoluteFile();
if (!dir.isDirectory())
{
return null;
}
}
// check filter, and determine if it denotes
// (1) a specific file
// (2) all files
// (3) a subset (filtered set) of files
Stack stackDirs = new Stack();
FileFilter filter;
if (sFilter.length() < 1)
{
sFilter = "*";
}
if (sFilter.indexOf('*') < 0 && sFilter.indexOf('?') < 0)
{
if (fRecurse)
{
// even though we are looking for a specific file, we still
// have to recurse through sub-dirs
filter = new ExactFilter(stackDirs, fRecurse, sFilter);
}
else
{
File file = new File(dir, sFilter);
if (file.isFile() && file.exists())
{
return new SimpleEnumerator(new File[] {file});
}
else
{
return NullImplementation.getEnumeration();
}
}
}
else if (sFilter.equals("*"))
{
filter = new AllFilter(stackDirs, fRecurse);
}
else
{
filter = new PatternFilter(stackDirs, fRecurse, sFilter);
}
stackDirs.push(dir);
return applyFilter(stackDirs, filter);
}
/**
* Using the passed filter, enumerate all files that pass the filter in
* the set of directories which are in the passed stack.
*/
protected static Enumeration applyFilter(Stack stackDirs, FileFilter filter)
{
Vector vectFiles = new Vector();
while (!stackDirs.isEmpty())
{
File dir = (File) stackDirs.pop();
if (!dir.isDirectory())
{
throw new IllegalArgumentException("Illegal directory: \"" + dir.getPath() + "\"");
}
File[] aFiles = dir.listFiles(filter);
int cFiles = aFiles.length;
for (int i = 0; i < cFiles; ++i)
{
vectFiles.addElement(aFiles[i]);
}
}
return vectFiles.elements();
}
/**
* Select files from a ZIP/JAR based on a filter specification.
*
* A zip file contains directories and files. (Directories are optional.)
* The format for a directory name is "//"
* The format for a file name is "//"
*
* @param zip an open ZIP/JAR file
* @param sFileSpec a file specification using the wild-cards "?" and "*"
* @param fRecurse true if sub-directories should be searched
*
* @return an enumeration of the selected files
*/
public static Enumeration applyZipFilter(ZipFile zip, String sFileSpec, boolean fRecurse)
{
// determine what the file filter is:
// (1) the path or name of a file (e.g. "Replace.java")
// (2) a path (implied *.*)
// (3) a path and a filter (e.g. "/*.java")
// (4) a filter (e.g. "*.java")
// file spec may be a complete dir specification
String sFilter = "*";
String sDir = sFileSpec;
if (sDir.length() > 0 && sDir.charAt(0) == '/')
{
sDir = sDir.substring(1);
}
// we know it is a directory if it ends with "/" ... but if it
// does not, it could still be a directory
if (sDir.length() > 0 && sDir.charAt(sDir.length()-1) != '/')
{
ZipEntry file = zip.getEntry(sDir);
if (file == null)
{
ZipEntry dir = zip.getEntry(sDir + '/');
if (dir == null)
{
int ofSlash = sDir.lastIndexOf('/') + 1;
if (ofSlash > 0)
{
sFilter = sDir.substring(ofSlash);
sDir = sDir.substring(0, ofSlash);
}
else
{
// assume it is a file name
if (fRecurse)
{
// search for that file name in all dirs
sFilter = sDir;
sDir = "";
}
else
{
// file not found
return NullImplementation.getEnumeration();
}
}
}
else
{
sDir += '/';
}
}
else
{
// if a dir is specified as part of the name or if it is
// the root directory of the zip and directory recursion
// is not specified, then it is an exact file match (not
// a directory)
if (sDir.indexOf('/') >= 0 || !fRecurse)
{
return new SimpleEnumerator(new Object[] {file});
}
// otherwise look for that file in all directories
sFilter = sDir;
sDir = "";
}
}
// check filter, and determine if it denotes
// (1) all files
// (2) a subset (filtered set) of files
// (3) a specific file
ZipFilter filter;
if (sFilter.length() < 1 || sFilter.equals("*"))
{
filter = new ZipFilter(sDir, fRecurse);
}
else if (sFilter.indexOf('*') >= 0 || sFilter.indexOf('?') >= 0)
{
filter = new ZipPatternFilter(sDir, fRecurse, sFilter);
}
else
{
filter = new ZipExactFilter(sDir, fRecurse, sFilter);
}
// build an ordered list of the entries
StringTable tbl = new StringTable();
for (Enumeration enmr = zip.entries(); enmr.hasMoreElements(); )
{
ZipEntry entry = (ZipEntry) enmr.nextElement();
if (filter.accept(entry))
{
tbl.put(entry.getName(), entry);
}
}
return tbl.elements();
}
/**
* Tests whether or not the passed file name matches the
* specified wildcard pattern.
*
* @param sName the file name
* @param achPattern the file pattern with wildcards (?, *)
*
* @return true
if and only if sName
* matches the pattern in achPattern
*/
public static boolean matches(String sName, char[] achPattern)
{
return matches(sName.toCharArray(), 0, achPattern, 0);
}
/**
* Tests whether or not the passed file name matches the
* specified wildcard pattern.
*
* @param achName the file name
* @param ofName the offset within the name to match
* @param achPattern the file pattern with wildcards (?, *)
* @param ofPattern the offset within the pattern to match
*
* @return true
if and only if achName
* matches the pattern in achPattern
*/
private static boolean matches(char[] achName, int ofName, char[] achPattern, int ofPattern)
{
int cchName = achName.length;
int cchPattern = achPattern.length;
while (ofName < cchName && ofPattern < cchPattern)
{
char chName = achName[ofName];
char chPattern = achPattern[ofPattern];
// latin conversion
if (chName >= 'A' && chName <= 'Z')
{
chName = (char) ('a' + (chName - 'A'));
}
if (chPattern >= 'A' && chPattern <= 'Z')
{
chPattern = (char) ('a' + (chPattern - 'A'));
}
switch (chPattern)
{
case '?':
// any char is OK
break;
case '*':
{
if (ofPattern == cchPattern-1)
{
// asterisk is at end of pattern ... any name
// goes at this point
return true;
}
char chTag = achPattern[ofPattern+1];
for (int ofTag = ofName; ofTag < cchName; ++ofTag)
{
if ((chTag == '?' || achName[ofTag] == chTag) &&
matches(achName, ofTag, achPattern, ofPattern+1))
{
return true;
}
}
}
return false;
default:
if (chName != chPattern)
{
return false;
}
break;
}
++ofName;
++ofPattern;
}
// equal iff both name and pattern were exhausted
return (ofName == cchName && ofPattern == cchPattern);
}
// ----- File filters ---------------------------------------------------
/**
* File filter which implements "*" (all files are accepted).
*/
public static class AllFilter implements FileFilter
{
protected Stack m_stackDirs;
protected boolean m_fPushDirs;
/**
* Construct filter.
*
* @param stackDirs a stack of directories encountered
* @param fPushDirs if true, this filter must push all
* encountered directories onto stackDirs
*/
public AllFilter(Stack stackDirs, boolean fPushDirs)
{
m_stackDirs = stackDirs;
m_fPushDirs = fPushDirs;
}
/**
* Tests whether or not the specified abstract path should be
* included in a path list.
*
* @param path the path to be tested
* @return true
if and only if path
* should be included
*/
public boolean accept(File path)
{
if (path.isDirectory())
{
if (m_fPushDirs)
{
m_stackDirs.push(path);
}
return false;
}
else
{
return true;
}
}
}
/**
* File filter which accepts files that match a pattern.
*/
public static class PatternFilter implements FileFilter
{
protected Stack m_stackDirs;
protected boolean m_fPushDirs;
protected char[] m_achPattern;
/**
* Construct filter.
*
* @param stackDirs a stack of directories encountered
* @param fPushDirs if true, this filter must push all
* encountered directories onto stackDirs
* @param sPattern the pattern (wildcards: *, ?)
*/
public PatternFilter(Stack stackDirs, boolean fPushDirs, String sPattern)
{
m_stackDirs = stackDirs;
m_fPushDirs = fPushDirs;
m_achPattern = sPattern.toCharArray();
}
/**
* Tests whether or not the specified abstract path should be
* included in a path list.
*
* @param path the path to be tested
* @return true
if and only if path
* should be included
*/
public boolean accept(File path)
{
if (path.isDirectory())
{
if (m_fPushDirs)
{
m_stackDirs.push(path);
}
return false;
}
else
{
return CommandLineTool.matches(path.getName(), m_achPattern);
}
}
}
/**
* File filter which accepts a file of an exact name.
*/
public static class ExactFilter implements FileFilter
{
protected Stack m_stackDirs;
protected boolean m_fPushDirs;
protected String m_sFile;
/**
* Construct filter.
*
* @param stackDirs a stack of directories encountered
* @param fPushDirs if true, this filter must push all
* encountered directories onto stackDirs
* @param sFile the name of the file being searched for
*/
public ExactFilter(Stack stackDirs, boolean fPushDirs, String sFile)
{
m_stackDirs = stackDirs;
m_fPushDirs = fPushDirs;
m_sFile = sFile;
}
/**
* Tests whether or not the specified abstract path should be
* included in a path list.
*
* @param path the path to be tested
* @return true
if and only if path
* should be included
*/
public boolean accept(File path)
{
if (path.isDirectory())
{
if (m_fPushDirs)
{
m_stackDirs.push(path);
}
return false;
}
else
{
return m_sFile.equalsIgnoreCase(path.getName());
}
}
}
// ----- ZipEntry filters -----------------------------------------------
/**
* A "FileFilter"-like class but for ZipEntry objects. All zip filters
* must derive from this class.
*/
public static class ZipFilter
{
/**
* The directory name to filter from (or 0-length to filter from the
* root).
*/
protected String m_sDir;
/**
* True if searching is recursive down the directory tree.
*/
protected boolean m_fRecurse;
/**
* Construct a ZipFilter.
*
* @param sDir the directory name to filter from (or 0-length to
* filter from the root)
* @param fRecurse true if searching is recursive down the directory
* tree
*/
public ZipFilter(String sDir, boolean fRecurse)
{
if (sDir.length() > 0 && sDir.charAt(sDir.length() - 1) != '/')
{
throw new IllegalStateException("ZipFilter: \"" + sDir + "\" is not a directory!");
}
m_sDir = sDir;
m_fRecurse = fRecurse;
}
/**
* Test a ZipEntry to see if it meets the filter criteria.
*
* @param entry the zip entry to test
*
* @return true if the entry meets the filter criteria
*/
public boolean accept(ZipEntry entry)
{
// the entry name must start with the directory name, and it must
// not be a directory
String sName = entry.getName();
if (sName.length() > m_sDir.length() && sName.startsWith(m_sDir)
&& sName.charAt(sName.length() - 1) != '/')
{
// if searching recursively, the entry can be several levels
// under the directory, otherwise it must be in that directory
return m_fRecurse || sName.indexOf('/', m_sDir.length()) < 0;
}
return false;
}
}
/**
* Zip filter which accepts files that match a pattern.
*/
public static class ZipPatternFilter extends ZipFilter
{
protected char[] m_achPattern;
/**
* Construct a ZipPatternFilter.
*
* @param sDir the directory name to filter from (or 0-length to
* filter from the root)
* @param fRecurse true if searching is recursive down the directory
* tree
* @param sPattern the pattern (wildcards: *, ?)
*/
public ZipPatternFilter(String sDir, boolean fRecurse, String sPattern)
{
super(sDir, fRecurse);
m_achPattern = sPattern.toCharArray();
}
/**
* Test a ZipEntry to see if it meets the filter criteria.
*
* @param entry the zip entry to test
*
* @return true if the entry meets the filter criteria
*/
public boolean accept(ZipEntry entry)
{
return super.accept(entry)
&& CommandLineTool.matches(entry.getName(), m_achPattern);
}
}
/**
* Zip filter which accepts an entry with an exact name.
*/
public static class ZipExactFilter extends ZipFilter
{
/**
* The file name to match.
*/
protected String m_sFile;
/**
* Construct a ZipExactFilter.
*
* @param sDir the directory name to filter from (or 0-length to
* filter from the root)
* @param fRecurse true if searching is recursive down the directory
* tree
* @param sFile the file pattern (wildcards: *, ?)
*/
public ZipExactFilter(String sDir, boolean fRecurse, String sFile)
{
super(sDir, fRecurse);
m_sFile = sFile;
}
/**
* Test a ZipEntry to see if it meets the filter criteria.
*
* @param entry the zip entry to test
*
* @return true if the entry meets the filter criteria
*/
public boolean accept(ZipEntry entry)
{
if (super.accept(entry))
{
String sName = entry.getName();
return m_sFile.equals(sName.substring(sName.lastIndexOf('/') + 1));
}
return false;
}
}
// ---- data members ----------------------------------------------------
private String[] m_asArg;
}