/* * Copyright (c) 2000-2001 Sosnoski Software Solutions, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ import java.io.*; import java.util.*; /** * Source configuration processor program. This program processes a set of Java * source files (or standard input to standard output, if no source files are * specified), scanning for configuration control lines. A control line is a * line that starts with "//#token*" (valid only as the first line of a * file), "//#token{" (beginning an option block), "//#}token{" (inverting an * option block), or "//#token} (closing an option block). The first two formats * also allow a '!' immediately before the token in order to invert the token * state.
* * The token strings to be processed are specified on the command line as * either enabled or disabled. See the program documentation for more details * of the command line options and usage.
* * Nested option blocks are allowed, but overlapping option blocks are an * error. In order to ensure proper processing of nested option blocks, the * user should generally specify every token used for the nested blocks * as either enabled or disabled if any of them are either enabled or * disabled. It is an error if an indeterminant beginning of block token (one * with a token which is not on either list) is immediately contained within * a block with an enabled token. In other words, the case: *
* //#a{ * //#b{ * //#c{ * //#c} * //#b} * //#a} ** gives an error if "a" is on the enabled list and "b" is not on either list, * or if "a" is not on the disabled list, "b" is on the enabled list, and "c" * is not on either list. * * @author Dennis M. Sosnoski * @version 0.8 */ public class JEnable { /** Lead text for option token. */ protected static final String OPTION_LEAD = "//#"; /** Length of lead text for option token. */ protected static final int LEAD_LENGTH = OPTION_LEAD.length(); /** Size of buffer used to copy file. */ protected static final int COPY_BUFFER_SIZE = 4096; /** Return code for not an option line. */ protected static final int NOT_OPTION = 0; /** Return code for file option line. */ protected static final int FILE_OPTION = 1; /** Return code for block start option line. */ protected static final int BLOCK_START_OPTION = 2; /** Return code for block else option line. */ protected static final int BLOCK_ELSE_OPTION = 3; /** Return code for block end option line. */ protected static final int BLOCK_END_OPTION = 4; /** Return code for block comment option line. */ protected static final int BLOCK_COMMENT_OPTION = 5; /** Error text for file option not on first line. */ protected static final String FILE_OPTION_ERR = "file option token must be first line of file"; /** Error text for token nested within block for same token. */ protected static final String NESTED_SAME_ERR = "block option start cannot be nested within block with same token"; /** Error text for indeterminant token nested in enabled block. */ protected static final String INDETERM_ERR = "block option token within enabled block must be enabled or disabled"; /** Error text for block end token not matching block start token. */ protected static final String UNBALANCED_ERR = "block option end without matching start"; /** Error text for block else token not matching block start token. */ protected static final String BADELSE_ERR = "block option else without matching start"; /** Error text for end of file with open block. */ protected static final String UNCLOSED_ERR = "end of file with open block"; /** Error text for unknown option token type. */ protected static final String UNKNOWN_OPTION_ERR = "unknown option line type"; /** Error text for file option line with unknown file extension. */ protected static final String EXTENSION_ERR = "unknown file option line extension"; /** Error text for unable to rename file to backup directory. */ protected static final String BACKUP_DIR_ERR = "unable to create backup directory"; /** Error text for unable to delete old backup file. */ protected static final String OLD_BACKUP_ERR = "unable to delete old backup file"; /** Error text for unable to rename file within directory. */ protected static final String BACKUP_FILE_ERR = "unable to rename file for backup"; /** Error text for unable to delete original file. */ protected static final String DELETE_ERR = "unable to delete input file"; /** Error text for unable to rename original file. */ protected static final String RENAME_ERR = "unable to rename source file"; /** Error text for unable to rename temp file. */ protected static final String TEMP_RENAME_ERR = "unable to rename temp file"; /** Error text for unable to delete temporary file. */ protected static final String TEMP_DELETE_ERR = "unable to delete temporary output file"; /** Error text for unable to change file modify timestamp. */ protected static final String STAMP_ERR = "unable to change file modify timestamp"; /** Error text for token in both sets. */ protected static final String DUAL_USE_ERR = "Same token cannot be both enabled and disabled"; /** Preserve timestamp on modified files flag. */ protected boolean m_keepStamp; /** Mark backup file with tilde flag. */ protected boolean m_markBackup; /** List modified files flag. */ protected boolean m_listModified; /** List all files processed flag. */ protected boolean m_listProcessed; /** List file summary by path flag. */ protected boolean m_listSummary; /** Backup root path (null if not backing up). */ protected File m_backupDir; /** Map for enabled tokens (values same as keys). */ protected Hashtable m_enabledTokens; /** Map for disabled tokens (values same as keys). */ protected Hashtable m_disabledTokens; /** Number of files matched. */ protected int m_matchedCount; /** Number of files modified. */ protected int m_modifiedCount; /** Current option token, set by check method. */ private String m_token; /** Inverted token flag, set by check method. */ private boolean m_invert; /** Offset past end of token in line, set by check method. */ private int m_endOffset; /** * Constructor. * * @param keep preserve timestamp on modified files flag * @param mark mark backup files with tilde flag * @param mods list modified files flag * @param quiet do not list file summaries by path flag * @param verbose list all files processed flag * @param backup root back for backup directory tree (
null
if
* no backups)
* @param enabled map of enabled tokens
* @param disabled map of disabled tokens
*/
protected JEnable(boolean keep, boolean mark, boolean mods, boolean quiet,
boolean verbose, File backup, Hashtable enabled, Hashtable disabled) {
m_keepStamp = keep;
m_markBackup = mark;
m_listModified = mods;
m_listProcessed = verbose;
m_listSummary = !quiet;
m_backupDir = backup;
m_enabledTokens = enabled;
m_disabledTokens = disabled;
}
/**
* Checks for valid first character of token. The first character must be
* an alpha or underscore.
*
* @param chr character to be validated
* @return true
if valid first character, false
* if not
*/
protected static boolean isTokenLeadChar(char chr) {
return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') ||
chr == '_';
}
/**
* Checks for valid body character of token. All body characters must be
* an alpha, digits, or underscore.
*
* @param chr character to be validated
* @return true
if valid body character, false
* if not
*/
protected static boolean isTokenBodyChar(char chr) {
return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') ||
chr == '_' || (chr >= '0' && chr <= '9');
}
/**
* Convenience method for generating an error report exception.
*
* @param lnum line number within file
* @param line source line to check for option
* @param msg error message text
* @throws IOException wrapping the error information
*/
protected void throwError(int lnum, String line, String msg)
throws IOException {
throw new IOException("Error on input line " + lnum + ", " +
msg + ":\n" + line);
}
/**
* Check if line is an option. Returns a code for the option line type
* (or "not an option line"), with the actual token from the line stored
* in the instance variable. Ugly technique, but the only easy way to
* return multiple results without using another class.
*
* @param lnum line number within file (used for error reporting)
* @param line source line to check for option
* @return return code for option line type
* @throws IOException on option line error
*/
protected int checkOptionLine(int lnum, String line) throws IOException {
// check if line is a candidate for option token
int type = NOT_OPTION;
m_token = null;
m_invert = false;
m_endOffset = 0;
if (line.length() > LEAD_LENGTH && line.startsWith(OPTION_LEAD)) {
// check for special leading character before token
int offset = LEAD_LENGTH;
char lead = line.charAt(offset);
if (lead == '!' || lead == '}') {
offset++;
} else {
lead = 0;
}
// make sure a valid token start character follows
int start = offset;
if (isTokenLeadChar(line.charAt(start))) {
// parse the token characters
int scan = LEAD_LENGTH+1;
while (scan < line.length()) {
char chr = line.charAt(scan++);
if (!isTokenBodyChar(chr)) {
// token found, classify and set type
m_token = line.substring(start, scan-1);
m_endOffset = scan;
switch (chr) {
case '*':
// file option, inverted token is only variation
type = FILE_OPTION;
if (lead == '!') {
m_invert = true;
} else if (lead != 0) {
throwError(lnum, line, UNKNOWN_OPTION_ERR);
}
break;
case '{':
// block start option, check variations
if (lead == '}') {
type = BLOCK_ELSE_OPTION;
} else {
if (lead == '!') {
m_invert = true;
}
type = BLOCK_START_OPTION;
}
break;
case '}':
// block end option, no variations allowed
type = BLOCK_END_OPTION;
if (lead != 0) {
throwError(lnum, line, UNKNOWN_OPTION_ERR);
}
break;
case ':':
// block comment option, no variations allowed
type = BLOCK_COMMENT_OPTION;
if (lead != 0) {
throwError(lnum, line, UNKNOWN_OPTION_ERR);
}
break;
default:
// no idea what this is supposed to be
throwError(lnum, line, UNKNOWN_OPTION_ERR);
break;
}
break;
}
}
}
}
return type;
}
/**
* Processes source options for a text stream. If an error occurs in
* processing, this generates an IOException
with the error
* information (including input line number).
*
* @param in input reader for source data
* @param lead first line of file (previously read for checking file enable
* or disable)
* @param out output writer for modified source data
* @return true
if source modified, false
if not
*/
protected boolean processStream(BufferedReader in, String lead,
BufferedWriter out) throws IOException {
// initialize state information
Stack enables = new Stack();
Stack nests = new Stack();
String disable = null;
boolean changed = false;
// basic file line copy loop
String line = lead;
int lnum = 1;
while (line != null) {
// process based on option type
boolean option = true;
int type = checkOptionLine(lnum, line);
switch (type) {
case NOT_OPTION:
option = false;
break;
case FILE_OPTION:
// file option processed outside, but must be first line
if (lnum > 1) {
throwError(lnum, line, FILE_OPTION_ERR);
}
break;
case BLOCK_START_OPTION:
// option block start token, must not be duplicated
if (nests.indexOf(m_token) >= 0) {
throwError(lnum, line, NESTED_SAME_ERR);
} else {
// push to nesting stack and check if we're handling
nests.push(m_token);
if (disable == null) {
// see if we know about this token
boolean on = m_enabledTokens.containsKey(m_token);
boolean off = m_disabledTokens.containsKey(m_token);
// swap flags if inverted token
if (m_invert) {
boolean hold = on;
on = off;
off = hold;
}
// handle start of block
if (off) {
// set disabling option
disable = m_token;
} else if (on) {
// stack enabled option
enables.push(m_token);
} else if (!enables.empty()) {
// error if unknown inside enable
throwError(lnum, line, INDETERM_ERR);
}
}
}
break;
case BLOCK_ELSE_OPTION:
// option block else, must match top of nesting stack
if (nests.empty() || !nests.peek().equals(m_token)) {
throwError(lnum, line, BADELSE_ERR);
} else {
// reverse current state, if known
if (disable == null) {
// enabled state, check if top of stack
if (!enables.empty()) {
if (enables.peek().equals(m_token)) {
// flip to disable state
enables.pop();
disable = m_token;
}
}
} else if (disable.equals(m_token)) {
// flip to enable state
disable = null;
enables.push(m_token);
}
}
break;
case BLOCK_END_OPTION:
// option block end, must match top of nesting stack
if (nests.empty() || !nests.peek().equals(m_token)) {
throwError(lnum, line, UNBALANCED_ERR);
} else {
// remove from nesting stack and check state
nests.pop();
if (disable == null) {
// enabled state, check if top of stack
if (!enables.empty()) {
if (enables.peek().equals(m_token)) {
enables.pop();
}
}
} else if (disable.equals(m_token)) {
disable = null;
}
}
break;
case BLOCK_COMMENT_OPTION:
// disabled line option, check if clearing
if ((disable != null && !disable.equals(m_token)) ||
(disable == null && enables.contains(m_token))) {
// clear disabled line option
line = line.substring(m_endOffset);
option = false;
changed = true;
}
break;
default:
throwError(lnum, line, UNKNOWN_OPTION_ERR);
}
// check for disabling lines
if (!option && disable != null) {
// change line to disabled state
line = OPTION_LEAD + disable + ':' + line;
changed = true;
}
// write (possibly modified) line to output
out.write(line);
out.newLine();
// read next line of input
line = in.readLine();
lnum++;
}
// check for valid end state
if (nests.size() > 0) {
throwError(lnum, (String)nests.pop(), UNCLOSED_ERR);
}
out.flush();
return changed;
}
/**
* Processes source options for a file. Starts by checking the first line
* of the file for a file option and processing that. If, after processing
* the file option, the file has a ".java" extension, it is processed for
* other option lines.* * This saves the output to a temporary file, then if processing is * completed successfully first renames or moves the original file (if * backup has been requested), or deletes it (if backup not requested), * and then renames the temporary file to the original file name.
*
* Processing errors are printed to System.err
, and any
* results are discarded without being saved.
*
* @param file source file to be processed
* @return true
if source modified, false
if not
*/
protected boolean processFile(File file) {
File temp = null;
try {
// set up basic information
String name = file.getName();
String dir = file.getParent();
int split = name.lastIndexOf('.');
String ext = (split >= 0) ? name.substring(split+1) : "";
String toext = ext;
long stamp = m_keepStamp ? file.lastModified() : 0;
File target = file;
// check first line for file option
BufferedReader in = new BufferedReader(new FileReader(file));
String line = in.readLine();
int type = checkOptionLine(1, line);
if (type == FILE_OPTION) {
// make sure we have one of the extensions we know about
if (ext.equals("java") || ext.equals("javx")) {
// set "to" extension based on option setting
if (m_enabledTokens.contains(m_token)) {
toext = m_invert ? "javx" : "java";
} else if (m_disabledTokens.contains(m_token)) {
toext = m_invert ? "java" : "javx";
}
// generate new target file name if different extension
if (!toext.equals(ext)) {
split = name.indexOf('.');
name = name.substring(0, split) + '.' + toext;
target = new File(dir, name);
}
} else {
throw new IOException(EXTENSION_ERR);
}
}
// check if extension valid for processing
boolean changed = false;
if (!toext.equals("javx")) {
// set up output to temporary file in same directory
temp = File.createTempFile("sop", null, file.getParentFile());
BufferedWriter out = new BufferedWriter(new FileWriter(temp));
// process the file for changes
changed = processStream(in, line, out);
in.close();
out.close();
if (changed) {
// handle backup of original file
if (m_backupDir != null) {
// construct path within backup directory
String extra = file.getCanonicalPath();
int mark = extra.indexOf(File.separatorChar);
if (mark >= 0) {
extra = extra.substring(mark+1);
}
File backup = new File(m_backupDir, extra);
// copy file to backup directory
File backdir = backup.getParentFile();
if (!backdir.exists() && !backdir.mkdirs()) {
throw new IOException(BACKUP_DIR_ERR + '\n' +
backdir.getPath());
}
if (backup.exists()) {
if (!backup.delete()) {
throw new IOException(OLD_BACKUP_ERR);
}
}
byte[] buff = new byte[COPY_BUFFER_SIZE];
InputStream is = new FileInputStream(file);
OutputStream os = new FileOutputStream(backup);
int bytes;
while ((bytes = is.read(buff)) >= 0) {
os.write(buff, 0, bytes);
}
is.close();
os.close();
backup.setLastModified(file.lastModified());
}
if (m_markBackup) {
// suffix file name with tilde
File backup = new File(dir, name+'~');
if (backup.exists()) {
if (!backup.delete()) {
throw new IOException(OLD_BACKUP_ERR);
}
}
if (!file.renameTo(backup)) {
throw new IOException(BACKUP_FILE_ERR);
}
} else {
// just delete the original file
if (!file.delete()) {
throw new IOException(DELETE_ERR);
}
}
// rename temp to target name
if (temp.renameTo(target)) {
if (m_keepStamp && !target.setLastModified(stamp)) {
throw new IOException(STAMP_ERR);
}
} else {
throw new IOException(TEMP_RENAME_ERR);
}
} else {
// just discard the temporary output file
if (!temp.delete()) {
throw new IOException(TEMP_DELETE_ERR);
}
// check if file needs to be renamed
if (!toext.equals(ext)) {
// just rename file for file option result
if (file.renameTo(target)) {
changed = true;
if (m_keepStamp && !target.setLastModified(stamp)) {
throw new IOException(STAMP_ERR);
}
} else {
throw new IOException(RENAME_ERR);
}
}
}
} else if (!toext.equals(ext)) {
// just rename file for file option result
in.close();
if (file.renameTo(target)) {
changed = true;
if (m_keepStamp && !target.setLastModified(stamp)) {
throw new IOException(STAMP_ERR);
}
} else {
throw new IOException(RENAME_ERR);
}
}
// check file listing
if (changed && (m_listProcessed || m_listModified)) {
System.out.println(" modified file " + file.getPath());
} else if (m_listProcessed) {
System.out.println(" checked file " + file.getPath());
}
return changed;
} catch (Exception ex) {
// report error
System.err.println("Error processing " + file.getPath());
System.err.println(ex.getMessage());
// discard temporary output file
if (temp != null) {
try {
temp.delete();
} catch (Exception ex2) {}
}
return false;
}
}
/**
* Checks if file or directory name directly matches a pattern. This
* method accepts one or more '*' wildcard characters in the pattern,
* calling itself recursively in order to handle multiple wildcards.
*
* @param name file or directory name
* @param pattern match pattern
* @return true
if any pattern matched, false
* if not
*/
protected boolean isPathMatch(String name, String pattern) {
// check special match cases first
if (pattern.length() == 0) {
return name.length() == 0;
} else if (pattern.charAt(0) == '*') {
// check if the wildcard is all that's left of pattern
if (pattern.length() == 1) {
return true;
} else {
// check if another wildcard follows next segment of text
pattern = pattern.substring(1);
int split = pattern.indexOf('*');
if (split > 0) {
// recurse on each match to text segment
String piece = pattern.substring(0, split);
pattern = pattern.substring(split);
int offset = -1;
while ((offset = name.indexOf(piece, ++offset)) > 0) {
int end = offset + piece.length();
if (isPathMatch(name.substring(end), pattern)) {
return true;
}
}
} else {
// no more wildcards, need exact match to end of name
return name.endsWith(pattern);
}
}
} else {
// check for leading text before first wildcard
int split = pattern.indexOf('*');
if (split > 0) {
// match leading text to start of name
String piece = pattern.substring(0, split);
if (name.startsWith(piece)) {
return isPathMatch(name.substring(split),
pattern.substring(split));
} else {
return false;
}
} else {
// no wildcards, need exact match
return name.equals(pattern);
}
}
return false;
}
/**
* Checks if file name matches a pattern. This works a little differently
* from the general path matching in that if the pattern does not include
* an extension both ".java" and ".javx" file extensions are matched. If
* the pattern includes an extension ending in '*' it is blocked from
* matching with a tilde final character in the file name as a special case.
*
* @param name file or directory name
* @param pattern match pattern
* @return true
if any file modified, false
if not
*/
protected boolean isNameMatch(String name, String pattern) {
// check for extension included in pattern
if (pattern.indexOf('.') >= 0) {
// pattern includes extension, use as is except for tilde endings
if (name.charAt(name.length()-1) != '~' ||
pattern.charAt(pattern.length()-1) == '~') {
return isPathMatch(name, pattern);
}
} else {
// split extension from file name
int split = name.lastIndexOf('.');
if (split >= 0) {
// check for valid extension with match on name
String ext = name.substring(split+1);
if (ext.equals("java") || ext.equals("javx")) {
return isPathMatch(name.substring(0, split), pattern);
}
}
}
return false;
}
/**
* Process files matching path segment. This method matches a single step
* (directory specification) in a path for each call, calling itself
* recursively to match the complete path.
*
* @param base base directory for path match
* @param path file path remaining to be processed
*/
protected void matchPathSegment(File base, String path) {
// find break for leading directory if any in path
File[] files = base.listFiles();
int split = path.indexOf('/');
if (split >= 0) {
// split off the directory and check it
String dir = path.substring(0, split);
String next = path.substring(split+1);
if (dir.equals("**")) {
// match directly against files in this directory
matchPathSegment(base, next);
// walk all directories in tree under this one
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
matchPathSegment(files[i], path);
}
}
} else {
// try for concrete match to directory
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
if (isPathMatch(files[i].getName(), dir)) {
matchPathSegment(files[i], next);
}
}
}
}
} else {
// match directly against files in this directory
for (int i = 0; i < files.length; i++) {
if (!files[i].isDirectory()) {
if (isNameMatch(files[i].getName(), path)) {
m_matchedCount++;
if (processFile(files[i])) {
m_modifiedCount++;
}
}
}
}
}
}
/**
* Process all files matching path and print summary. The file path
* format is similar to Ant, supporting arbitrary directory recursion using
* '**' separators between '/' separators. Single '*'s may be used within
* names for wildcarding, but aside from the special case of the directory
* recursion matcher only one '*' may be used per name.
*
* If an extension is
* not specified for the final name in the path both ".java" and ".javx"
* extensions are checked, but after checking for file option lines (which
* may change the file extension) only ".java" extensions are processed for
* other source options.
*
* @param path file path to be processed
*/
protected void processPath(String path) {
// make sure we have something to process
if (path.length() > 0) {
// begin matching from root or current directory
if (path.charAt(0) == '/') {
matchPathSegment(new File(File.separator), path.substring(1));
} else {
matchPathSegment(new File("."), path);
}
// print summary information for path
if (m_listSummary) {
System.out.println(" matched " + m_matchedCount +
" files and modified " + m_modifiedCount +
" for path: " + path);
m_matchedCount = 0;
m_modifiedCount = 0;
}
}
}
/**
* Parse comma-separated token list. Parses and validates the tokens,
* adding them to the supplied list. errors are signalled by throwing
* IllegalArgumentException
.
*
* @param list comma-separated token list to be parsed
* @param tokens list of tokens to add to
* @throws IllegalArgumentException on error in supplied list
*/
protected static void parseTokenList(String list, Vector tokens) {
// accumulate comma-delimited tokens from list
while (list.length() > 0) {
// find end for next token
int mark = list.indexOf(',');
String token;
if (mark == 0) {
throw new IllegalArgumentException("Empty token not " +
"allowed: \"" + list + '"');
} else if (mark > 0) {
// split token off list
token = list.substring(0, mark);
list = list.substring(mark+1);
} else {
// use rest of list as final token
token = list;
list = "";
}
// validate the token
for (int i = 0; i < token.length(); i++) {
char chr = token.charAt(i);
if ((i == 0 && !isTokenLeadChar(chr)) ||
(i > 0 && !isTokenBodyChar(chr))) {
throw new IllegalArgumentException("Illegal " +
"character in token: \"" + token + '"');
}
}
// add validated token to list
tokens.add(token);
}
}
/**
* Test driver, just reads the input parameters and executes the source
* checks.
*
* @param argv command line arguments
*/
public static void main(String[] argv) {
if (argv.length > 0) {
// parse the leading command line parameters
boolean valid = true;
boolean keep = false;
boolean listmod = false;
boolean quiet = false;
boolean tilde = false;
boolean verbose = false;
File backup = null;
Vector enables = new Vector();
Vector disables = new Vector();
int anum = 0;
while (anum < argv.length && argv[anum].charAt(0) == '-') {
String arg = argv[anum++];
int cnum = 1;
while (cnum < arg.length()) {
char option = Character.toLowerCase(arg.charAt(cnum++));
switch (option) {
case 'b':
if (anum < argv.length) {
try {
backup = new File(argv[anum++]);
if (!backup.isDirectory()) {
System.err.println("Backup directory " +
"path must be a directory");
}
} catch (SecurityException ex) {
System.err.println("Unable to access " +
"backup directory");
}
} else {
System.err.println("Missing directory path " +
"for -b option");
}
break;
case 'd':
case 'e':
if (anum < argv.length) {
// accumulate comma-delimited tokens from list
Vector tokens = (option == 'd') ?
disables : enables;
try {
parseTokenList(argv[anum++], tokens);
} catch (IllegalArgumentException ex) {
System.err.println(ex.getMessage());
return;
}
if (option == 'd') {
disables = tokens;
} else {
enables = tokens;
}
} else {
System.err.println("Missing token list for -" +
option + " option");
return;
}
break;
case 'p':
keep = true;
break;
case 'q':
quiet = true;
break;
case 'm':
listmod = true;
break;
case 't':
tilde = true;
break;
case 'v':
verbose = true;
break;
default:
System.err.println("Unknown option -" + option);
return;
}
}
}
// build hashsets of the tokens, checking for overlap
Hashtable enabled = new Hashtable();
for (int i = 0; i < enables.size(); i++) {
Object token = enables.elementAt(i);
enabled.put(token, token);
}
Hashtable disabled = new Hashtable();
for (int i = 0; i < disables.size(); i++) {
Object token = disables.elementAt(i);
disabled.put(token, token);
if (enabled.containsKey(token)) {
System.err.println(DUAL_USE_ERR + ": " + token);
return;
}
}
// construct an instance of class
JEnable opt = new JEnable(keep, tilde, listmod, quiet, verbose,
backup, enabled, disabled);
// check if we have file paths
if (anum < argv.length) {
// process each file path supplied
while (anum < argv.length) {
String arg = argv[anum++];
int split;
while ((split = arg.indexOf(',')) > 0) {
String path = arg.substring(0, split);
opt.processPath(path);
arg = arg.substring(split+1);
}
opt.processPath(arg);
}
} else {
// just process standard input to standard output
BufferedReader in = new BufferedReader
(new InputStreamReader(System.in));
BufferedWriter out = new BufferedWriter
(new OutputStreamWriter(System.out));
// check first line for disabled token
try {
String line = in.readLine();
int type = opt.checkOptionLine(1, line);
if (type == FILE_OPTION) {
boolean discard = opt.m_invert ?
enabled.contains(opt.m_token) :
disabled.contains(opt.m_token);
if (discard) {
return;
}
}
opt.processStream(in, line, out);
} catch (IOException ex) {
System.err.println(ex.getMessage());
}
}
} else {
System.err.println
("\nJEnable Java source configuration processor version " +
"0.8\nUsage: JEnable [-options] path-list\n" +
"Options are:\n" +
" -b backup directory tree (base directory is next " +
"argument)\n" +
" -d disabled token list (comma-separated token name list" +
" is next argument)\n" +
" -e enabled token list (comma-separated token name list" +
" is next argument)\n" +
" -m list modified files as they're processed\n" +
" -p preserve timestamp on modified files\n" +
" -q quiet mode, do not print file summary by path\n" +
" -t backup modified files in same directory with '~' " +
"suffix\n" +
" -v verbose listing of all files processed (modified or " +
"not)\n" +
"These options may be concatenated together with a single" +
" leading dash.\n\n" +
"Path lists may include '*' wildcards, and may consist of " +
"multiple paths\n" +
"separated by ',' characters. The special directory " +
"pattern '**' matches\n" +
"any number of intervening directories. Any number of path " +
"list parameters may\n" +
"be supplied.\n");
}
}
}