package com.Ostermiller.util;
import java.io.*;
import java.text.MessageFormat;
import java.util.ResourceBundle;
import java.util.Locale;
public class Tabs {
public static final String version = "1.1";
protected static ResourceBundle labels = ResourceBundle.getBundle("com.Ostermiller.util.Tabs", Locale.getDefault());
public final static int TABS = -1;
private enum TabsCmdLnOption {
HELP(new CmdLnOption(labels.getString("help.option")).setDescription( labels.getString("help.message"))),
VERSION(new CmdLnOption(labels.getString("version.option")).setDescription(labels.getString("version.message"))),
ABOUT(new CmdLnOption(labels.getString("about.option")).setDescription(labels.getString("about.message"))),
WIDTH(new CmdLnOption(labels.getString("width.option"), 'w').setDescription(labels.getString("w.message")).setRequiredArgument()),
GUESS(new CmdLnOption(labels.getString("guess.option"), 'g').setDescription(labels.getString("g.message") + " (" + labels.getString("default") + ")")),
TABS(new CmdLnOption(labels.getString("tabs.option"), 't').setDescription(labels.getString("t.message"))),
SPACES(new CmdLnOption(labels.getString("spaces.option"), 's').setDescription(labels.getString("s.message") + " (" + labels.getString("default") + "=4)").setRequiredArgument()),
FORCE(new CmdLnOption(labels.getString("force.option"), 'f').setDescription(labels.getString("f.message"))),
NOFORCE(new CmdLnOption(labels.getString("noforce.option")).setDescription(labels.getString("noforce.message") + " (" + labels.getString("default") + ")")),
REALLYVERBOSE(new CmdLnOption(labels.getString("reallyverbose.option"), 'V').setDescription(labels.getString("V.message"))),
VERBOSE(new CmdLnOption(labels.getString("verbose.option"), 'v').setDescription(labels.getString("v.message"))),
QUIET(new CmdLnOption(labels.getString("quiet.option"), 'q').setDescription(labels.getString("q.message") + " (" + labels.getString("default") + ")")),
REALLYQUIET(new CmdLnOption(labels.getString("reallyquiet.option"), 'Q').setDescription(labels.getString("Q.message")));
private CmdLnOption option;
private TabsCmdLnOption(CmdLnOption option){
option.setUserObject(this);
this.option = option;
}
private CmdLnOption getCmdLineOption(){
return option;
}
}
public static void main(String[] args){
CmdLn commandLine = new CmdLn(
args
).setDescription(
labels.getString("tabs") + labels.getString("purpose.message")
);
for (TabsCmdLnOption option: TabsCmdLnOption.values()){
commandLine.addOption(option.getCmdLineOption());
}
int inputTabWidth = TABS;
int outputTabWidth = 4;
boolean force = false;
boolean printMessages = false;
boolean printExtraMessages = false;
boolean printErrors = true;
for(CmdLnResult result: commandLine.getResults()){
switch((TabsCmdLnOption)result.getOption().getUserObject()){
case HELP:{
commandLine.printHelp();
System.exit(0);
} break;
case VERSION:{
System.out.println(MessageFormat.format(labels.getString("version"), (Object[])new String[] {version}));
System.exit(0);
} break;
case ABOUT:{
System.out.println(
labels.getString("tabs") + " -- " + labels.getString("purpose.message") + "\n" +
MessageFormat.format(labels.getString("copyright"), (Object[])new String[] {"2002-2007", "Stephen Ostermiller (http://ostermiller.org/contact.pl?regarding=Java+Utilities)"}) + "\n\n" +
labels.getString("license")
);
System.exit(0);
} break;
case WIDTH:{
try {
inputTabWidth = Integer.parseInt(commandLine.getResult('w').getArgument());
} catch (NumberFormatException x){
inputTabWidth = -1;
}
if (inputTabWidth<1 || inputTabWidth>20){
System.err.println(labels.getString("widtherror"));
System.exit(1);
}
} break;
case GUESS:{
inputTabWidth = TABS;
} break;
case SPACES:{
try {
outputTabWidth = Integer.parseInt(commandLine.getResult('s').getArgument());
} catch (NumberFormatException x){
outputTabWidth = -1;
}
if (outputTabWidth<1 || outputTabWidth>20){
System.err.println("widtherror");
System.exit(1);
}
} break;
case TABS:{
outputTabWidth = TABS;
} break;
case FORCE:{
force = true;
} break;
case NOFORCE:{
force = false;
} break;
case REALLYVERBOSE:{
printExtraMessages = true;
printMessages = true;
printErrors = true;
} break;
case VERBOSE:{
printExtraMessages = false;
printMessages = true;
printErrors = true;
} break;
case QUIET:{
printExtraMessages = false;
printMessages = false;
printErrors = true;
} break;
case REALLYQUIET:{
printExtraMessages = false;
printMessages = false;
printErrors = false;
} break;
}
}
int exitCond = 0;
boolean done = false;
for (String argument: commandLine.getNonOptionArguments()){
done = true;
File source = new File(argument);
if (!source.exists()){
if(printErrors){
System.err.println(MessageFormat.format(labels.getString("doesnotexist"), (Object[])new String[] {argument}));
}
exitCond = 1;
} else if (!source.canRead()){
if(printErrors){
System.err.println(MessageFormat.format(labels.getString("cantread"), (Object[])new String[] {argument}));
}
exitCond = 1;
} else if (!source.canWrite()){
if(printErrors){
System.err.println(MessageFormat.format(labels.getString("cantwrite"), (Object[])new String[] {argument}));
}
exitCond = 1;
} else {
try {
if(convert (source, inputTabWidth, outputTabWidth, !force)){
if (printMessages){
System.out.println(MessageFormat.format(labels.getString("modified"), (Object[])new String[] {argument}));
}
} else {
if (printExtraMessages){
System.out.println(MessageFormat.format(labels.getString("alreadycorrect"), (Object[])new String[] {argument}));
}
}
} catch (IOException x){
if(printErrors){
System.err.println(argument + ": " + x.getMessage());
}
exitCond = 1;
}
}
}
if (!done){
if(inputTabWidth == TABS){
System.err.println(labels.getString("stdinguess"));
exitCond = 1;
} else {
try {
convert (System.in, System.out, inputTabWidth, outputTabWidth, !force);
} catch (IOException x){
System.err.println(x.getMessage());
exitCond = 1;
}
}
}
System.exit(exitCond);
}
private final static int DEFAULT_INPUT_TAB_WIDTH = 4;
private final static int DEFAULT_INPUT_FILE_TAB_WIDTH = TABS;
private final static int DEFAULT_OUTPUT_TAB_WIDTH = 4;
private final static boolean DEFAULT_MODIFY_BINARY = false;
public static boolean convert(InputStream in, OutputStream out) throws IOException {
return convert(in, out, DEFAULT_INPUT_TAB_WIDTH, DEFAULT_OUTPUT_TAB_WIDTH, DEFAULT_MODIFY_BINARY);
}
public static boolean convert(InputStream in, OutputStream out, int inputTabWidth) throws IOException {
return convert(in, out, inputTabWidth, DEFAULT_OUTPUT_TAB_WIDTH, DEFAULT_MODIFY_BINARY);
}
public static boolean convert(InputStream in, OutputStream out, int inputTabWidth, int outputTabWidth) throws IOException {
return convert(in, out, inputTabWidth, outputTabWidth, DEFAULT_MODIFY_BINARY);
}
public static boolean convert(InputStream in, OutputStream out, int inputTabWidth, int outputTabWidth, boolean binaryException) throws IOException {
if ((inputTabWidth < 1 || inputTabWidth > 20) && inputTabWidth != TABS){
throw new IllegalArgumentException(labels.getString("widtherror"));
}
if ((outputTabWidth < 1 || outputTabWidth > 20) && outputTabWidth != TABS){
throw new IllegalArgumentException(labels.getString("widtherror"));
}
int state = STATE_INIT;
int spaces = 0;
int tabs = 0;
int tabStops = 0;
int extraSpaces = 0;
boolean modified = false;
byte[] buffer = new byte[BUFFER_SIZE];
int read;
while((read = in.read(buffer)) != -1){
for (int i=0; i<read; i++){
byte b = buffer[i];
if(binaryException && b!='\r' && b!='\n' && b!='\t' && b!='\f' && (b & 0xff)<32){
throw new BinaryDataException(labels.getString("binaryexcepion"));
}
switch (b){
case ' ': {
if (state == STATE_INIT) {
spaces++;
extraSpaces++;
if (extraSpaces == inputTabWidth){
tabStops++;
extraSpaces = 0;
}
} else {
out.write(b);
}
} break;
case '\t': {
if (state == STATE_INIT) {
if (spaces > 0){
modified = true;
}
tabs++;
tabStops++;
extraSpaces = 0;
} else {
out.write(b);
}
} break;
case '\r': case '\n': {
out.write(b);
spaces = 0;
tabs = 0;
tabStops = 0;
extraSpaces = 0;
state = STATE_INIT;
} break;
default: {
if (state == STATE_INIT){
if (outputTabWidth == TABS){
for (int j=0; j<tabStops; j++){
out.write((byte)'\t');
}
} else {
extraSpaces += tabStops * outputTabWidth;
tabStops = 0;
}
for (int j=0; j<extraSpaces; j++){
out.write((byte)' ');
}
if (extraSpaces != spaces || tabStops != tabs) modified = true;
}
out.write(b);
state = STATE_SOMETHING;
} break;
}
}
}
return modified;
}
public static boolean convert(File f) throws IOException {
return convert(f, DEFAULT_INPUT_FILE_TAB_WIDTH, DEFAULT_OUTPUT_TAB_WIDTH, DEFAULT_MODIFY_BINARY);
}
public static boolean convert(File f, int inputTabWidth) throws IOException {
return convert(f, inputTabWidth, DEFAULT_OUTPUT_TAB_WIDTH, DEFAULT_MODIFY_BINARY);
}
public static boolean convert(File f, int inputTabWidth, int outputTabWidth) throws IOException {
return convert(f, inputTabWidth, outputTabWidth, DEFAULT_MODIFY_BINARY);
}
public static boolean convert(File f, int inputTabWidth, int outputTabWidth, boolean binaryException) throws IOException {
File temp = null;
InputStream in = null;
OutputStream out = null;
boolean modified = false;
try {
if (inputTabWidth == TABS){
inputTabWidth = guessTabWidth(new FileInputStream(f));
}
in = new FileInputStream(f);
temp = File.createTempFile("LineEnds", null, null);
out = new FileOutputStream(temp);
modified = convert(in, out, inputTabWidth, outputTabWidth, binaryException);
in.close();
in = null;
out.flush();
out.close();
out = null;
if (modified){
FileHelper.move(temp, f, true);
} else {
if (!temp.delete()){
throw new IOException(
MessageFormat.format(
labels.getString("tempdeleteerror"),
(Object[])new String[] {temp.toString()}
)
);
}
}
} finally {
if (in != null){
in.close();
in = null;
}
if (out != null){
out.flush();
out.close();
out = null;
}
}
return modified;
}
private final static int BUFFER_SIZE = 1024;
private final static int STATE_INIT = 0;
private final static int STATE_SOMETHING = 1;
final private static int MAX_SPACES = 128;
final private static int MAX_TABS = 16;
final private static int MAX_COMBINED = 256;
public static int guessTabWidth(InputStream in) throws IOException {
byte[] buffer = new byte[BUFFER_SIZE];
int[][] data = new int[MAX_SPACES][MAX_TABS];
int[] spaceData = new int[MAX_SPACES*MAX_TABS];
int read;
int state = STATE_INIT;
int tabs = 0;
int spaces = 0;
int mostTabs = 0;
int mostSpaces = 0;
while((read = in.read(buffer)) != -1){
for (int i=0; i<read; i++){
byte b = buffer[i];
switch (b){
case ' ': {
if (state == STATE_INIT) spaces++;
} break;
case '\t': {
if (state == STATE_INIT) tabs++;
} break;
case '\r': case '\n': {
state = STATE_INIT;
if (spaces < MAX_SPACES && tabs < MAX_TABS){
data[spaces][tabs]++;
if (tabs > mostTabs) mostTabs = tabs;
if (spaces > mostSpaces) mostSpaces = spaces;
spaces = 0;
tabs = 0;
}
} break;
default: {
state = STATE_SOMETHING;
} break;
}
}
}
for (int tabWidth=2; tabWidth<=20; tabWidth++){
int mostCombined=0;
for (int tabInd=0; tabInd <= mostTabs; tabInd++){
for (int spaceInd=0; spaceInd <= mostSpaces; spaceInd++){
int totInd = spaceInd + (tabInd * tabWidth);
if (totInd < MAX_COMBINED){
int numLines = data[spaceInd][tabInd];
if (numLines > 0){
if (mostCombined < totInd) mostCombined = totInd;
spaceData[totInd] += numLines;
}
}
}
}
boolean found = true;
for(int combInd=0; found && combInd < mostCombined; combInd+=tabWidth){
found = spaceData[combInd] > 0;
}
if (found) return tabWidth;
}
return 2;
}
}