/* * 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"); } } }