package jplagTutorial;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Vector;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
import java.rmi.RemoteException;
import com.sun.xml.rpc.client.ClientTransportException;
import com.sun.xml.rpc.util.exception.JAXRPCExceptionBase;
import javax.xml.rpc.handler.Handler;
import javax.xml.rpc.handler.HandlerChain;
import jplagTutorial.jplagClient.*;
import jplagTutorial.util.JPlagClientAccessHandler;
import jplagTutorial.util.ZipUtil;
public class ExampleClient {
/*
* Status constants
*/
public static final int JPLAG_UPLOADING = 0;
public static final int JPLAG_INQUEUE = 50;
public static final int JPLAG_PARSING = 100;
public static final int JPLAG_COMPARING = 200;
public static final int JPLAG_GENRESULT = 230;
public static final int JPLAG_PACKRESULT = 250;
public static final int JPLAG_DONE = 300;
public static final int JPLAG_ERROR = 400;
/*
* Login data
*/
private String username = null;
private String password = null;
/**
* Options for JPlag specified by the command line
*/
private Option option = new Option();
/**
* Name of directory where the result pages will be stored
*/
private String resultDirName = "result";
/**
* True, if the user wants to get a list of his submissions on the server
*/
private boolean listSubmissions = false;
/**
* The number of the submission to be downloaded plus 1 or 0
*/
private int downloadResultNumber = 0;
/**
* The number of the submission to be cancelled plus 1 or 0
*/
private int cancelSubmissionNumber = 0;
/**
* Suffix array generated from the suffix option or the language info
* suffix array
*/
private String[] suffixes = null;
/**
* Filename filter used by collectInDir()
*/
private FilenameFilter subdirFileFilter = null;
/**
* Current position of progress bar
*/
private int progressPos;
/**
* Maximum position of progress bar
*/
private int progressMax;
/**
* The stub for the JPlag Web Service
*/
private JPlagTyp_Stub stub = null;
/**
* Helper function to easily evaluate web service related exceptions
* @param e Exception thrown by a stub method
*/
public static void checkException(Exception e) {
if(e instanceof JPlagException) {
JPlagException je = (JPlagException) e;
System.out.println("JPlagException: " + je.getDescription()
+ "\n" + je.getRepair());
}
else if(e instanceof RemoteException) {
RemoteException re = (RemoteException) e;
Throwable cause = re.getCause();
if(cause != null && cause instanceof ClientTransportException) {
cause = ((JAXRPCExceptionBase) cause).getLinkedException();
if(cause != null) {
System.out.println("Connection exception: "
+ cause.getMessage());
return;
}
}
System.out.println("Unexpected RemoteException: "
+ re.getMessage());
re.printStackTrace();
}
else {
System.out.println("Unexpected Exception: " + e.getMessage());
e.printStackTrace();
}
}
/**
* Initializes the JPlag stub, by installing an all-trusting trust manager
* for the SSL connection to the server, instantiating a stub object and
* setting username and password
* @return True, if username and password have been set
*/
private boolean initJPlagStub() {
/*
* Create a trust manager that does not validate certificate chains
*/
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs,
String authType) {}
public void checkServerTrusted(X509Certificate[] certs,
String authType) {}
}
};
/*
* Install the all-trusting trust manager
*/
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
System.out.println("Warning: Unable to install all-trusting trust "
+ "manager! SSL connection may not work!");
}
/*
* Get JPlag client stub
*/
stub = (JPlagTyp_Stub) (new JPlagService_Impl()
.getJPlagServicePort());
/*
* Search for the JPlagClientAccessHandler in the handler chain
*/
HandlerChain handlerchain = stub._getHandlerChain();
Iterator handlers = handlerchain.iterator();
JPlagClientAccessHandler accessHandler = null;
while(handlers.hasNext()) {
Handler handler = (Handler) handlers.next();
if(handler instanceof JPlagClientAccessHandler) {
accessHandler = (JPlagClientAccessHandler)handler;
break;
}
}
if(accessHandler == null) {
System.out.println("Unable to find access handler! Cannot set "
+ "username and password!");
return false;
}
/*
* Initialize access handler
*/
accessHandler.setUserPassObjects(username, password);
return true;
}
/**
* Accepts directories and files with one of the given suffixes
*/
private class RecursiveFilenameFilter implements FilenameFilter {
public boolean accept(File dir, String name) {
if(new File(dir, name).isDirectory()) return true;
for(int i=0; i<suffixes.length; i++) {
if(name.endsWith(suffixes[i]))
return true;
}
return false;
}
}
/**
* Only accepts files with one of the given suffixes
*/
private class NonRecursiveFilenameFilter implements FilenameFilter {
public boolean accept(@SuppressWarnings("unused") File dir,
String name)
{
for(int i=0; i<suffixes.length; i++) {
if(name.endsWith(suffixes[i]))
return true;
}
return false;
}
}
/**
* Collects all valid files inside a directory. If subdirFileFilter also
* accepts directories, subdirectories are included in the search
* @param colfiles Vector receiving the found files
* @param dir The directory which will be searched
*/
private void collectInDir(Vector<File> colfiles, File dir) {
if(!dir.exists()) return;
File[] files = dir.listFiles(subdirFileFilter);
for(int i=0; i<files.length; i++) {
if(files[i].isDirectory()) {
collectInDir(colfiles, files[i]);
}
else colfiles.add(files[i]);
}
}
/**
* Collects all valid files according to the set options
* @return A Vector object of all valid files
*/
private Vector<File> collectFiles() {
Vector<File> colfiles = new Vector<File>();
File[] files = new File(option.getOriginalDir()).listFiles(
new RecursiveFilenameFilter());
if(files == null) {
System.out.println("\"" + option.getOriginalDir()
+ "\" is not a directory or an I/O error occurred!");
return null;
}
if(option.isReadSubdirs())
subdirFileFilter = new RecursiveFilenameFilter();
else
subdirFileFilter = new NonRecursiveFilenameFilter();
for(int i=0; i<files.length; i++) {
if(files[i].isDirectory()) {
if(option.getPathToFiles()!=null)
collectInDir(colfiles, new File(files[i],
option.getPathToFiles()));
else
collectInDir(colfiles, files[i]);
}
else colfiles.add(files[i]);
}
if(colfiles.size() <= 1) {
System.out.println("\"" + option.getOriginalDir()
+ "\" didn't contain at least two files\n"
+ "suitable for the specified options!");
return null;
}
return colfiles;
}
/**
* Creates a temporary zip file containing all files specified by the Option
* object and sends it to the server in 80 kB parts
* @return The submissionID string or null, if there was an error
*/
private String sendSubmission() {
Vector<File> submissionFiles = collectFiles();
if(submissionFiles == null) return null;
File zipfile = null;
FileInputStream input = null;
String submissionID = null;
try {
zipfile = File.createTempFile("jplagtmp",".zip");
ZipUtil.zipFilesTo(submissionFiles, option.getOriginalDir(),
zipfile);
input = new FileInputStream(zipfile);
int filesize = (int) zipfile.length();
int sentsize = 0;
int partsize = (filesize<81920) ? filesize : 81920;
byte[] data = new byte[partsize];
input.read(data);
initProgressBar(filesize);
StartSubmissionUploadParams params =
new StartSubmissionUploadParams(option, filesize, data);
submissionID = stub.startSubmissionUpload(params);
sentsize += partsize;
while(sentsize<filesize-partsize) {
setProgressBarValue(sentsize);
input.read(data);
stub.continueSubmissionUpload(data);
sentsize += partsize;
}
if(sentsize!=filesize) { // transfer last part
setProgressBarValue(sentsize);
data = new byte[filesize-sentsize];
input.read(data);
stub.continueSubmissionUpload(data);
sentsize = filesize;
}
setProgressBarValue(sentsize);
input.close();
zipfile.delete();
}
catch(Exception e) {
System.out.println();
checkException(e);
if(input != null) {
try { input.close(); } catch(Exception ex) {}
}
if(zipfile != null) zipfile.delete();
return null;
}
return submissionID;
}
/**
* Retrieves the status of the given submission
* @param submissionID submission ID identifying the submission
* @return The according status object or null on error
*/
private Status getStatus(String submissionID) {
Status status;
try {
status = stub.getStatus(submissionID);
}
catch(Exception e) {
checkException(e);
return null;
}
return status;
}
/**
* Waits until either the submission has been finished or an error occurred
* @param submissionID String identifying the submission
* @return True, if the submission has been completed successfully
*/
private boolean waitForResult(String submissionID) {
Status status;
try {
while(true) {
status = stub.getStatus(submissionID);
/*
* Here you could print out more details about the status of
* the submission, but it's left out here...
*/
if(status.getState() >= JPLAG_DONE) break;
Thread.sleep(10000); // wait 10 seconds
System.out.print("."); // tell user something's happening
}
if(status.getState() >= JPLAG_ERROR) {
/*
* An error occurred: Print out error message and acknowledge
* error by cancelling the submission
*/
System.out.println("\nSome error occurred: "
+ status.getReport());
stub.cancelSubmission(submissionID);
return false;
}
}
catch(Exception e) {
checkException(e);
return false;
}
return true;
}
/**
* Downloads and unzips the result
* @param submissionID The submission id String
* @return True on success
*/
private boolean receiveResult(String submissionID) {
File zipfile = null;
FileOutputStream output = null;
try {
File resultDir = new File(resultDirName);
if(!resultDir.exists()) resultDir.mkdirs();
zipfile = File.createTempFile("jplagtmpresult",".zip");
output = new FileOutputStream(zipfile);
StartResultDownloadData srdd = stub.startResultDownload(
submissionID);
int filesize = srdd.getFilesize();
int loadedsize = srdd.getData().length;
initProgressBar(srdd.getFilesize());
output.write(srdd.getData());
setProgressBarValue(loadedsize);
while(loadedsize<filesize) {
byte[] data = stub.continueResultDownload(0);
output.write(data);
loadedsize += data.length;
setProgressBarValue(loadedsize);
}
output.close();
/*
* Unzip result archive and delete the zip file
*/
ZipUtil.unzip(zipfile, resultDir);
zipfile.delete();
}
catch(Exception e) {
if(output != null) {
try { output.close(); } catch(Exception ex) {}
}
if(zipfile != null) zipfile.delete();
checkException(e);
return false;
}
return true;
}
/**
* Cancels the submission identified by submissionID
* @param submissionID The submission id string
* @return True on success
*/
private boolean cancelSubmission(String submissionID) {
try {
stub.cancelSubmission(submissionID);
}
catch(Exception e) {
checkException(e);
return false;
}
return true;
}
/**
* Checks the current options for validity using the information provided
* by the ServerInfo object and fills remaining empty fields with defaults.
* If "-l ?" or "-cl ?" is used, a list of valid languages respectively
* country languages is printed and false is returned.
* @param info ServerInfo object
* @return True, if all options are legal
*/
private boolean checkOptions(ServerInfo info) {
LanguageInfo[] languages = info.getLanguageInfos();
String[] countryLangs = info.getCountryLanguages();
int i;
if(option.getLanguage() == null) {
i = 0;
option.setLanguage(languages[0].getName());
System.out.println("Using default language: "
+ languages[0].getName());
}
else {
for(i=0; i<languages.length; i++) {
if(option.getLanguage().equals(languages[i].getName())) break;
}
if(i==languages.length) {
if(!option.getLanguage().equals("?"))
System.out.println("Unknown language: \""
+ option.getLanguage() + "\"");
System.out.println("\nAvailable languages:");
for(i=0; i<languages.length; i++) {
System.out.println(" - \"" + languages[i].getName()
+ "\"" + (i==0 ? " (default language)\n" : "\n")
+ " default minimum match length = "
+ languages[i].getDefMinMatchLen()
+ "\n default suffixes: "
+ arrayToString(languages[i].getSuffixes()));
}
return false;
}
}
if(suffixes == null) {
suffixes = languages[i].getSuffixes();
System.out.println("Using default suffixes: "
+ arrayToString(suffixes));
}
if(option.getTitle() == null)
option.setTitle("submission-"
+ new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
if(option.getCountryLang() == null)
option.setCountryLang("en");
else {
for(i=0; i<countryLangs.length; i++) {
if(option.getCountryLang().equals(countryLangs[i])) break;
}
if(i==countryLangs.length) {
if(!option.getCountryLang().equals("?"))
System.out.println("Unknown country language: \""
+ option.getCountryLang() + "\"");
System.out.println("\nAvailable country languages:");
for(i=0; i<countryLangs.length; i++) {
System.out.println(" - \"" + countryLangs[i]
+ (i==0 ? "\" (default)" : "\""));
}
return false;
}
}
return true;
}
/**
* Parses the arguments and sets the appropriate attributes
* @param args Array of argument strings
* @return True, if no error was noticed like missing login data
*/
private boolean parseArguments(String[] args) {
boolean requestDetails = false;
for(int i=0; i<args.length; i++) {
if(args[i].equals("-user") && i+1<args.length) {
i++;
username = args[i];
}
else if(args[i].equals("-pass") && i+1<args.length) {
i++;
password = args[i];
}
else if(args[i].equals("-l") && i+1<args.length) {
i++;
option.setLanguage(args[i]);
if(args[i].equals("?")) requestDetails = true;
}
else if(args[i].equals("-cl") && i+1<args.length) {
i++;
option.setCountryLang(args[i]);
if(args[i].equals("?")) requestDetails = true;
}
else if(args[i].equals("-s")) {
option.setReadSubdirs(true);
}
else if(args[i].equals("-S") && i+1<args.length) {
i++;
option.setPathToFiles(args[i]);
}
else if(args[i].equals("-p") && i+1<args.length) {
i++;
suffixes = args[i].split(",");
option.setSuffixes(suffixes);
}
else if(args[i].equals("-t") && i+1<args.length) {
i++;
try {
option.setMinimumMatchLength(Integer.parseInt(args[i]));
}
catch(NumberFormatException e) {
System.out.println("Illegal minimum match length: "
+ args[i] + "\nMust be an integer!");
return false;
}
}
else if(args[i].equals("-m") && i+1<args.length) {
i++;
option.setStoreMatches(args[i]);
}
else if(args[i].equals("-bc") && i+1<args.length) {
i++;
option.setBasecodeDir(args[i]);
}
else if(args[i].equals("-r") && i+1<args.length) {
i++;
resultDirName = args[i];
}
else if(args[i].equals("-title") && i+1<args.length) {
i++;
option.setTitle(args[i]);
}
else if(args[i].equals("-list")) {
listSubmissions = true;
}
else if(args[i].equals("-download")) {
if(i+1<args.length && Character.isDigit(args[i+1].charAt(0))) {
// isDigit is true => positive value
i++;
try {
downloadResultNumber = Integer.parseInt(args[i]);
}
catch(NumberFormatException e) {
System.out.println("Illegal download number: "
+ args[i] + "\nMust be an positive integer!");
return false;
}
}
else downloadResultNumber = 1;
}
else if(args[i].equals("-cancel")) {
if(i+1<args.length && Character.isDigit(args[i+1].charAt(0))) {
// isDigit is true => positive value
i++;
try {
cancelSubmissionNumber = Integer.parseInt(args[i]);
}
catch(NumberFormatException e) {
System.out.println("Illegal cancel number: "
+ args[i] + "\nMust be an positive integer!");
return false;
}
}
else cancelSubmissionNumber = 1;
}
else if(args[i].startsWith("-")) {
System.out.println("Unknown option: " + args[i]);
return false;
}
else
{
if(option.getOriginalDir() != null) {
System.out.println("The rootdir has already been defined "
+ "as \"" + option.getOriginalDir() + "\"!");
return false;
}
option.setOriginalDir(args[i]);
}
}
boolean valid = true;
if(username == null) {
System.out.println("Username is missing!");
valid = false;
}
if(password == null) {
System.out.println("Password is missing!");
valid = false;
}
if(!requestDetails && !listSubmissions && downloadResultNumber == 0
&& cancelSubmissionNumber == 0
&& option.getOriginalDir() == null) {
System.out.println("You must specify either a \"root-dir\", the "
+ "\"-list\", the \"-download\"\nor the \"-cancel\" option!");
valid = false;
}
return valid;
}
/**
* Initializes a text progress bar
*/
public void initProgressBar(int max) {
progressMax = max;
progressPos = 0;
System.out.println(
"0%----------+----------50%-----------+--------100%");
}
/**
* Sets the current value of the progress bar updating the current position
* The progress may only increase, not decrease!
*/
public void setProgressBarValue(int val) {
int pos = (val*50)/progressMax;
if(pos <= progressPos) return;
System.out.print("##################################################"
.substring(progressPos, pos));
progressPos = pos;
}
/**
* Prints out how to use the program
*/
public static void printUsage() {
System.out.println(
"\nUsage: ExampleClient [options] (<root-dir> | -list | -download [<n>]\n"
+ " | -cancel [<n>])\n"
+ "<root-dir> The directory which contains all programs\n"
+ "Options are:\n"
+ " -user <username> Sets the username (required).\n"
+ " -pass <password> Sets the password (required).\n"
+ " -l <language> (Language) Programming language.\n"
+ " (\"-l ?\" for supported and default languages.\n"
+ " Also lists default suffixes and minimum match length)\n"
+ " -S <dir> Look in directories <root-dir>/*/<dir> for programs.\n"
+ " (default: <root-dir>/*)\n"
+ " -s (Subdirs) Look at files in subdirs, too. (default: disabled)\n"
+ " -p <suffixes> <suffixes> is a comma-separated list of filename suffixes\n"
+ " to be included. (default: language specific)\n"
+ " -t <n> (Token) Set the minimum match length in tokens.\n"
+ " A smaller <n> increases the sensitivity of the comparison.\n"
+ " -m <n> (Matches) Number of matches that will be saved. (default:20)\n"
+ " -m <p>% Saves all matches with more than <p>% average similitarity.\n"
+ " -bc <dir> Name of the directory containing the basecode\n"
+ " (common framework).\n"
+ " -r <dir> (Result) Name of directory where the result pages will\n"
+ " be stored. (default: result)\n"
+ " -title <title> Title of this submission (default: submission-<date>)\n"
+ " -cl <locale> (Country language) Language the result files will\n"
+ " be written in.\n"
+ " (\"-cl ?\" for supported country languages and default)\n"
+ " -list Lists all submissions on the server belonging to the user.\n"
+ " -download [<n>] Downloads the <n>-th submission from server.\n"
+ " The <n>-th submission must be \"done\".\n"
+ " All non required options except \"-r <dir>\" will be ignored.\n"
+ " -cancel [<n>] Cancels the <n>-th submission on server.\n"
+ " All non required options will be ignored.\n");
}
/**
* Concatenates the string representations of objects in an array
* @param array Object array
* @return Comma-separated list of string representations of those objects
*/
private String arrayToString(Object[] array) {
String str = "";
for(int i=0; i<array.length; i++) {
str += array[i].toString();
if(i!=array.length-1) str += ",";
}
return str;
}
/**
* The main routine
* @param args Array of command line parameters
*/
public void run(String[] args) {
if(args.length == 0 || !parseArguments(args)) {
printUsage();
return;
}
if(!initJPlagStub()) {
System.out.println("Unable to initialize JPlag stub!");
return;
}
/*
* Get a ServerInfo object
*/
ServerInfo info;
try {
info = stub.getServerInfo();
}
catch(Exception e) {
checkException(e);
return;
}
/*
* Check for submissions on server by looking at submissions field
* in the ServerInfo object
*/
Submission[] subs = info.getSubmissions();
if(subs.length > 0) {
System.out.println("\nSubmissions on server with states:\n");
for(int i=0; i<subs.length; i++) {
System.out.println(" " + (i+1) + ". \"" + subs[i].getTitle()
+ "\" on " + subs[i].getDate());
String stateString = "";
switch(subs[i].getLastState()) {
case JPLAG_UPLOADING: // can not occur in this application
stateString = "uploading";
break;
case JPLAG_INQUEUE: stateString = "in queue"; break;
case JPLAG_PARSING: stateString = "parsing"; break;
case JPLAG_COMPARING: stateString = "comparing"; break;
case JPLAG_GENRESULT:
stateString = "generating result files";
break;
case JPLAG_PACKRESULT:
stateString = "packing result files";
break;
case JPLAG_DONE: stateString = "done"; break;
default: {
// an error occurred, so get more details
Status status = getStatus(subs[i].getSubmissionID());
if(status == null)
stateString = "unable to retrieve status";
else
stateString = status.getReport();
}
}
System.out.println(" (" + stateString + ")");
}
if(listSubmissions) return;
/*
* If "-download" is used, download the n-th submission
*/
if(downloadResultNumber != 0) {
if(downloadResultNumber > subs.length) {
System.out.println("Illegal download number!\n"
+ "There are only " + subs.length + " submissions!");
return;
}
Submission sub = subs[downloadResultNumber-1];
if(sub.getLastState() != 300) {
System.out.println("Illegal download number!\n"
+ "You can only download results for successfully"
+ " finished submissions!");
return;
}
System.out.print("Downloading \"" + sub.getTitle()
+ "\"...");
if(!receiveResult(sub.getSubmissionID())) return;
System.out.println(" completed.\nThe result files are available"
+ " in \"" + resultDirName + "\"");
return;
}
/*
* If "-cancel" is used, cancel the n-th submission
*/
if(cancelSubmissionNumber != 0) {
if(cancelSubmissionNumber > subs.length) {
System.out.println("Illegal cancel number!\n"
+ "There are only " + subs.length + " submissions!");
return;
}
Submission sub = subs[cancelSubmissionNumber-1];
System.out.print("Cancelling \"" + sub.getTitle()
+ "\"...");
if(!cancelSubmission(sub.getSubmissionID())) return;
System.out.println(" completed.\n");
return;
}
}
else if(downloadResultNumber != 0 || cancelSubmissionNumber != 0
|| listSubmissions) {
System.out.println("\nCurrently there are no submissions on the "
+ "server for this user!");
return;
}
if(!checkOptions(info)) return;
System.out.println("\nSending files...");
String submissionID = sendSubmission();
if(submissionID == null) return;
System.out.print("\n\nWaiting for result...");
if(!waitForResult(submissionID)) return;
System.out.println(" result available.\n\nDownloading...");
if(!receiveResult(submissionID)) return;
System.out.println("\n\nThe result files are available in \""
+ resultDirName + "\"");
}
public static void main(String[] args) {
new ExampleClient().run(args);
}
}