2024-06-21 19:37:17 -05:00

1075 lines
42 KiB
Java

package prc.autodoc;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static prc.Main.*;
/**
* This class forms an interface for accessing 2da files in the
* PRC automated manual generator.
*/
public class Data_2da implements Cloneable {
// String matching pattern. Gets a block of non-whitespace (tab is not counted as whitespace here) that does not contain any " OR " followed by any characters until the next "
private static final Pattern pattern = Pattern.compile("[[\\S&&[^\"]][\t]]+|\"[^\"]+\"");
// Same as the above, but counts tab as whitespace. Bug-compatibility with BioWare's own violations of 2da spec
private static final Pattern bugCompatPattern = Pattern.compile("[\\S&&[^\"]]+|\"[^\"]+\"");
//private static Matcher matcher = pattern.matcher("");
private LinkedHashMap<String, ArrayList<String>> mainData = new LinkedHashMap<String, ArrayList<String>>();
private final String name;
private String defaultValue;
/**
* Bugcompatibility feature for a special case where the first line of a 2da has * for line number instead of 0
*/
private boolean starOnLine0 = false;
/**
* Used for storing the original case of the labels. The ones used in the hashmap are lowercase
*/
private ArrayList<String> realLabels = new ArrayList<String>();
private final boolean TLKEditCompatible = true;
/**
* Creates a new, empty Data_2da with the specified name.
*
* @param name The new 2da file's name.
*/
public Data_2da(String name) {
this(name, "");
}
/**
* Creates a new, empty Data_2da with the specified name and default value.
*
* @param name the new 2da file's name
* @param defaultValue the new 2da file's default value
*/
public Data_2da(String name, String defaultValue) {
this.name = name;
this.defaultValue = defaultValue;
}
/**
* Private constructor for use in cloning.
*
* @param name the file name
* @param defaultValue the default value
* @param realLabels the labels with original case
* @param mainData the contents
*/
public Data_2da(String name, String defaultValue, ArrayList<String> realLabels, LinkedHashMap<String, ArrayList<String>> mainData) {
this.name = name;
this.defaultValue = defaultValue;
this.realLabels = realLabels;
this.mainData = mainData;
}
/**
* Saves the 2da represented by this object to file. Equivalent to calling
* <code>save2da(path, false, false)</code>.
*
* @param path the directory to save the file in. If this is "" or null,
* the current directory is used.
* @throws IOException if <code>true</code> and a file with the same name as this 2da
* exists at <code>path</code>, it is overwritten.
* If <code>false</code> and in the same situation, an IOException is
* thrown.
*/
public void save2da(String path) throws IOException {
save2da(path, false, false);
}
/**
* Saves the 2da represented by this object to file. CRLFs are explicitly used
* instead of system specific line terminator.
*
* @param path the directory to save the file in. If this is "" or null,
* the current directory is used.
* @param allowOverWrite if <code>true</code> and a file with the same name as this 2da
* exists at <code>path</code>, it is overwritten.
* If <code>false</code> and in the same situation, an IOException is
* thrown.
* @param evenColumns if <code>true</code>, every entry in a column will be padded until they
* start from the same position
* @throws IOException if cannot overwrite, or the underlying IO throws one
*/
public void save2da(String path, boolean allowOverWrite, boolean evenColumns) throws IOException {
String CRLF = "\r\n";
if (path == null || path.equals(""))
path = "." + File.separator;
if (!path.endsWith(File.separator))
path += File.separator;
File file = new File(path + name + ".2da");
if (file.exists() && !allowOverWrite)
throw new IOException("File exists already: " + file.getAbsolutePath());
// Inform user
if (verbose) System.out.print("Saving 2da file: " + name + " ");
FileWriter fw = new FileWriter(file, false);
String[] labels = this.getLabels();
String toWrite;
// Get the amount of padding used, if any
int[] widths = new int[labels.length + 1];// All initialised to 0
if (evenColumns) {
ArrayList<String> column;
int pad;
// Loop over columns
for (int i = 0; i < labels.length; i++) {
pad = labels[i].length();
column = mainData.get(labels[i]);
// Loop over rows
for (int j = 0; j < this.getEntryCount(); j++) {
toWrite = column.get(j);
// If the string contains spaces, it needs to be wrapped in "
if (toWrite.indexOf(" ") != -1)
toWrite = "\"" + toWrite + "\"";
if (toWrite.length() > pad) pad = toWrite.length();
}
widths[i] = pad;
}
// The last entry in the array is used for the numbers column
widths[widths.length - 1] = new Integer(this.getEntryCount()).toString().length();
}
// Write the header and default lines
fw.write("2DA V2.0" + CRLF);
if (!defaultValue.equals(""))
fw.write("DEFAULT: " + defaultValue + CRLF);
else
fw.write(CRLF);
// Write the labels row using the original case
for (int i = 0; i < widths[widths.length - 1]; i++) fw.write(" ");
for (int i = 0; i < realLabels.size(); i++) {
fw.write(" " + realLabels.get(i));
for (int j = 0; j < widths[i] - realLabels.get(i).length(); j++) fw.write(" ");
}
fw.write((TLKEditCompatible ? " " : "") + CRLF);
// Write the data
for (int i = 0; i < this.getEntryCount(); i++) {
// Write the number row and it's padding
if (i == 0 && starOnLine0)
fw.write("*");
else
fw.write("" + i);
for (int j = 0; j < widths[widths.length - 1] - new Integer(i).toString().length(); j++) fw.write(" ");
// Loop over columns
for (int j = 0; j < labels.length; j++) {
toWrite = mainData.get(labels[j]).get(i);
// If the string contains spaces, it needs to be wrapped in "
if (toWrite.indexOf(" ") != -1)
toWrite = "\"" + toWrite + "\"";
fw.write(" " + toWrite);
// Write padding
for (int k = 0; k < widths[j] - toWrite.length(); k++) fw.write(" ");
}
fw.write((TLKEditCompatible ? " " : "") + CRLF);
if (verbose) spinner.spin();
}
fw.flush();
fw.close();
if (verbose) System.out.println("- Done");
}
/**
* Creates a new Data_2da on the 2da file specified.
*
* @param filePath path to the 2da file to load
* @return a Data_2da instance containing the read 2da
* @throws IllegalArgumentException <code>filePath</code> does not specify a 2da file
* @throws TwoDAReadException reading the 2da file specified does not succeed,
* or the file does not contain any data
*/
public static Data_2da load2da(String filePath) throws IllegalArgumentException, TwoDAReadException {
return load2da(filePath, false);
}
/**
* Creates a new Data_2da on the 2da file specified.
*
* @param filePath path to the 2da file to load
* @param bugCompat if this is <code>true</code>, ignores
* departures from the 2da spec present in Bioware 2das
* @return a Data_2da instance containing the read 2da
* @throws IllegalArgumentException <code>filePath</code> does not specify a 2da file
* @throws TwoDAReadException reading the 2da file specified does not succeed,
* or the file does not contain any data
*/
public static Data_2da load2da(String filePath, boolean bugCompat) throws IllegalArgumentException, TwoDAReadException {
Data_2da toReturn;
Matcher matcher = bugCompat ? bugCompatPattern.matcher("") : pattern.matcher("");
String name;
// Some paranoia checking for bad parameters
if (!filePath.toLowerCase().endsWith("2da"))
throw new IllegalArgumentException("Non-2da filename passed to Data_2da: " + filePath);
// Create the file handle
File baseFile = new File(filePath);
// More paraoia
if (!baseFile.exists())
throw new IllegalArgumentException("Nonexistent file passed to Data_2da: " + filePath);
if (!baseFile.isFile())
throw new IllegalArgumentException("Nonfile passed to Data_2da: " + filePath);
// Drop the path from the filename
name = baseFile.getName().substring(0, baseFile.getName().length() - 4);
//toReturn = new Data_2da(baseFile.getName().substring(0, baseFile.getName().length() - 4));
// Tell the user what we are doing
if (verbose) System.out.print("Reading 2da file: " + name + " ");
// Create a Scanner for reading the 2da
Scanner reader = null;
try {
// Fully read the file into a byte array
RandomAccessFile raf = new RandomAccessFile(baseFile, "r");
byte[] bytebuf = new byte[(int) raf.length()];
raf.readFully(bytebuf);
raf.close();
//reader = new Scanner(baseFile);
reader = new Scanner(new String(bytebuf));
} catch (Exception e) {
err_pr.println("Error: File operation failed. Aborting.\nException data:\n" + e);
System.exit(1);
}
try {
// Check the 2da header
if (!reader.hasNextLine())
throw new TwoDAReadException("Empty file: " + name + "!");
String data = reader.nextLine();
if (!(bugCompat ? Pattern.matches("2DA[ \t]V2\\.0\\s*", data) : data.contains("2DA V2.0")))
throw new TwoDAReadException("2da header missing or invalid: " + name);
// Initialise the return object
toReturn = new Data_2da(name);
// Start the actual reading
try {
toReturn.createData(reader, matcher, bugCompat);
} catch (TwoDAReadException e) {
throw new TwoDAReadException("Exception occurred when reading 2da file: " + toReturn.getName() + "\n" + e.getMessage(), e);
}
} finally {
// Cleanup
reader.close();
}
if (verbose) System.out.println("- Done");
return toReturn;
}
/**
* Reads the data rows from the 2da into the hashmap and
* does validity checking on the 2da while doing so.
*
* @param reader Scanner that the method reads from
* @param matcher Matcher being used to parse the data read
* @param bugCompat if this is <code>true</code>, ignores
* departures from the 2da spec present in Bioware 2das
*/
private void createData(Scanner reader, Matcher matcher, boolean bugCompat) {
Scanner rowParser;
String data, bugCompat_data;
boolean bugCompat_MissingDefaultLine = false;
int line = 0;
// Get the default - though it's not used by this implementation, it should not be lost by opening and resaving a file
if (!reader.hasNextLine())
throw new TwoDAReadException("No contents after header in 2da file " + name + "!");
bugCompat_data = data = reader.nextLine();
matcher.reset(data);
if (matcher.find()) { // Non-blank default line
data = matcher.group();
if (data.trim().equalsIgnoreCase("DEFAULT:")) {
if (matcher.find())
this.defaultValue = matcher.group();
else
throw new TwoDAReadException("Malformed default line in 2da file " + name + "!");
} else if (!bugCompat)
throw new TwoDAReadException("Malformed default line in 2da file " + name + "!");
else
bugCompat_MissingDefaultLine = true;
}
// Find the labels row
if (bugCompat_MissingDefaultLine) // Handle cases where the labels are on the second line in the file instead of 3rd
data = bugCompat_data;
else {
if (!reader.hasNextLine())
throw new TwoDAReadException("No labels found in 2da file!");
data = reader.nextLine();
}
// Check for blank lines between the DEFAULT line and labels
if (data.trim().equals(""))
if (!bugCompat)
throw new TwoDAReadException("Labels not present on third line of the file!");
else
while (true) {
if (reader.hasNextLine()) {
data = reader.nextLine();
if (!data.trim().equals(""))
break;
} else
throw new TwoDAReadException("No data in 2da file!");
}
// Parse the labels
String[] localrealLabels = data.trim().split("\\p{javaWhitespace}+");
String[] labels = new String[localrealLabels.length];
//System.arraycopy(realLabels, 0, labels, 0, localrealLabels.length);
// Create the row containers and the main store
for (int i = 0; i < labels.length; i++) {
realLabels.add(localrealLabels[i]);
labels[i] = localrealLabels[i].toLowerCase();
mainData.put(labels[i], new ArrayList<String>());
}
// Error if there are empty lines between the header and the data or no lines at all
if (!reader.hasNextLine())
if (!bugCompat)
throw new TwoDAReadException("No data in 2da file!");
else
return;
if ((data = reader.nextLine()).trim().equals(""))
if (!bugCompat)
throw new TwoDAReadException("Blank lines following labels row!");
else
while (true) {
if (reader.hasNextLine()) {
data = reader.nextLine();
if (!data.trim().equals(""))
break;
} else
return;
}
while (true) {
//rowParser = new Scanner(data);
matcher.reset(data);
matcher.find();
// Check for the presence of the row number
try {
// Special case - In bugcompatibility mode, 0 can be replaced with *
if (bugCompat && line == 0 && matcher.group().trim().equals("*")) {
starOnLine0 = true;
} else
line = Integer.parseInt(matcher.group());
} catch (NumberFormatException e) {
return;
// throw new TwoDAReadException("Numberless 2da line: " + (line + 1));
}
// Start parsing the row
for (int i = 0; i < labels.length; i++) {
// Find the next match and check for too short rows
if (!matcher.find())
throw new TwoDAReadException("Too short 2da line: " + line);
// Get the next element and add it to the data structure
data = matcher.group();
// Remove the surrounding quotes if they are present
if (data.startsWith("\"")) data = data.substring(1, data.length() - 1);
mainData.get(labels[i]).add(data);
}
// Check for too long rows
if (matcher.find())
return;
// throw new TwoDAReadException("Too long 2da line: " + line);
// Increment the entry counter
//entries++;
/* Get the next line if there is one, or break the loop
* A bit ugly, but I couldn't figure an easy way of making the loop go right
* even for 2das with only one row without biggish changes
*/
if (reader.hasNextLine()) {
data = reader.nextLine();
if (data.trim().equals(""))
break;
} else
break;
if (verbose) spinner.spin();
}
// Some validity checking on the 2da. Empty rows allowed only in the end
if (getNextNonEmptyRow(reader) != null)
throw new TwoDAReadException("Empty row in the middle of 2da. After row: " + line);
}
/**
* Reads rows from a Scanner pointed at a 2da file until it finds a
* row containing non-whitespace characters.
*
* @param reader Scanner that the method reads from
* @return The row found, or null if none were found.
*/
private static String getNextNonEmptyRow(Scanner reader) {
String toReturn = null;
while (reader.hasNextLine()) {
toReturn = reader.nextLine();
if (!toReturn.trim().equals(""))
break;
}
if (toReturn == null || toReturn.trim().equals(""))
return null;
return toReturn;
}
/**
* Get the list of column labels in this 2da.
*
* @return an array of Strings containing the column labels
*/
public String[] getLabels() {
// For some reason, it won't let me cast the keyset directly into a String[]
// return (String[])(mainData.keySet().toArray());
Object[] temp = mainData.keySet().toArray();
String[] toReturn = new String[temp.length];
for (int i = 0; i < temp.length; i++) toReturn[i] = (String) temp[i];
/*String[] toReturn = (String[])mainData.keySet().toArray();*/
return toReturn;
}
/**
* Get the 2da entry on the given row and column
*
* @param label the label of the column to get
* @param row the number of the row to get, as string
* @return int represeting the 2da entry or <code>null</code> if the column does not exist
* if the column is **** then 0 will be returned
* @throws NumberFormatException if <code>row</code> cannot be converted to an integer
*/
public int getBiowareEntryAsInt(String label, String row) {
String returnString = this.getEntry(label, Integer.parseInt(row));
if (returnString.equals("****"))
return 0;
else if (returnString.matches("\\D")) //check its a number
return 0;
return Integer.decode(returnString);
}
/**
* Get the 2da entry on the given row and column
*
* @param label the label of the column to get
* @param row the number of the row to get
* @return String represeting the 2da entry or <code>null</code> if the column does not exist
* if the column is **** then a zero length string will be returned
*/
public int getBiowareEntryAsInt(String label, int row) {
ArrayList<String> column = mainData.get(label.toLowerCase());
String returnString = column != null ? column.get(row) : null;
if (returnString.equals("****"))
return 0;
else if (returnString.matches("\\D")) //check its a number
return 0;
return Integer.decode(returnString);
}
/**
* Get the 2da entry on the given row and column
*
* @param label the label of the column to get
* @param row the number of the row to get, as string
* @return String represeting the 2da entry or <code>null</code> if the column does not exist
* if the column is **** then a zero length string will be returned
* @throws NumberFormatException if <code>row</code> cannot be converted to an integer
*/
public String getBiowareEntry(String label, String row) {
String returnString = this.getEntry(label, Integer.parseInt(row));
if (returnString.equals("****"))
return "";
return returnString;
}
/**
* Get the 2da entry on the given row and column
*
* @param label the label of the column to get
* @param row the number of the row to get
* @return String represeting the 2da entry or <code>null</code> if the column does not exist
* if the column is **** then a zero length string will be returned
*/
public String getBiowareEntry(String label, int row) {
ArrayList<String> column = mainData.get(label.toLowerCase());
String returnString = column != null ? column.get(row) : null;
if (returnString.equals("****"))
return "";
return returnString;
}
/**
* Get the 2da entry on the given row and column
*
* @param label the label of the column to get
* @param row the number of the row to get, as string
* @return String represeting the 2da entry or <code>null</code> if the column does not exist
* @throws NumberFormatException if <code>row</code> cannot be converted to an integer
*/
public String getEntry(String label, String row) {
return this.getEntry(label, Integer.parseInt(row));
}
/**
* Get the 2da entry on the given row and column
*
* @param label the label of the column to get
* @param row the number of the row to get
* @return String represeting the 2da entry or <code>null</code> if the column does not exist
*/
public String getEntry(String label, int row) {
ArrayList<String> column = mainData.get(label.toLowerCase());
return column != null ? column.get(row) : null;
}
/**
* Get number of entries in this 2da. Works by returning the size of one of the columns in the 2da.
*
* @return integer equal to the number of entries in this 2da
*/
public int getEntryCount() {
if (mainData.entrySet().size() == 0) {
return 0;
}
return mainData.entrySet().iterator().next().getValue().size();
}
/**
* Get the name of this 2da
*
* @return String representing this 2da's name
*/
public String getName() {
return name;
}
/**
* Sets the 2da entry on the given row and column
*
* @param label the label of the column to get
* @param row the number of the row to get, as string
* @param entry the new contents of the entry. If this is null or empty, it is replaced with ****
* @throws NumberFormatException if <code>row</code> cannot be converted to an integer
*/
public void setEntry(String label, String row, String entry) {
this.setEntry(label, Integer.parseInt(row), entry);
}
/**
* Sets the 2da entry on the given row and column
*
* @param label the label of the column to get
* @param row the number of the row to get, as string
* @param entry the new contents of the entry. If this is null or empty, it is replaced with ****
* or with the default value if that is set
*/
public void setEntry(String label, int row, String entry) {
if (entry == null || entry.equals(""))
if (defaultValue.equals(""))
entry = "****";
else
entry = defaultValue;
mainData.get(label.toLowerCase()).set(row, entry);
}
/**
* Returns the contents of the requested row as a string array. The order the columns are
* taken is the same as the order of labels from getLabels().
*
* @param index the index of the row to get
* @return an array of strings containing the elements in the r
* @throws NumberFormatException if <code>index</code> cannot be converted to an integer
*/
public String[] getRow(String index) {
return getRow(Integer.parseInt(index));
}
/**
* Returns the contents of the requested row as a string array. The order the columns are
* taken is the same as the order of labels from getLabels().
*
* @param index the index of the row to get
* @return an array of strings containing the elements in the row
*/
public String[] getRow(int index) {
String[] labels = this.getLabels();
String[] toReturn = new String[labels.length];
for (int i = 0; i < labels.length; i++) {
toReturn[i] = mainData.get(labels[i]).get(index);
}
return toReturn;
}
/**
* Appends a new, empty row to the end of the 2da file, with entries defaulting to the
* default value or if that is not set, ****
*/
public void appendRow() {
String[] labels = this.getLabels();
for (String label : labels) {
if (defaultValue.equals(""))
mainData.get(label).add("****");
else
mainData.get(label).add(defaultValue);
}
}
/**
* Appends a new, empty row to the end of the 2da file. The new row will be filled with the values
* given as parameter.
*
* @param data the strings that will be used to fill the new row
* @throws IllegalArgumentException if the number of elements in <code>data</code> array is not
* same as number of columns in the 2da
*/
public void appendRow(String[] data) {
String[] labels = this.getLabels();
// Sanity check
if (labels.length != data.length)
throw new IllegalArgumentException("Differing column width when attempting to insert row");
for (int i = 0; i < labels.length; i++) {
mainData.get(labels[i]).add(data[i]);
}
}
/**
* Inserts a new row into the given index in the 2da file. The row currently at the index and all
* subsequent rows have their index increased by one. The new row will be filled with the values
* given as parameter.
*
* @param index the index where the new row will be located
* @param data the strings that will be used to fill the new row
* @throws IllegalArgumentException if the number of elements in <code>data</code> array is not
* same as number of columns in the 2da
* @throws NumberFormatException if <code>index</code> cannot be converted to an integer
*/
public void insertRow(String index, String[] data) {
insertRow(Integer.parseInt(index), data);
}
/**
* Inserts a new row into the given index in the 2da file. The row currently at the index and all
* subsequent rows have their index increased by one. The new row will be filled with the values
* given as parameter.
*
* @param index the index where the new row will be located
* @param data the strings that will be used to fill the new row
* @throws IllegalArgumentException if the number of elements in <code>data</code> array is not
* same as number of columns in the 2da
*/
public void insertRow(int index, String[] data) {
String[] labels = this.getLabels();
// Sanity check
if (labels.length != data.length)
throw new IllegalArgumentException("Differing column width when attempting to insert row");
for (int i = 0; i < labels.length; i++) {
mainData.get(labels[i]).add(index, data[i]);
}
}
/**
* Adds a new, empty row to the given index in the 2da file. The row currently at the index and all
* subsequent rows have their index increased by one.
* The entries default to ****.
*
* @param index the index where the new row will be located
* @throws NumberFormatException if <code>index</code> cannot be converted to an integer
*/
public void insertRow(String index) {
insertRow(Integer.parseInt(index));
}
/**
* Adds a new, empty row to the given index in the 2da file. The row currently at the index and all
* subsequent rows have their index increased by one.
* The entries default to default value or if that is not set, ****.
*
* @param index the index where the new row will be located
*/
public void insertRow(int index) {
String[] labels = this.getLabels();
for (String label : labels) {
if (defaultValue.equals(""))
mainData.get(label).add(index, "****");
else
mainData.get(label).add(index, defaultValue);
}
}
/**
* Removes the row at the given index. All subsequent rows have their indexed shifted down by one.
*
* @param index the index of the row to remove
* @throws NumberFormatException if <code>index</code> cannot be converted to an integer
*/
public void removeRow(String index) {
removeRow(Integer.parseInt(index));
}
/**
* Removes the row at the given index. All subsequent rows have their indexed shifted down by one.
*
* @param index the index of the row to remove
*/
public void removeRow(int index) {
String[] labels = this.getLabels();
for (String label : labels) {
mainData.get(label).remove(index);
}
}
/**
* Adds a new column to the 2da file. The new column will be the last in the file.
*
* @param label the name of the column to add
*/
public void addColumn(String label) {
ArrayList<String> column = new ArrayList<String>();
mainData.put(label.toLowerCase(), column);
realLabels.add(label);
if (defaultValue.equals("")) {
for (int i = 0; i < this.getEntryCount(); i++) {
column.add("****");
}
} else {
for (int i = 0; i < this.getEntryCount(); i++) {
column.add(defaultValue);
}
}
}
/**
* Removes the column with the given label from the 2da.
*
* @param label the name of the column to remove
*/
public void removeColumn(String label) {
mainData.remove(label);
realLabels.remove(label);
}
/**
* The main method, as usual
*
* @param args
*/
public static void main(String[] args) {
if (args.length == 0) readMe();
List<String> fileNames = new ArrayList<String>();
boolean compare = false;
boolean resave = false;
boolean minimal = false;
boolean ignoreErrors = false;
boolean readStdin = false;
boolean bugCompat = false;
for (String param : args) {//[-bcrmnqs] file... | -
// Parameter parseage
if (param.startsWith("-")) {
if (param.equals("-"))
readStdin = true;
else if (param.equals("--help")) readMe();
else {
for (char c : param.substring(1).toCharArray()) {
switch (c) {
case 'b':
bugCompat = true;
break;
case 'c':
compare = true;
if (resave) resave = false;
break;
case 'r':
resave = true;
if (compare) compare = false;
break;
case 'm':
minimal = true;
break;
case 'n':
ignoreErrors = true;
break;
case 'q':
verbose = false;
break;
case 's':
spinner.disable();
break;
default:
err_pr.println("Error: Unknown parameter: " + c);
readMe();
}
}
}
} else
// It's a filename
fileNames.add(param);
}
// Read files from stdin if specified
if (readStdin) {
Scanner scan = new Scanner(System.in);
String s;
while (scan.hasNextLine()) {
s = scan.nextLine();
if (s.charAt(0) == '"' && s.charAt(s.length() - 1) == '"')
s = s.substring(1, s.length() - 1);
fileNames.add(s);
}
}
// Run the specified operation
if (compare) {
Data_2da file1, file2;
file1 = load2da(args[1], bugCompat);
file2 = load2da(args[2], bugCompat);
doComparison(file1, file2);
} else if (resave) {
Data_2da temp;
for (String fileName : fileNames) {
try {
temp = load2da(fileName, bugCompat);
temp.save2da(new File(fileName).getCanonicalFile().getParent() + File.separator, true, !minimal);
} catch (Exception e) {
// Print the error
err_pr.printException(e);
// If ignoring errors, and this error is of expected type, continue
if (e instanceof IllegalArgumentException ||
e instanceof TwoDAReadException ||
e instanceof IOException)
if (ignoreErrors)
continue;
System.exit(1);
}
}
} else {
// Validify by loading
for (String fileName : fileNames) {
try {
load2da(fileName, bugCompat);
} catch (Exception e) {
// Print the error
err_pr.printException(e);
// If ignoring errors, and this error is of expected type, continue
if (e instanceof IllegalArgumentException || e instanceof TwoDAReadException)
if (ignoreErrors)
continue;
System.exit(1);
}
}
}
}
private static void readMe() {
System.out.println("Usage:\n" +
" [-bcrmnqs] file... | -\n" +
"\n" +
" -b bug-compatibility mode. Counts tabs as whitespace instead of data\n" +
" -c prints the differing lines between the 2das given as first two\n" +
" parameters. They must have the same label set and entrycount.\n" +
" Mutually exclusive with -r\n" +
" -r resaves the 2das given as parameters. Mutually exclusive with -c\n" +
" -m saves the files with minimal spaces. Only relevant when resaving\n" +
" -n ignores errors that occur during validity testing and resaving,\n" +
" just skips to the next file\n" +
" -q quiet mode\n" +
" -s no spinner\n" +
" - a line given as a lone parameter means that the list of files is\n" +
" read from stdin in addition to the ones passed from command line.\n" +
" The list passed in such manner should contain one filename per line\n" +
"\n" +
" --help prints this text\n" +
"\n" +
"\n" +
"if neither -c or -r is specified, performs validity testing on the given files"
);
System.exit(0);
}
/**
* Compares the given two 2da files and prints differences it finds
* Differing number of rows, or row names will cause comparison to abort.
*
* @param file1 Data_2da containing one of the files to be compared
* @param file2 Data_2da containing the other file to be compared
*/
public static void doComparison(Data_2da file1, Data_2da file2) {
// Check labels
String[] labels1 = file1.getLabels(),
labels2 = file2.getLabels();
if (labels1.length != labels2.length) {
System.out.println("Differing amount of row labels\n" +
file1.getName() + ": " + labels1.length + "\n" +
file2.getName() + ": " + labels2.length);
return;
}
for (int i = 0; i < labels1.length; i++) {
if (!labels1[i].equals(labels2[i])) {
System.out.println("Differing labels");
return;
}
}
// Check lengths
int shortCount = file1.getEntryCount();
if (file1.getEntryCount() != file2.getEntryCount()) {
System.out.println("Differing line counts.\n" +
file1.getName() + ": " + file1.getEntryCount() + "\n" +
file2.getName() + ": " + file2.getEntryCount());
shortCount = shortCount > file2.getEntryCount() ? file2.getEntryCount() : shortCount;
}
// Check elements
for (int i = 0; i < shortCount; i++) {
for (String label : labels1) {
if (!file1.getEntry(label, i).equals(file2.getEntry(label, i))) {
System.out.println("Differing entries on row " + i + ", column " + label + "\n" +
file1.getName() + ": " + file1.getEntry(label, i) + "\n" +
file2.getName() + ": " + file2.getEntry(label, i));
}
}
}
}
public String toString() {
return this.name + "(" + this.getEntryCount() + " entries)";
}
/**
* @see java.lang.Object#toString()
*/
public String toOutputString() {
String CRLF = "\r\n";
StringBuffer toReturn = new StringBuffer();
boolean evenColumns = true;
String[] labels = this.getLabels();
String toWrite;
// Get the amount of padding used, if any
int[] widths = new int[labels.length + 1];// All initialised to 0
ArrayList<String> column;
int pad;
// Loop over columns
for (int i = 0; i < labels.length; i++) {
pad = labels[i].length();
column = mainData.get(labels[i]);
// Loop over rows
for (int j = 0; j < this.getEntryCount(); j++) {
toWrite = column.get(j);
// If the string contains spaces, it needs to be wrapped in "
if (toWrite.indexOf(" ") != -1)
toWrite = "\"" + toWrite + "\"";
if (toWrite.length() > pad) pad = toWrite.length();
}
widths[i] = pad;
}
// The last entry in the array is used for the numbers column
widths[widths.length - 1] = new Integer(this.getEntryCount()).toString().length();
// Write the header and default lines
toReturn.append("2DA V2.0" + CRLF);
if (!defaultValue.equals(""))
toReturn.append("DEFAULT: " + defaultValue + CRLF);
else
toReturn.append(CRLF);
// Write the labels row using the original case
for (int i = 0; i < widths[widths.length - 1]; i++) toReturn.append(" ");
for (int i = 0; i < realLabels.size(); i++) {
toReturn.append(" " + realLabels.get(i));
for (int j = 0; j < widths[i] - realLabels.get(i).length(); j++) toReturn.append(" ");
}
toReturn.append((TLKEditCompatible ? " " : "") + CRLF);
// Write the data
for (int i = 0; i < this.getEntryCount(); i++) {
// Write the number row and it's padding
toReturn.append("" + i);
for (int j = 0; j < widths[widths.length - 1] - new Integer(i).toString().length(); j++)
toReturn.append(" ");
// Loop over columns
for (int j = 0; j < labels.length; j++) {
toWrite = mainData.get(labels[j]).get(i);
// If the string contains spaces, it needs to be wrapped in "
if (toWrite.indexOf(" ") != -1)
toWrite = "\"" + toWrite + "\"";
toReturn.append(" " + toWrite);
// Write padding
for (int k = 0; k < widths[j] - toWrite.length(); k++) toReturn.append(" ");
}
toReturn.append((TLKEditCompatible ? " " : "") + CRLF);
}
return toReturn.toString();
}
/**
* Makes an independent copy of this 2da.
*
* @see java.lang.Object#clone()
*/
@SuppressWarnings("unchecked")
public Object clone() {
// Make a sufficiently deep copy of the main data arrays
LinkedHashMap<String, ArrayList<String>> cloneData = new LinkedHashMap<String, ArrayList<String>>();
for (String key : this.getLabels()) // Use real labels to preserve order
cloneData.put(key, (ArrayList<String>) this.mainData.get(key).clone());
// Create a new Data_2da. The Strings are immutable, so they can be used as-is and clone()
// on an array produces a sufficiently deep copy right away
return new Data_2da(
this.name,
this.defaultValue,
(ArrayList<String>) this.realLabels.clone(),
cloneData
);
}
}