
com.sshtools.maven.codeswitcher.CodeSwitcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of codeswitcher-maven-plugin Show documentation
Show all versions of codeswitcher-maven-plugin Show documentation
Pre-processes source to enable or disable code paths at compile time.
The newest version!
/**
* SSHTOOLS Limited licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License.
*
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.sshtools.maven.codeswitcher;
/*
* Heavily based on HSQLDB's CodeSwitcher utility. All remaining portions subject
* to the below license. All other code, copyright 2011 SSHTools, license under the Apache 2
* license.
*
* ----------------------------------------------------------------------
*
* Copyrights and Licenses
*
* This product includes Hypersonic SQL.
* Originally developed by Thomas Mueller and the Hypersonic SQL Group.
*
* Copyright (c) 1995-2000 by the Hypersonic SQL Group. All rights reserved.
* Redistribution and use in source and binary forms, with or without modification, are permitted
* provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* - All advertising materials mentioning features or use of this software must display the
* following acknowledgment: "This product includes Hypersonic SQL."
* - Products derived from this software may not be called "Hypersonic SQL" nor may
* "Hypersonic SQL" appear in their names without prior written permission of the
* Hypersonic SQL Group.
* - Redistributions of any form whatsoever must retain the following acknowledgment: "This
* product includes Hypersonic SQL."
* This software is provided "as is" and any expressed or implied warranties, including, but
* not limited to, the implied warranties of merchantability and fitness for a particular purpose are
* disclaimed. In no event shall the Hypersonic SQL Group or its contributors be liable for any
* direct, indirect, incidental, special, exemplary, or consequential damages (including, but
* not limited to, procurement of substitute goods or services; loss of use, data, or profits;
* or business interruption). However caused any on any theory of liability, whether in contract,
* strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this
* software, even if advised of the possibility of such damage.
* This software consists of voluntary contributions made by many individuals on behalf of the
* Hypersonic SQL Group.
*
*
* For work added by the HSQL Development Group:
*
* Copyright (c) 2001-2002, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer, including earlier
* license statements (above) and comply with all above license conditions.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution, including earlier
* license statements (above) and comply with all above license conditions.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.*;
import java.text.ParseException;
import java.util.*;
/**
* Modifies the source code to support different JDK or profile settings.
*/
public class CodeSwitcher {
private static final String DEFAULT_LINE_SEPARATOR = System.getProperty("line.separator", "\n");
private List vList;
private List vSwitchOn;
private List vSwitchOff;
private List vSwitches;
private boolean comment = true;
private boolean failOnError = true;
private Map tokens;
private String lineSeparator = DEFAULT_LINE_SEPARATOR;
private boolean keepLastModified = true;
/**
* Constructor declaration
*
*/
public CodeSwitcher() {
vList = new ArrayList();
vSwitchOn = new ArrayList();
vSwitchOff = new ArrayList();
vSwitches = new ArrayList();
}
/**
* Set if processed files should have the same modification time as the
* original. Setting this to true would allow compiles to be more efficient,
* but might mean pre-processing state is wrong if the switches change.
*
* @param keepLastModified keep last modified time
*/
public void setKeepLastModified(boolean keepLastModified) {
this.keepLastModified = keepLastModified;
}
/**
* Get if processed files should have the same modification time as the
* original. Setting this to true would allow compiles to be more efficient,
* but might mean pre-processing state is wrong if the switches change.
*
* @return keep last modified time
*/
public boolean getKeepLastModified() {
return keepLastModified;
}
/**
* Set the output line separator. Defaults to the platform separator.
*
* @param lineSeparator line separator
*/
public void setLineSeparator(String lineSeparator) {
this.lineSeparator = lineSeparator;
}
/**
* Get the output line separator. Defaults to the platform separator.
*
* @return line separator
*/
public String getLineSeparator() {
return lineSeparator;
}
/**
* Set a map of tokens that will be used for simple string replacement.
* Every line will be checked for the strings contained in the keys. Any
* occurences will be replaced with the associated value.
*
* @param tokens tokens
*/
public void setTokens(Map tokens) {
this.tokens = tokens;
}
/**
* Get a map of tokens that will be used for simple string replacement.
* Every line will be checked for the strings contained in the keys. Any
* occurences will be replaced with the associated value.
*
* @return tokens
*/
public Map getTokens() {
return tokens;
}
/**
* Set whether an exception should be thrown when a parsing error occurs.
*
* @param failOnError fail on error
*/
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}
/**
* Get whether an exception should be thrown when a parsing error occurs.
*
* @return fail on error
*/
public boolean isFailOnError() {
return failOnError;
}
/**
* Enabled code the is tagged with the provided symbol.
*
* @param symbol symbol for code that should be enabled
*/
public void enableSymbol(String symbol) {
vSwitchOn.add(symbol);
}
/**
* Disable code the is tagged with the provided symbol.
*
* @param symbol symbol for code that should be disabled
*/
public void disableSymbol(String symbol) {
vSwitchOff.add(symbol);
}
/**
* Set whether any removed code should be commented instead of stripped.
*
* @param comment comment out source code instead of stripping it
*/
public void setComment(boolean comment) {
this.comment = comment;
}
/**
* Get whether any removed code should be commented instead of stripped.
*
* @return comment out source code instead of stripping it
*/
public boolean isComment() {
return comment;
}
/**
* Process all files
*
* @throws ParseException
*/
public void process() throws ParseException {
for (String file : vList) {
if (!processFile(file)) {
printError(new File(file), 0, "in file " + file + " !");
}
}
printMessage(null, 0, "");
}
/**
* Prints out all used labels
*/
void printSwitches() {
printMessage(null, 0, "Used labels:");
for (int i = 0; i < vSwitches.size(); i++) {
printMessage(null, 0, vSwitches.get(i));
}
}
/**
* Adds a directory to those that must be processed.
*
* @param path
*/
void addDir(String path) {
File f = new File(path);
if (f.isFile() && path.endsWith(".java")) {
vList.add(path);
} else if (f.isDirectory()) {
String list[] = f.list();
for (int i = 0; i < list.length; i++) {
addDir(path + File.separatorChar + list[i]);
}
}
}
class State {
int state = 0; // 0=normal 1=inside_if 2=inside_else
boolean switchoff = false;
boolean working = false;
int removeFrom = -1;
int i = 0;
public boolean delete;
}
private boolean processFile(String name) throws ParseException {
File f = new File(name);
long lastModified = f.lastModified();
File fnew = new File(name + ".new");
State state = new State();
try {
List newContents = getFileLines(f);
List originalContents = new ArrayList(newContents.size());
for (int i = 0; i < newContents.size(); i++) {
originalContents.add(newContents.get(i));
}
for (; state.i < newContents.size() && !state.delete; state.i++) {
String line = (String) newContents.get(state.i);
if (line == null) {
break;
}
String lineStripped = stripSpaces(line);
if (state.working) {
if (lineStripped.equals("/*") || lineStripped.equals("*/")) {
newContents.remove(state.i--);
} else if (lineStripped.startsWith("*")) {
int idx = line.indexOf('*');
newContents.set(state.i, line.substring(0, idx) + line.substring(idx + 1).trim());
} else if (lineStripped.startsWith("//") && !lineStripped.startsWith("//#")) {
int idx = line.indexOf("/");
newContents.set(state.i, line.substring(0, idx) + line.substring(idx + 2).trim());
}
}
if (lineStripped.indexOf("//#") != -1) {
// Handle single line comment switches
if (lineStripped.startsWith("//#[")) {
if (!handleSingleLineComment(f, state, newContents, line, lineStripped)) {
return false;
}
} else if (lineStripped.startsWith("//#ifdef")) {
if (!handleIf(f, state, newContents, lineStripped)) {
return false;
}
} else if (lineStripped.startsWith("//#else")) {
if (!handleElse(f, state, newContents)) {
return false;
}
} else if (lineStripped.startsWith("//#endif")) {
if (!handleEndIf(f, state, newContents)) {
return false;
}
} else if (lineStripped.startsWith("//#del")) {
if (!handleDel(f, state, lineStripped)) {
return false;
}
}
}
}
if (state.delete) {
if (!f.delete()) {
printError(f, 0, "Failed to delete file " + f);
}
} else {
if (state.state != 0) {
printError(f, newContents.size(), "'#endif' missing");
return false;
}
// Look for any tokens to replace
for (int i = 0; i < newContents.size(); i++) {
for (String token : tokens.keySet()) {
String value = tokens.get(token);
newContents.set(i, newContents.get(i).replace(token, value));
}
}
// Determine if the file has changed at all
boolean filechanged = false;
for (int i = 0; i < newContents.size(); i++) {
if (!originalContents.get(i).equals(newContents.get(i))) {
filechanged = true;
break;
}
}
if (!filechanged) {
return true;
}
writeFileLines(newContents, fnew, lineSeparator);
File fbak = new File(name + ".bak");
fbak.delete();
f.renameTo(fbak);
File fcopy = new File(name);
fnew.renameTo(fcopy);
fbak.delete();
if (keepLastModified) {
f.setLastModified(lastModified);
}
}
return true;
} catch (Exception e) {
printError(null, 0, e.getMessage());
return false;
}
}
private boolean handleDel(File f, State state, String lineStripped) throws ParseException {
String symbol = lineStripped.substring(6);
if (symbol.length() == 0) {
printError(f, state.i, "No symbol provide for #del statement");
return false;
}
if (vSwitchOn.contains(symbol)) {
state.delete = true;
printMessage(f, state.i, "Will delete file " + f);
}
return true;
}
protected boolean handleElse(File f, State state, List v) throws ParseException {
if (state.state != 1) {
printError(f, state.i, "'#else' without '#ifdef'");
return false;
}
state.state = 2;
if (state.working) {
if (state.switchoff) {
if (comment) {
if (v.get(state.i - 1).equals("")) {
v.add(state.i - 1, "*/");
state.i++;
} else {
v.add(state.i++, "*/");
}
} else {
removeMarkedLinesFrom(f, state, v);
}
state.switchoff = false;
} else {
if (comment) {
v.add(++state.i, "/*");
} else {
state.removeFrom = state.i;
}
state.switchoff = true;
}
}
return true;
}
protected boolean handleIf(File f, State state, List v, String lineStripped) throws ParseException {
if (state.state != 0) {
printError(f, state.i, "'#ifdef' not allowed inside '#ifdef'");
return false;
}
state.state = 1;
state.removeFrom = -1;
String s = lineStripped.substring(8);
if (vSwitchOn.indexOf(s) != -1) {
printMessage(f, state.i, "Including " + s);
state.working = true;
state.switchoff = false;
if (!comment) {
// If not commenting, removing the ifdef itself
v.remove(state.i--);
}
} else if (vSwitchOff.indexOf(s) != -1) {
printMessage(f, state.i, "Excluding " + s);
state.working = true;
if (comment) {
v.add(++state.i, "/*");
} else {
state.removeFrom = state.i;
}
state.switchoff = true;
}
if (vSwitches.indexOf(s) == -1) {
vSwitches.add(s);
}
return true;
}
protected boolean handleEndIf(File f, State state, List v) throws ParseException {
if (state.state == 0) {
printError(f, state.i, "'#endif' without '#ifdef'");
return false;
}
state.state = 0;
if (state.working && state.switchoff) {
if (v.get(state.i - 1).equals("")) {
if (comment) {
v.add(state.i - 1, "*/");
}
state.i++;
} else {
if (comment) {
v.add(state.i++, "*/");
}
}
removeMarkedLinesFrom(f, state, v);
}
else if (!comment) {
// If not commenting, removing the endif itself
state.removeFrom = state.i;
removeMarkedLinesFrom(f, state, v);
}
state.working = false;
state.switchoff = false;
return true;
}
protected boolean handleSingleLineComment(File f, State state, List v, String line, String lineStripped)
throws ParseException {
int idx = line.indexOf("[");
int eidx = line.indexOf(']');
if (idx == -1) {
printError(f, state.i, "unclosed simple switch");
return false;
} else {
String symbol = line.substring(idx + 1, eidx);
if (vSwitchOn.contains(symbol)) {
v.set(state.i, line.substring(0, line.indexOf('/')) + line.substring(eidx + 1).trim());
} else if (vSwitchOff.contains(symbol)) {
state.removeFrom = state.i;
removeMarkedLinesFrom(f, state, v);
}
}
return true;
}
private void removeMarkedLinesFrom(File f, State state, List v) {
if (state.removeFrom != -1) {
printMessage(f, state.removeFrom, "Removing " + (state.i - state.removeFrom) + " lines");
for(int i = state.removeFrom; i <= state.i; i++) {
printMessage(f, state.removeFrom, "Removed '" + v.remove(state.removeFrom) + "'");
}
state.i = state.removeFrom - 1;
state.removeFrom = -1;
}
}
static List getFileLines(File f) throws IOException {
LineNumberReader read = null;
List v = new ArrayList();
try {
read = new LineNumberReader(new FileReader(f));
for (;;) {
String line = read.readLine();
if (line == null) {
break;
}
v.add(line);
}
} finally {
if (read != null) {
read.close();
}
}
return v;
}
static String trimBoth(String text) {
text = text.trim();
int s = text.length();
@SuppressWarnings("unused")
char ch;
int i;
for (i = 0; i < s && Character.isWhitespace((ch = text.charAt(i))); i++)
;
return i < s ? text.substring(i) : text;
}
static String stripSpaces(String text) {
StringBuffer buf = new StringBuffer();
int s = text.length();
char ch;
for (int i = 0; i < s; i++) {
ch = text.charAt(i);
if (!Character.isWhitespace(ch)) {
buf.append(ch);
}
}
return buf.toString();
}
static void writeFileLines(List v, File f, String ls) throws IOException {
FileWriter write = null;
try {
write = new FileWriter(f);
for (String line : v) {
write.write(line);
write.write(ls);
}
write.flush();
} finally {
if (write != null) {
write.close();
}
}
}
/**
* Print an error message. May be overridden to integrate with logging
* frameworks. If failOnError
is set, an exception will also be
* thrown.
*
* @param f file message occurred on
* @param line line number
* @param error message
* @throws ParseException if failOnError is set
*/
protected void printError(File f, int line, String error) throws ParseException {
if (f == null) {
System.out.println("ERROR: " + error);
if (failOnError) {
throw new ParseException("Failed to codeswitch.", 0);
}
} else {
System.out.println("ERROR: " + f.getName() + "[" + line + "] " + error);
if (failOnError) {
throw new ParseException("Failed to parse " + f.getPath() + ". " + error, line);
}
}
}
/**
* Print a message. May be overridden to integrate with logging frameworks.
*
* @param f file message occured on
* @param line line number
* @param message message
*/
protected void printMessage(File f, int line, String message) {
if (f == null) {
System.out.println("MSG: " + message);
} else {
System.out.println("MSG: " + f.getName() + "[" + line + "] " + message);
}
}
/**
* Command line entry point,
*
*
* Usage: java CodeSwitcher [paths] [labels] [+][-]
* If no labels are specified then all used
* labels in the source code are shown.
* Use +MODE to switch on the things labeld MODE
* Use -MODE to switch off the things labeld MODE
* Path: Any number of path or files may be
* specified. Use . for the current directory
* (including sub-directories).
* Example: java CodeSwitcher +JAVA2 .
* This example switches on code labeled JAVA2
* in all *.java files in the current directory
* and all subdirectories.
*
*
* @param a arguments.
* @throws ParseException
*/
public static void main(String a[]) throws ParseException {
CodeSwitcher s = new CodeSwitcher();
if (a.length == 0) {
showUsage();
return;
}
boolean path = false;
for (int i = 0; i < a.length; i++) {
String p = a[i];
if (p.startsWith("/")) {
String opt = p.substring(1);
if (opt.equalsIgnoreCase("strip")) {
s.comment = false;
}
} else if (p.startsWith("+")) {
s.enableSymbol(p.substring(1));
} else if (p.startsWith("-")) {
s.disableSymbol(p.substring(1));
} else {
s.addDir(p);
path = true;
}
}
if (!path) {
s.printError(null, 0, "no path specified");
showUsage();
} else {
if (s.vSwitchOff.size() == 0 && s.vSwitchOn.size() == 0) {
s.printSwitches();
} else {
s.process();
}
}
}
/**
* Method declaration
*
*/
static void showUsage() {
System.out.print("Usage: java CodeSwitcher [paths] [labels] [+][-]\n" + "If no labels are specified then all used\n"
+ "labels in the source code are shown.\n" + "Use +MODE to switch on the things labeld MODE\n"
+ "Use -MODE to switch off the things labeld MODE\n" + "Path: Any number of path or files may be\n"
+ "specified. Use . for the current directory\n" + "(including sub-directories).\n"
+ "Example: java CodeSwitcher +JAVA2 .\n" + "This example switches on code labeled JAVA2\n"
+ "in all *.java files in the current directory\n" + "and all subdirectories.\n");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy