/*
*/
/**
*
* @author atol systems
*/
package com.atolsystems.atolutilities;
import com.atolsystems.atolutilities.CommandLine.Arg;
import com.atolsystems.atolutilities.CommandLine.ArgDef;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.filechooser.FileNameExtensionFilter;
public class aCat {
static private final String DSTFILE_PATH_PREFERENCE_KEY = "com.atolsystems.atolutilities.aCat.dstFilePath";
static private final String INPUTFILE_PATH_PREFERENCE_KEY = "com.atolsystems.atolutilities.aCat.inputFilePath";
/**
* An utility to concatenate several files into one
* @param args
*/
public static void main(String[] args){
aCat u=new aCat(args);
}
public static void showHelp(CommandLine cl){
java.util.ResourceBundle bundle1 = java.util.ResourceBundle.getBundle("com/atolsystems/atolutilities/resources/BuildNumber");
System.out.println("\n\naCat v0.3 ("+"build "+bundle1.getString("build.number")+"), atol systems, 2011");
System.out.println("Utility to concatenate several files into one, with various optional codecs");
System.out.println("This program is currently run by the JVM from:");
System.out.println("\t"+System.getProperty("java.home"));
System.out.println("\nCommand line usage:");
System.out.println(cl.help());
System.out.println();
}
File dstFile;
File tmpOutFile;
boolean append;
boolean internalAppend;
boolean outHex;
boolean inHex;
boolean out64;
boolean in64;
boolean askOutKey;
boolean askInKey;
String outKey;
String inKey;
public aCat(String[] args) {
try {
CommandLine cl=new CommandLine();
HelpArgHandler helpArgHandler=new HelpArgHandler();
cl.addArgDef(helpArgHandler.argDef);
GuiArgHandler guiArgHandler=new GuiArgHandler();
cl.addArgDef(guiArgHandler.argDef);
DstArgHandler setDstArgHandler=new DstArgHandler();
cl.addArgDef(setDstArgHandler.argDef);
OutHexArgHandler outHexArgHandler=new OutHexArgHandler();
cl.addArgDef(outHexArgHandler.argDef);
InHexArgHandler inHexArgHandler=new InHexArgHandler();
cl.addArgDef(inHexArgHandler.argDef);
Out64ArgHandler out64ArgHandler=new Out64ArgHandler();
cl.addArgDef(out64ArgHandler.argDef);
In64ArgHandler in64ArgHandler=new In64ArgHandler();
cl.addArgDef(in64ArgHandler.argDef);
OutKeyArgHandler outKeyArgHandler=new OutKeyArgHandler();
cl.addArgDef(outKeyArgHandler.argDef);
InKeyArgHandler inKeyArgHandler=new InKeyArgHandler();
cl.addArgDef(inKeyArgHandler.argDef);
AppendOnArgHandler appendOnArgHandler=new AppendOnArgHandler();
cl.addArgDef(appendOnArgHandler.argDef);
AppendOffArgHandler appendOffArgHandler=new AppendOffArgHandler();
cl.addArgDef(appendOffArgHandler.argDef);
AddArgHandler addArgHandler=new AddArgHandler();
cl.addArgDef(addArgHandler.argDef);
FileListArgHandler fileListArgHandler=new FileListArgHandler();
cl.addArgDef(fileListArgHandler.argDef);
if(0==args.length){
FileNameExtensionFilter filter = new FileNameExtensionFilter("aCat arg files", "aCat");
cl.askForArgFile(filter, true, true, true);
}else
cl.addNewArgs(args);
if(cl.isEmpty()){
showHelp(cl);
}else{
cl.processArgs();
}
if(null!=tmpOutFile)
commitToOutputFile();
} catch(ExitException e){
System.out.println(e.getMessage());
} catch (StopRequestFromUserException e) {
} catch (IOException ex) {
Logger.getLogger(CommandLine.class.getName()).log(Level.SEVERE, null, ex);
}
}
class GuiArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="gui";
final public ArgDef argDef=new ArgDef(mark, this);
public String help() {
return "gui\n"+
" Start graphical user interface mode";
}
public boolean processArg(Arg arg, CommandLine cl) {
if(null==dstFile){
try {
dstFile = AFileChooser.askForFile(null, true, "Choose destination file or 'Cancel' to exit", DSTFILE_PATH_PREFERENCE_KEY, true, false, true);
if(null==dstFile)
throw new StopRequestFromUserException();
} catch (IOException ex) {
Logger.getLogger(aCat.class.getName()).log(Level.SEVERE, null, ex);
}
if(dstFile.exists()){
String actions[]={"append to file","overwrite file"};
String s = (String) JOptionPane.showInputDialog(
null,
"Choose an action or 'Cancel' to exit.",
"Selected file exist",
JOptionPane.PLAIN_MESSAGE,
null,
actions,
actions[0]);
if(null==s)
throw new StopRequestFromUserException();
if(s.equals(actions[0]))
internalAppend=true;
else
internalAppend=false;
}
}
while(true){
File[] input=null;
try {
input = AFileChooser.askForFiles(null, true, "Choose an input file to append or 'Cancel' to exit", INPUTFILE_PATH_PREFERENCE_KEY, true, true, false, false);
} catch (IOException ex) {
Logger.getLogger(aCat.class.getName()).log(Level.SEVERE, null, ex);
}
if(null==input)
break;
for(int i=0;i<input.length;i++){
processInputFile(input[i]);
internalAppend=true;
}
}
return true;
}
}
class HelpArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="help";
final public ArgDef argDef=new ArgDef(mark, this);
public String help() {
return "help\n"+
" Show this help";
}
public boolean processArg(Arg arg, CommandLine cl) {
showHelp(cl);
return true;
}
}
class AppendOnArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="appendOn";
final public ArgDef argDef=new ArgDef(mark, this);
public String help() {
return "appendOn\n"+
" Enable \"append\" mode: concatenated files are appended to destination file";
}
public boolean processArg(Arg arg, CommandLine cl) {
append=true;
internalAppend=append;
return true;
}
}
class AppendOffArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="appendOff";
final public ArgDef argDef=new ArgDef(mark, this);
public String help() {
return "appendOff\n"+
" Disable \"append\" mode: concatenated files replace the destination file";
}
public boolean processArg(Arg arg, CommandLine cl) {
append=false;
internalAppend=append;
return true;
}
}
class OutHexArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="outHex";
final public ArgDef argDef=new ArgDef(mark, this);
public String help() {
return "outHex\n"+
" Output an intel-hex file.";
}
public boolean processArg(Arg arg, CommandLine cl) {
outHex=true;
return true;
}
}
class InHexArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="inHex";
final public ArgDef argDef=new ArgDef(mark, this);
public String help() {
return "inHex\n"+
" Read input files as intel-hex files, offset and unspecified area are discarded.";
}
public boolean processArg(Arg arg, CommandLine cl) {
inHex=true;
return true;
}
}
class Out64ArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="out64";
final public ArgDef argDef=new ArgDef(mark, this);
public String help() {
return "out64\n"+
" Use base64 encoding for output file.";
}
public boolean processArg(Arg arg, CommandLine cl) {
out64=true;
return true;
}
}
class In64ArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="in64";
final public ArgDef argDef=new ArgDef(mark, this);
public String help() {
return "in64\n"+
" Use base64 encoding for input files.";
}
public boolean processArg(Arg arg, CommandLine cl) {
in64=true;
return true;
}
}
class OutKeyArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="outKey";
final public ArgDef argDef=new ArgDef(mark, this);
public String help() {
return "outKey\n"+
" Ask for a password to generate an AES encryption key to encrypt the output file.";
}
public boolean processArg(Arg arg, CommandLine cl) {
askOutKey=true;
return true;
}
}
class InKeyArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="inKey";
final public ArgDef argDef=new ArgDef(mark, this);
public String help() {
return "inKey\n"+
" Ask for a password to generate a decryption key to decrypt the input files.";
}
public boolean processArg(Arg arg, CommandLine cl) {
askInKey=true;
return true;
}
}
class DstArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="dst:";
final public ArgDef argDef=new ArgDef(mark, this);
public String help() {
return "dst:[file]\n"+
" Set destination file. If file is omitted, a dialog box ask for it";
}
public boolean processArg(Arg arg, CommandLine cl) {
if(null!=dstFile) commitToOutputFile();
dstFile=arg.getOptionalFile(mark, null, "Select destination file", DSTFILE_PATH_PREFERENCE_KEY, true);
try {
tmpOutFile = File.createTempFile("aCat", "aCatTmp");
} catch (IOException ex) {
throw new RuntimeException("Cannot create temp file.",ex);
}
internalAppend=append;
if(null==dstFile) throw new RuntimeException("Cannot find file '"+arg.getSingleFileName(mark)+"'");
return true;
}
}
class AddArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="add:";
final public ArgDef argDef=new ArgDef(mark, this);
ArrayList<File> files=new ArrayList<File>();
int iFiles=0;
public String help() {
return "add:[file]\n"+
" Append a file. If file is omitted, a dialog box ask for it";
}
public boolean processArg(Arg arg, CommandLine cl) {
try {
File file=files.get(iFiles);
iFiles++;
internalAppend=true;
if(null!=file)//if user pressed "cancel" for an optional file, file==null here
processInputFile(file);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return true;
}
@Override
public boolean inspectArg(Arg arg, CommandLine cl) {
File toAdd=arg.getOptionalFile(mark, null, "Add a file to concatenate", INPUTFILE_PATH_PREFERENCE_KEY, false);
//if(null==toAdd) throw new ExitException("Cannot find file '"+arg.getSingleFileName(mark)+"'");
files.add(toAdd);
return true;
}
}
class FileListArgHandler extends SimpleArgHandler implements SelfDocumented{
final public String mark="fileList:";
final public ArgDef argDef=new ArgDef(mark, this);
ArrayList<ArrayList<File>> files=new ArrayList<ArrayList<File>>();
int iFiles=0;
public String help() {
return "fileList:[file]\n"+
" Add a file list: a file which contain one file name per line\n"+
" Each file listed is concatenated to the destination file\n"+
" To include a file list with a file list, start its line with \"<\"\n"+
" If file is omitted, a dialog box ask for it\n";
}
public boolean processArg(Arg arg, CommandLine cl) {
for(File file:files.get(iFiles)){
processInputFile(file);
internalAppend=true;
}
iFiles++;
return true;
}
HashSet<String> fileListSet=new HashSet<String>();
void processFileList(File fileListFile) throws FileNotFoundException, IOException{
String line;
File refDir=fileListFile.getParentFile();
fileListSet.add(fileListFile.getCanonicalPath());
BufferedReader in = new BufferedReader(new FileReader(fileListFile));
line=in.readLine().trim();
while(null!=line){
if(line.startsWith("<")){
fileListFile=AFileUtilities.newFile(line.substring(1));
if(fileListSet.contains(fileListFile.getCanonicalPath()))
throw new InvalidCommandLineException("Circular reference in file list '"+fileListFile.getCanonicalPath()+"'");
processFileList(fileListFile);
}else if (line.startsWith("//") || line.isEmpty()) {
//do nothing
} else if (line.startsWith("/*")) {
int open = 1;
while(null!=line){
line=in.readLine().trim();
if (line.startsWith("/*")) {
open++;
} else if (line.startsWith("*/")) {
open--;
}
if (0 == open) {
break;
}
}
if (0 != open) {
throw new InvalidCommandLineException("Unterminated multiline comment found in list file '"+fileListFile.getCanonicalPath()+"'");
}
} else{
//File toAdd=new File(refDir,line);
File toAdd=AFileUtilities.newFile(refDir,line);
files.get(files.size()-1).add(toAdd);
}
line=in.readLine();
}
in.close();
}
@Override
public boolean inspectArg(Arg arg, CommandLine cl) {
File fileListFile=arg.getOptionalFile(mark, null, "Choose a \"file list\" file", INPUTFILE_PATH_PREFERENCE_KEY, false);
try {
fileListSet.clear();
ArrayList<File> newFileList=new ArrayList<File>();
files.add(newFileList);
if(null!=fileListFile)
processFileList(fileListFile);
} catch (FileNotFoundException ex) {
Logger.getLogger(aCat.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(aCat.class.getName()).log(Level.SEVERE, null, ex);
}
return true;
}
}
void processInputFile(File inputFile){
try {
AFileUtilities.copyFile(tmpOutFile, inputFile, internalAppend);
} catch (Throwable ex) {
throw new RuntimeException(ex);
}
}
private void commitToOutputFile(){
try {
if (null == dstFile) {
if (null == tmpOutFile) {
return; //nothing to do
}
throw new RuntimeException("Ouptut file not set.");
}
//File tmpFilePostProcess=tmpOutFile;
File tmpFilePostProcess=File.createTempFile("aCat", "aCatTmp");
File tmpOutFileBu=tmpOutFile;
//not the fastest way, should open the input do all transformation using streams...
if(in64){
AFileUtilities.copyFile(tmpFilePostProcess, tmpOutFile, false, false, true);
tmpOutFile=tmpFilePostProcess;
tmpFilePostProcess=tmpOutFileBu;
tmpOutFileBu=tmpOutFile;
}
if(inHex){
IntelHexFileReader.hex2Bin(tmpFilePostProcess, tmpOutFile, false);
tmpOutFile=tmpFilePostProcess;
tmpFilePostProcess=tmpOutFileBu;
tmpOutFileBu=tmpOutFile;
}
if(askInKey){
byte [] key=askKey();
AFileUtilities.copyFile(tmpFilePostProcess, tmpOutFile, false, key, false);
Arrays.fill(key,(byte)0);
tmpOutFile=tmpFilePostProcess;
tmpFilePostProcess=tmpOutFileBu;
tmpOutFileBu=tmpOutFile;
}
if(askOutKey){
byte [] key=askKey();
AFileUtilities.copyFile(tmpFilePostProcess, tmpOutFile, false, key, true);
Arrays.fill(key,(byte)0);
tmpOutFile=tmpFilePostProcess;
tmpFilePostProcess=tmpOutFileBu;
tmpOutFileBu=tmpOutFile;
}
if(outHex){
IntelHexFileWriter.bin2Hex(tmpFilePostProcess, tmpOutFile, false, 8);
tmpOutFile=tmpFilePostProcess;
tmpFilePostProcess=tmpOutFileBu;
tmpOutFileBu=tmpOutFile;
}
if(out64){
AFileUtilities.copyFile(tmpFilePostProcess, tmpOutFile, false, true, false);
tmpOutFile=tmpFilePostProcess;
tmpFilePostProcess=tmpOutFileBu;
tmpOutFileBu=tmpOutFile;
}
AFileUtilities.copyFile(dstFile, tmpOutFile, append);
tmpOutFile.delete();
tmpOutFile = null;
tmpFilePostProcess.delete();
tmpFilePostProcess=null;
dstFile = null;
} catch (Throwable ex) {
throw new RuntimeException(ex);
}
}
private byte[] askKey(){
JPasswordField password = new JPasswordField();
JCheckBox restrictTo128Bits = new JCheckBox("Restrict key generation to 128 bits");
final JComponent[] inputs = new JComponent[] {
new JLabel("Password"),
password,
restrictTo128Bits
};
JOptionPane.showMessageDialog(null, inputs, "Password", JOptionPane.PLAIN_MESSAGE);
int keySize=(restrictTo128Bits.isSelected()) ? 16 : 32;
return stringToAesKey(password.getPassword(), keySize);
}
private byte[] stringToAesKey(char[] password, int keySize){
byte [] passwordTrailerBytes=new byte[32];//add 32 null bytes to make sure the resulting key can reach 256 bits
byte [] passwordBytes = new byte[password.length+passwordTrailerBytes.length];
System.arraycopy(passwordTrailerBytes, 0, passwordBytes, password.length, passwordTrailerBytes.length);
for(int i=0;i<password.length;i++){
passwordBytes[i]=(byte) password[i];
}
byte [] key=new byte[16];
byte[] extendedKey;
try {
extendedKey = AES.performAES(key, passwordBytes, true);
} catch (InvalidKeyException ex) {
throw new RuntimeException(ex);
}
byte[] output = new byte[keySize];
for(int i=0;i<extendedKey.length;i++)
output[i%keySize]^=extendedKey[i];
Arrays.fill(password,'0');
Arrays.fill(passwordBytes,(byte)0);
Arrays.fill(extendedKey,(byte)0);
//System.out.println(AStringUtilities.bytesToHex(output));
return output;
}
}