├── .travis.yml ├── src ├── site │ ├── resources │ │ └── images │ │ │ └── moji.png │ ├── apt │ │ ├── download.apt.vm │ │ └── quickstart.apt │ ├── site.xml │ ├── xdoc │ │ └── changelog.xml │ └── fml │ │ └── faq.fml ├── test │ ├── resources │ │ └── testset-UKR │ │ │ └── 1 │ │ │ └── 1.cpp │ └── java │ │ └── it │ │ └── zielke │ │ └── moji │ │ ├── ExpectedValues.java │ │ ├── EncodingTest.java │ │ ├── SocketClientTestBase.java │ │ ├── SocketClientCommandsTest.java │ │ ├── MockServer.java │ │ └── SocketClientUploadTest.java └── main │ └── java │ └── it │ └── zielke │ └── moji │ ├── Stage.java │ ├── MossException.java │ └── SocketClient.java ├── example └── MojiEclipseQuickStart │ ├── lib │ ├── moji-1.0.2.jar │ └── commons-io-2.3.jar │ ├── .settings │ └── org.jboss.ide.eclipse.as.core.prefs │ ├── .classpath │ ├── .project │ └── src │ └── example │ └── QuickStart.java ├── .gitignore ├── LICENSE.txt ├── README.md └── pom.xml /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: oraclejdk7 3 | -------------------------------------------------------------------------------- /src/site/resources/images/moji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nordicway/moji/HEAD/src/site/resources/images/moji.png -------------------------------------------------------------------------------- /src/test/resources/testset-UKR/1/1.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nordicway/moji/HEAD/src/test/resources/testset-UKR/1/1.cpp -------------------------------------------------------------------------------- /example/MojiEclipseQuickStart/lib/moji-1.0.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nordicway/moji/HEAD/example/MojiEclipseQuickStart/lib/moji-1.0.2.jar -------------------------------------------------------------------------------- /example/MojiEclipseQuickStart/lib/commons-io-2.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nordicway/moji/HEAD/example/MojiEclipseQuickStart/lib/commons-io-2.3.jar -------------------------------------------------------------------------------- /example/MojiEclipseQuickStart/.settings/org.jboss.ide.eclipse.as.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.jboss.ide.eclipse.as.core.singledeployable.deployableList= 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project/Java/Eclipse related 2 | .settings/** 3 | .project 4 | .classpath 5 | .metadata 6 | bin/** 7 | .svn/** 8 | target/** 9 | .ignore 10 | *.log 11 | 12 | # OS specific files 13 | .DS_Store 14 | .DS_Store? 15 | .Trashes 16 | ehthumbs.db 17 | Thumbs.db 18 | /target 19 | -------------------------------------------------------------------------------- /example/MojiEclipseQuickStart/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/MojiEclipseQuickStart/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | MojiEclipseQuickstart 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/it/zielke/moji/Stage.java: -------------------------------------------------------------------------------- 1 | package it.zielke.moji; 2 | 3 | /** 4 | * Internal stage of the client. Describes the imperative nature of the MOSS 5 | * service, eg. the client cannot ask the server for results if no files have 6 | * been sent. 7 | * 8 | * @see SocketClient#getCurrentStage() 9 | * 10 | */ 11 | public enum Stage { 12 | 13 | DISCONNECTED, AWAITING_INITIALIZATION, AWAITING_LANGUAGE, AWAITING_FILES, AWAITING_QUERY, AWAITING_RESULTS, AWAITING_END; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/site/apt/download.apt.vm: -------------------------------------------------------------------------------- 1 | -------- 2 | Download 3 | -------- 4 | 5 | Download 6 | 7 | * {{{./downloads/moji-${project.version}.jar}Download MOJI ${project.version} JAR}} 8 | 9 | * {{{./downloads/moji-${project.version}-javadoc.jar}Download MOJI ${project.version} Javadoc JAR}} 10 | 11 | * {{{./downloads/moji-${project.version}-sources.jar}Download MOJI ${project.version} Sources JAR}} 12 | 13 | 14 | If you don't know the difference between these files, just download the first one. -------------------------------------------------------------------------------- /src/main/java/it/zielke/moji/MossException.java: -------------------------------------------------------------------------------- 1 | package it.zielke.moji; 2 | 3 | public class MossException extends Exception { 4 | private static final long serialVersionUID = -3296167322751430755L; 5 | private Exception origin; 6 | 7 | public MossException(String string) { 8 | super(string); 9 | } 10 | 11 | public MossException(String string, Exception origin) { 12 | this(string); 13 | this.setOrigin(origin); 14 | } 15 | 16 | public Exception getOrigin() { 17 | return origin; 18 | } 19 | 20 | public void setOrigin(Exception origin) { 21 | this.origin = origin; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Bjoern Zielke 2 | 3 | Permission to use, copy, modify, and distribute this software and its 4 | documentation for any purpose, without fee, and without a written 5 | agreement is hereby granted, provided that the above copyright notice 6 | and this paragraph and the following two paragraphs appear in all 7 | copies. 8 | 9 | IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE TO ANY PARTY FOR DIRECT, 10 | INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING 11 | LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS 12 | DOCUMENTATION, EVEN IF THE AUTHOR OR CONTRIBUTORS HAVE BEEN ADVISED OF THE 13 | POSSIBILITY OF SUCH DAMAGE. 14 | 15 | THE AUTHOR OR CONTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT 16 | NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 17 | FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS 18 | IS" BASIS, AND THE AUTHOR OR CONTRIBUTORS HAVE NO OBLIGATIONS TO PROVIDE 19 | MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. -------------------------------------------------------------------------------- /src/test/java/it/zielke/moji/ExpectedValues.java: -------------------------------------------------------------------------------- 1 | package it.zielke.moji; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Represents a combination of file and byte strings as known-good values for 7 | * testing binary compatibility with the Moss client. 8 | * 9 | */ 10 | public class ExpectedValues { 11 | 12 | private File file; 13 | 14 | private int bytesLength; 15 | 16 | private String bytesHex; 17 | 18 | public File getFile() { 19 | return file; 20 | } 21 | 22 | public ExpectedValues setFile(File file) { 23 | this.file = file; 24 | return this; 25 | } 26 | 27 | public int getBytesLength() { 28 | return bytesLength; 29 | } 30 | 31 | public ExpectedValues setBytesLength(int bytesLength) { 32 | this.bytesLength = bytesLength; 33 | return this; 34 | } 35 | 36 | public String getBytesHex() { 37 | return bytesHex; 38 | } 39 | 40 | public ExpectedValues setBytesHex(String bytesHex) { 41 | this.bytesHex = bytesHex.replaceAll("[^A-Za-z0-9]", "").toLowerCase(); 42 | return this; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/site/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.apache.maven.skins 4 | maven-fluido-skin 5 | 1.3.0 6 | 7 | 8 | 9 | true 10 | false 11 | 12 | true 13 | 14 | 15 | 16 | multiplag-logo 17 | images/moji.png 18 | MOJI 19 | http://www.zielke.it/moji/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/site/xdoc/changelog.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CHANGES 6 | 7 | 8 | 9 |
10 |

Dates are m/d/yyyy

11 | 12 |
    13 |
  • Fixed "connection reset" issue encountered when uploading files with advanced encodings
  • 14 |
15 |
16 | 17 |
    18 |
  • This is a maintenance release without any changes in functionality.
  • 19 |
  • MOJI is now on Maven Central!
  • 20 |
21 |
22 | 23 |
    24 |
  • Added base file support
  • 25 |
  • Added an example project for Eclipse
  • 26 |
  • Bumped version to 1.0 since it has been used in production for a long time now.
  • 27 |
28 |
29 | 30 |
    31 |
  • Initial public release
  • 32 |
  • Added stages to better reflect the logic of the service. Methods will throw runtime exceptions if you try to call them in the wrong order.
  • 33 |
  • Added documentation
  • 34 |
35 |

Known issues:

36 |
    37 |
  • Unlike the original Perl client, this version does not support uploading base files yet.
  • 38 |
39 |
40 | 41 |

Initial private release.

42 |
43 |
44 | 45 |
46 | -------------------------------------------------------------------------------- /example/MojiEclipseQuickStart/src/example/QuickStart.java: -------------------------------------------------------------------------------- 1 | package example; 2 | 3 | import it.zielke.moji.SocketClient; 4 | 5 | import java.io.File; 6 | import java.net.URL; 7 | import java.util.Collection; 8 | 9 | import org.apache.commons.io.FileUtils; 10 | 11 | public class QuickStart { 12 | public static void main(String[] args) throws Exception { 13 | // a list of students' source code files located in the prepared 14 | // directory. 15 | Collection files = FileUtils.listFiles(new File( 16 | "C:\\temp\\moss-dir"), new String[] { "java" }, true); 17 | 18 | // a list of base files that was given to the students for this 19 | // assignment. 20 | Collection baseFiles = FileUtils.listFiles(new File( 21 | "C:\\temp\\moss-base-dir"), new String[] { "java" }, true); 22 | 23 | // get a new socket client to communicate with the MOSS server 24 | // and set its parameters. 25 | SocketClient socketClient = new SocketClient(); 26 | 27 | // set your MOSS user ID 28 | socketClient.setUserID("123456789"); 29 | // socketClient.setOpt... 30 | 31 | // set the programming language of all student source codes 32 | socketClient.setLanguage("java"); 33 | 34 | // initialize connection and send parameters 35 | socketClient.run(); 36 | 37 | // upload all base files 38 | for (File f : baseFiles) { 39 | socketClient.uploadBaseFile(f); 40 | } 41 | 42 | // upload all source files of students 43 | for (File f : files) { 44 | socketClient.uploadFile(f); 45 | } 46 | 47 | // finished uploading, tell server to check files 48 | socketClient.sendQuery(); 49 | 50 | // get URL with MOSS results and do something with it 51 | URL results = socketClient.getResultURL(); 52 | System.out.println("Results available at " + results.toString()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/it/zielke/moji/EncodingTest.java: -------------------------------------------------------------------------------- 1 | package it.zielke.moji; 2 | 3 | import static org.junit.Assert.fail; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.OutputStreamWriter; 7 | import java.nio.charset.Charset; 8 | import java.util.Locale; 9 | 10 | import org.junit.Test; 11 | 12 | public class EncodingTest { 13 | 14 | @Test 15 | public void testEncoding() { 16 | if (!Charset.isSupported("US-ASCII")) { 17 | fail("No ASCII charset support available (?!)"); 18 | } 19 | } 20 | 21 | @Test 22 | public void testLocale() { 23 | boolean localeAvailable = false; 24 | for (String s : Locale.getISOCountries()) { 25 | if (s.toLowerCase().equals("us")) { 26 | localeAvailable = true; 27 | break; 28 | } 29 | } 30 | if (!localeAvailable) { 31 | fail("No US Locale available"); 32 | } 33 | } 34 | 35 | @Test 36 | public void testDefaultCharset() { 37 | try { 38 | System.out.println("Java version: " 39 | + System.getProperty("java.version")); 40 | System.out.println(String.format( 41 | "Java VM:\n\tvendor: %s\n\tname: %s", 42 | System.getProperty("java.vm.vendor"), 43 | System.getProperty("java.vm.name"))); 44 | System.out.println(String.format( 45 | "OS:\n\tname: %s\n\tversion: %s\n\tarch: %s", 46 | System.getProperty("os.name"), 47 | System.getProperty("os.version"), 48 | System.getProperty("os.arch"))); 49 | System.out.println("Encoding:"); 50 | System.out 51 | .println("\tDefault Charset: " + Charset.defaultCharset()); 52 | System.out.println("\tfile.encoding: " 53 | + System.getProperty("file.encoding")); 54 | System.out.println("\tDefault Charset in Use: " 55 | + getDefaultCharSet()); 56 | } catch (SecurityException e) { 57 | System.out.println("WARN - Running under security manager: " 58 | + e.getMessage()); 59 | e.printStackTrace(); 60 | } 61 | } 62 | 63 | private String getDefaultCharSet() { 64 | OutputStreamWriter writer = new OutputStreamWriter( 65 | new ByteArrayOutputStream()); 66 | String enc = writer.getEncoding(); 67 | return enc; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/site/fml/faq.fml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | General 7 | 8 | 9 | What are the differences between MOJI and the official Perl client? 10 | 11 | 12 |
    13 |
  • MOJI works cross-platform. Including Windows, where the original client requires some modifications to run properly.
  • 14 |
  • MOJI is better suited for integration into an already existing online e-Learning system, especially if that system is Java based. It has exception handling and can be deployed using Maven.
  • 15 |
  • MOJI, internally, uses directory mode only. This detail should be negligible if you are starting from scratch. But please keep it in mind when porting your application from the Perl client.
  • 16 |
  • MOJI uses Java as its default language for programming sources.
  • 17 |
18 |

19 |

20 |
21 |
22 | 23 | 24 | 25 | What is the meaning of optN, optX, etc.? 26 | 27 | 28 |

Detailed explanations of these parameters can be found in the source code of the original client. Here is a short summary:

29 |

optC: Comment string. Will show up on the results page so different results can be distinguished from each other.

30 |

optD: Directory mode.

31 |

optM: Do not treat source code passage as plagiarism if it is found at least this many times. Supplies an easy way to detect base/template code.

32 |

optN: Defines how many matches to display on the results page.

33 |

optX: Experimental feature support. Set to 1 to use the experimental MOSS server.

34 |
35 |
36 |
37 | 38 | 39 | Development 40 | 41 | 42 | 43 | Why are there runtime exceptions after compiling successfully? 44 | 45 | 46 |

47 | If you get runtime exceptions, you are trying to call methods of the socket client in the wrong order -- for example by uploading files before authenticating with the server. 48 |

49 |
50 |
51 |
52 | 53 |
54 | -------------------------------------------------------------------------------- /src/site/apt/quickstart.apt: -------------------------------------------------------------------------------- 1 | ----------- 2 | Quick Start 3 | ----------- 4 | 5 | Preparation 6 | 7 | 8 | * copy student source codes to a directory where each subdirectory contains the source code of a single student 9 | 10 | * a valid directory containing student solutions might look like this: 11 | 12 | ------- 13 | solution_directory 14 | |- student1 15 | |- classA.java 16 | |- ... 17 | |- student2 18 | |- ... 19 | |_ student3 20 | |- ... 21 | ------- 22 | 23 | Usage 24 | 25 | {{{./download.html}Download}} the JAR and include it into your Java project. 26 | Alternatively use Maven (note: there is no public Maven repo including MOJI as of writing this). 27 | 28 | Example code 29 | 30 | You can find a preconfigured Eclipse project in the "example" subdirectory. 31 | This is the relevant snippet from it: 32 | 33 | +-----+ 34 | import java.io.File; 35 | import java.util.Collection; 36 | import java.net.URL; 37 | import org.apache.commons.io.FileUtils; 38 | import it.zielke.moji.SocketClient; 39 | 40 | public class QuickStart { 41 | public static void main(String[] args) throws Exception { 42 | // a list of students' source code files located in the prepared 43 | // directory. 44 | Collection files = FileUtils.listFiles(new File( 45 | "C:\\temp\\solution_directory"), new String[] { "java" }, true); 46 | 47 | // a list of base files that was given to the students for this 48 | // assignment. 49 | Collection baseFiles = FileUtils.listFiles(new File( 50 | "C:\\temp\\base_directory"), new String[] { "java" }, true); 51 | 52 | //get a new socket client to communicate with the MOSS server 53 | //and set its parameters. 54 | SocketClient socketClient = new SocketClient(); 55 | 56 | //set your MOSS user ID 57 | socketClient.setUserID("123456789"); 58 | //socketClient.setOpt... 59 | 60 | //set the programming language of all student source codes 61 | socketClient.setLanguage("java"); 62 | 63 | //initialize connection and send parameters 64 | socketClient.run(); 65 | 66 | // upload all base files 67 | for (File f : baseFiles) { 68 | socketClient.uploadBaseFile(f); 69 | } 70 | 71 | //upload all source files of students 72 | for (File f : files) { 73 | socketClient.uploadFile(f); 74 | } 75 | 76 | //finished uploading, tell server to check files 77 | socketClient.sendQuery(); 78 | 79 | //get URL with MOSS results and do something with it 80 | URL results = socketClient.getResultURL(); 81 | System.out.println("Results available at " + results.toString()); 82 | } 83 | } 84 | +-----+ -------------------------------------------------------------------------------- /src/test/java/it/zielke/moji/SocketClientTestBase.java: -------------------------------------------------------------------------------- 1 | package it.zielke.moji; 2 | 3 | import static org.junit.Assert.fail; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import org.junit.After; 10 | import org.junit.Before; 11 | 12 | public class SocketClientTestBase { 13 | 14 | public static final String TEST_IP = "127.0.0.1"; 15 | 16 | protected SocketClient socketClient; 17 | protected MockServer mockServer; 18 | protected Thread ms; 19 | 20 | @Before 21 | public void setUp() throws Exception { 22 | try { 23 | ms = new Thread(mockServer = new MockServer()); 24 | mockServer.setPort(0); 25 | ms.setDaemon(true); 26 | ms.start(); 27 | while (!mockServer.isBound()) { 28 | Thread.sleep(10); 29 | } 30 | socketClient = new SocketClient(); 31 | socketClient.setServer(TEST_IP); 32 | int mockServerPort = mockServer.getLocalPort(); 33 | socketClient.setPort(mockServerPort); 34 | socketClient.setUserID("900000"); 35 | socketClient.connect(); 36 | status("Started test on port " + mockServerPort); 37 | } catch (Exception e) { 38 | e.printStackTrace(); 39 | fail("Failed to setup test server/client pair due to Exception: " 40 | + e.getMessage()); 41 | } 42 | } 43 | 44 | @After 45 | public void tearDown() throws Exception { 46 | try { 47 | mockServer.setShutdown(true); 48 | socketClient.close(); 49 | } catch (Exception e) { 50 | 51 | } 52 | } 53 | 54 | public String sendStringCommands(String[] strings) { 55 | // might as well make the function public to avoid this mess 56 | Method method = null; 57 | Object o = socketClient; 58 | try { 59 | method = o.getClass().getDeclaredMethod("sendCommandStrings", 60 | strings.getClass()); 61 | method.setAccessible(true); 62 | return (String) method.invoke(o, new Object[] { strings }); 63 | } catch (Exception e) { 64 | e.printStackTrace(); 65 | } 66 | return null; 67 | } 68 | 69 | public String sendObjectCommands(Object[] objects) { 70 | // might as well make the function public to avoid this mess 71 | Method method = null; 72 | Object o = socketClient; 73 | 74 | List os = Arrays.asList(objects); 75 | os.toString(); 76 | try { 77 | method = o.getClass().getDeclaredMethod("sendCommand", 78 | new Object[] {}.getClass()); 79 | method.setAccessible(true); 80 | return (String) method.invoke(o, new Object[][] { objects }); 81 | } catch (Exception e) { 82 | e.printStackTrace(); 83 | } 84 | return null; 85 | } 86 | 87 | protected void status(String s) { 88 | System.out.println("SocketClient - " 89 | + String.format("%d - %s", socketClient.getPort(), s)); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## MOJI 2 | 3 | MOJI is an unofficial Java client for the 4 | Moss plagiarism detection 5 | service. 6 | It has the following key features: 7 | 8 | - Pure Java implementation with few dependencies 9 | - Cross-platform support 10 | 11 | ## Requirements 12 | 13 | - Java 6 or better 14 | - Apache Commons IO 2.3 15 | 16 | ## Installation 17 | 18 | Use either of the following methods to obtain a MOJI binary. 19 | 20 | - add as Maven dependency: 21 | 22 | 23 | it.zielke 24 | moji 25 | 1.0.2 26 | 27 | 28 | 29 | - download the JAR and include it in your Java project. 30 | 31 | - build it yourself using Maven and include the JAR in your Java project. 32 | 33 | ## Quick Start 34 | 35 | ### 1. Preparation 36 | 37 | MOJI requires some kind of Moss-compatible directory structure to distinguish 38 | between different students. 39 | 40 | To achieve this, copy all student source codes to a directory where each 41 | subdirectory contains the source code of a single student. 42 | 43 | A valid directory containing student solutions might look like this: 44 | 45 | 46 | solution_directory 47 | |- student1 48 | |- classA.java 49 | |- ... 50 | |- student2 51 | |- ... 52 | |_ student3 53 | |- ... 54 | 55 | ### 2. Download the JAR (or build it yourself) and include it in your project. 56 | 57 | ### 3. Create a new socket client object to communicate with the Moss server. 58 | 59 | ## Example code 60 | 61 | import java.io.File; 62 | import java.util.Collection; 63 | import java.net.URL; 64 | import org.apache.commons.io.FileUtils; 65 | import it.zielke.moji.SocketClient; 66 | 67 | public class QuickStart { 68 | public static void main(String[] args) throws Exception { 69 | // a list of students' source code files located in the prepared 70 | // directory. 71 | Collection files = FileUtils.listFiles(new File( 72 | "C:\\temp\\solution_directory"), new String[] { "java" }, true); 73 | 74 | // a list of base files that was given to the students for this 75 | // assignment. 76 | Collection baseFiles = FileUtils.listFiles(new File( 77 | "C:\\temp\\base_directory"), new String[] { "java" }, true); 78 | 79 | //get a new socket client to communicate with the Moss server 80 | //and set its parameters. 81 | SocketClient socketClient = new SocketClient(); 82 | 83 | //set your Moss user ID 84 | socketClient.setUserID("123456789"); 85 | //socketClient.setOpt... 86 | 87 | //set the programming language of all student source codes 88 | socketClient.setLanguage("java"); 89 | 90 | //initialize connection and send parameters 91 | socketClient.run(); 92 | 93 | // upload all base files 94 | for (File f : baseFiles) { 95 | socketClient.uploadBaseFile(f); 96 | } 97 | 98 | //upload all source files of students 99 | for (File f : files) { 100 | socketClient.uploadFile(f); 101 | } 102 | 103 | //finished uploading, tell server to check files 104 | socketClient.sendQuery(); 105 | 106 | //get URL with Moss results and do something with it 107 | URL results = socketClient.getResultURL(); 108 | System.out.println("Results available at " + results.toString()); 109 | } 110 | } 111 | 112 | ## Tests 113 | 114 | To execute all tests, simply run 115 | 116 | mvn test 117 | 118 | ## Documentation 119 | 120 | http://www.zielke.it/moji/ 121 | 122 | ## Attribution 123 | 124 | This software communicates with 125 | Moss by Alex Aiken of 126 | Stanford University. 127 | MOJI is not in any way affiliated with Moss. 128 | 129 | ## License 130 | 131 | MOJI is under MIT License. Please see the corresponding license files. -------------------------------------------------------------------------------- /src/test/java/it/zielke/moji/SocketClientCommandsTest.java: -------------------------------------------------------------------------------- 1 | package it.zielke.moji; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.fail; 5 | 6 | import java.io.File; 7 | import java.net.URL; 8 | 9 | import org.apache.commons.io.FileUtils; 10 | import org.junit.After; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | 14 | public class SocketClientCommandsTest extends SocketClientTestBase { 15 | 16 | @Before 17 | public void setUp() throws Exception { 18 | super.setUp(); 19 | } 20 | 21 | @After 22 | public void tearDown() throws Exception { 23 | super.tearDown(); 24 | } 25 | 26 | @Test 27 | public void testSocketClient() { 28 | try { 29 | testSendQuery(); 30 | testClose(); 31 | assertEquals(Stage.DISCONNECTED, socketClient.getCurrentStage()); 32 | } catch (Exception e) { 33 | fail(e.getMessage()); 34 | } 35 | 36 | } 37 | 38 | @Test 39 | public void testSendInitialization() { 40 | try { 41 | assertEquals(Stage.AWAITING_INITIALIZATION, 42 | socketClient.getCurrentStage()); 43 | socketClient.sendInitialization(); 44 | assertEquals(Stage.AWAITING_LANGUAGE, 45 | socketClient.getCurrentStage()); 46 | } catch (MossException e) { 47 | fail(e.getMessage()); 48 | } 49 | 50 | } 51 | 52 | @Test 53 | public void testSetLanguage() { 54 | String invalidLanguage = "does_not_exist"; 55 | String validLanguage = "java"; 56 | 57 | try { 58 | socketClient.setLanguage(invalidLanguage); 59 | } catch (MossException e) { 60 | try { 61 | socketClient.setLanguage(validLanguage); 62 | return; 63 | } catch (MossException e1) { 64 | fail("Failed to accept a valid language"); 65 | } 66 | } 67 | fail("Failed because an invalid language was accepted"); 68 | 69 | } 70 | 71 | @Test 72 | public void testSendLanguage() { 73 | try { 74 | socketClient.sendInitialization(); 75 | assertEquals(Stage.AWAITING_LANGUAGE, 76 | socketClient.getCurrentStage()); 77 | socketClient.sendLanguage(); 78 | assertEquals(Stage.AWAITING_FILES, socketClient.getCurrentStage()); 79 | status("Test sendLanguage finished"); 80 | } catch (Exception e) { 81 | fail(e.getMessage()); 82 | } 83 | status("sent language"); 84 | } 85 | 86 | @Test 87 | public void testUploadFile() { 88 | /* 89 | * TODO implement with a file mock object. The test below will fail if 90 | * no temporary file can be created. 91 | */ 92 | File tempFile = null; 93 | try { 94 | testSendLanguage(); 95 | tempFile = File.createTempFile("MOJITest", ".java"); 96 | socketClient.uploadFile(tempFile); 97 | } catch (Exception e) { 98 | e.printStackTrace(); 99 | fail(e.getMessage()); 100 | } finally { 101 | FileUtils.deleteQuietly(tempFile); 102 | } 103 | 104 | } 105 | 106 | @Test 107 | public void testSendQuery() { 108 | try { 109 | testUploadFile(); 110 | socketClient.sendQuery(); 111 | assertEquals(Stage.AWAITING_END, socketClient.getCurrentStage()); 112 | } catch (Exception e) { 113 | fail(e.getMessage()); 114 | } 115 | } 116 | 117 | @Test 118 | public void testGetResultURL() { 119 | try { 120 | testSendQuery(); 121 | URL resultURL = socketClient.getResultURL(); 122 | assertEquals(new MockServer().getResultURL(), resultURL.toString()); 123 | } catch (Exception e) { 124 | fail(e.getMessage()); 125 | } 126 | } 127 | 128 | @Test 129 | public void testClose() { 130 | assertEquals(mockServer.isShutdown(), false); 131 | try { 132 | socketClient.close(); 133 | } catch (Exception e) { 134 | fail(e.getMessage()); 135 | } 136 | assertEquals(Stage.DISCONNECTED, socketClient.getCurrentStage()); 137 | } 138 | 139 | @Test 140 | public void testSendCommand() { 141 | 142 | String[] commandString = { "language", "java" }; 143 | assertEquals("language java\n", sendStringCommands(commandString)); 144 | 145 | // TODO rewrite the whole test without using reflection 146 | // Object[] commandObjects = { "directory", 1 }; 147 | // assertEquals("directory 1", sendObjectCommands(commandObjects)); 148 | } 149 | 150 | @Test 151 | public void testGetSocket() { 152 | if (socketClient.getSocket() == null 153 | || socketClient.getSocket().isBound() == false) { 154 | fail("no connection to Server"); 155 | } 156 | } 157 | 158 | @Test 159 | public void testGetIncSetID() { 160 | int current = socketClient.getSetID(); 161 | assertEquals(socketClient.getIncSetID(), current); 162 | assertEquals(socketClient.getSetID(), current + 1); 163 | } 164 | 165 | public static junit.framework.Test suite() { 166 | return new junit.framework.JUnit4TestAdapter( 167 | SocketClientCommandsTest.class); 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/test/java/it/zielke/moji/MockServer.java: -------------------------------------------------------------------------------- 1 | package it.zielke.moji; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStreamWriter; 6 | import java.net.ServerSocket; 7 | import java.net.Socket; 8 | import java.nio.ByteBuffer; 9 | 10 | import org.apache.commons.io.IOUtils; 11 | 12 | public class MockServer implements Runnable { 13 | private boolean shutdown; 14 | private int port = 7690; 15 | private ServerSocket ss; 16 | private InputStream in; 17 | private OutputStreamWriter out; 18 | 19 | private String userID; 20 | private String language; 21 | private int setID; 22 | private long optM; 23 | private int optD; 24 | private int optX; 25 | private long optN; 26 | private String optC; 27 | private String resultURL = "http://moss.stanford.edu/results/20132013"; 28 | 29 | // expected results for test 30 | ByteBuffer expectedBytes; 31 | ByteBuffer receivedBytes; 32 | String lastFileReceived; 33 | 34 | public MockServer() { 35 | } 36 | 37 | public void setExpectedBytes(byte[] expectedBytes) { 38 | this.expectedBytes = ByteBuffer.wrap(expectedBytes); 39 | } 40 | 41 | private void status(String s) { 42 | System.out.println(String.format("MockServer - %d - %s", this.port, s)); 43 | } 44 | 45 | public void waitForCommand() throws Exception { 46 | String s = null; 47 | try { 48 | StringBuilder sb = new StringBuilder(); 49 | for (int read = in.read(); read >= 0 && read != 10; read = in 50 | .read()) { 51 | sb.append(Character.toString((char) read)); 52 | } 53 | s = sb.toString(); 54 | status("found command string: " + s); 55 | } catch (Exception e) { 56 | throw new RuntimeException(e); 57 | } 58 | 59 | if (s != null) { 60 | status("received command: " + s); 61 | processCommand(s); 62 | } 63 | } 64 | 65 | public void processCommand(String s) throws Exception { 66 | String[] parts = s.split(" "); 67 | String command = parts[0]; 68 | String parameter = ""; 69 | if (parts.length > 0) { 70 | for (int i = 1; i < parts.length; i++) { 71 | parameter += parts[i]; 72 | if (i != parts.length - 1) { 73 | parameter += " "; 74 | } 75 | } 76 | } 77 | parameter = parameter.trim(); 78 | status("command: " + command + ", parameter: " + parameter); 79 | 80 | if (command.equals("language")) { 81 | this.setLanguage(parameter); 82 | out.write("yes\n"); 83 | out.flush(); 84 | } else if (command.equals("moss")) { 85 | this.setUserID(parameter); 86 | } else if (command.equals("X")) { 87 | this.setOptX(Integer.parseInt(parameter)); 88 | } else if (command.equals("file")) { 89 | checkFileUpload(parameter); 90 | } else if (command.equals("query")) { 91 | out.write(this.resultURL + "\n"); 92 | out.flush(); 93 | status("sent url: " + this.resultURL); 94 | } else if (command.trim().equals("end")) { 95 | this.shutdown = true; 96 | } 97 | } 98 | 99 | public void checkFileUpload(String fileUploadString) { 100 | String[] parts = fileUploadString.split(" "); 101 | int id = Integer.valueOf(parts[0]); 102 | String language = parts[1]; 103 | int size = Integer.valueOf(parts[2]); 104 | String fileName = parts[3]; 105 | 106 | checkFileBytes(size); 107 | lastFileReceived = fileName; 108 | } 109 | 110 | public void checkFileBytes(int expectedSize) { 111 | receivedBytes = null; 112 | byte[] b = null; 113 | try { 114 | b = IOUtils.toByteArray(this.in, expectedSize); 115 | } catch (IOException e) { 116 | endConnection(); 117 | throw new RuntimeException(e); 118 | } 119 | ByteBuffer buffer = ByteBuffer.wrap(b); 120 | if (expectedBytes != null) { 121 | if (buffer.array().length != this.expectedBytes.array().length) { 122 | endConnection(); 123 | throw new RuntimeException( 124 | String.format( 125 | "Error during file upload: expected number of bytes do not match the actual number of bytes. Expected bytes: %d, actual bytes: %d", 126 | this.expectedBytes.array().length, 127 | b == null ? "n/a" : b.length)); 128 | } else { 129 | receivedBytes = buffer; 130 | } 131 | } 132 | } 133 | 134 | public void endConnection() { 135 | try { 136 | this.in.close(); 137 | this.out.close(); 138 | } catch (IOException e) { 139 | e.printStackTrace(); 140 | } 141 | } 142 | 143 | public void startServer() { 144 | try { 145 | try { 146 | ss = new ServerSocket(port); 147 | } catch (Exception e) { 148 | e.printStackTrace(); 149 | } 150 | status("listening for connection"); 151 | Socket s = ss.accept(); 152 | // s.setKeepAlive(true); 153 | // s.setSoTimeout(5000); 154 | status("accepted connection - remote port: " + s.getPort()); 155 | in = s.getInputStream(); 156 | out = new OutputStreamWriter(s.getOutputStream()); 157 | while (!shutdown) { 158 | status("waiting for command"); 159 | waitForCommand(); 160 | } 161 | status("shutting down"); 162 | s.close(); 163 | } catch (Exception e) { 164 | e.printStackTrace(); 165 | } finally { 166 | try { 167 | in.close(); 168 | out.close(); 169 | ss.close(); 170 | } catch (IOException e) { 171 | } 172 | 173 | } 174 | } 175 | 176 | public boolean isBound() { 177 | return (this.ss != null && this.ss.isBound()); 178 | } 179 | 180 | public int getPort() { 181 | return port; 182 | } 183 | 184 | public void setPort(int port) { 185 | this.port = port; 186 | } 187 | 188 | public ServerSocket getSs() { 189 | return ss; 190 | } 191 | 192 | public void setSs(ServerSocket ss) { 193 | this.ss = ss; 194 | } 195 | 196 | public OutputStreamWriter getOut() { 197 | return out; 198 | } 199 | 200 | public void setOut(OutputStreamWriter out) { 201 | this.out = out; 202 | } 203 | 204 | public String getUserID() { 205 | return userID; 206 | } 207 | 208 | public void setUserID(String userID) { 209 | this.userID = userID; 210 | } 211 | 212 | public String getLanguage() { 213 | return language; 214 | } 215 | 216 | public void setLanguage(String language) { 217 | this.language = language; 218 | } 219 | 220 | public int getSetID() { 221 | return setID; 222 | } 223 | 224 | public void setSetID(int setID) { 225 | this.setID = setID; 226 | } 227 | 228 | public long getOptM() { 229 | return optM; 230 | } 231 | 232 | public void setOptM(long optM) { 233 | this.optM = optM; 234 | } 235 | 236 | public int getOptD() { 237 | return optD; 238 | } 239 | 240 | public void setOptD(int optD) { 241 | this.optD = optD; 242 | } 243 | 244 | public int getOptX() { 245 | return optX; 246 | } 247 | 248 | public void setOptX(int optX) { 249 | this.optX = optX; 250 | } 251 | 252 | public long getOptN() { 253 | return optN; 254 | } 255 | 256 | public void setOptN(long optN) { 257 | this.optN = optN; 258 | } 259 | 260 | public String getOptC() { 261 | return optC; 262 | } 263 | 264 | public void setOptC(String optC) { 265 | this.optC = optC; 266 | } 267 | 268 | public String getResultURL() { 269 | return resultURL; 270 | } 271 | 272 | public void setResultURL(String resultURL) { 273 | this.resultURL = resultURL; 274 | } 275 | 276 | public void run() { 277 | status("ready to run"); 278 | startServer(); 279 | 280 | } 281 | 282 | public boolean isShutdown() { 283 | return shutdown; 284 | } 285 | 286 | public void setShutdown(boolean shutdown) { 287 | this.shutdown = shutdown; 288 | } 289 | 290 | public int getLocalPort() { 291 | return this.ss.getLocalPort(); 292 | } 293 | 294 | public ByteBuffer getReceivedBytes() { 295 | return receivedBytes; 296 | } 297 | 298 | public String getLastFileReceived() { 299 | return lastFileReceived; 300 | } 301 | 302 | } 303 | -------------------------------------------------------------------------------- /src/test/java/it/zielke/moji/SocketClientUploadTest.java: -------------------------------------------------------------------------------- 1 | package it.zielke.moji; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.fail; 5 | 6 | import java.io.File; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import org.apache.commons.codec.binary.Hex; 11 | import org.junit.After; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | 15 | public class SocketClientUploadTest extends SocketClientTestBase { 16 | 17 | @Before 18 | public void setUp() throws Exception { 19 | super.setUp(); 20 | } 21 | 22 | @After 23 | public void tearDown() throws Exception { 24 | super.tearDown(); 25 | } 26 | 27 | @Test 28 | public void testUploadUKRTestSet() { 29 | 30 | for (ExpectedValues expectedValues : createTestSet()) { 31 | try { 32 | socketClient.sendInitialization(); 33 | socketClient.sendLanguage(); 34 | mockServer.setExpectedBytes(Hex.decodeHex(expectedValues 35 | .getBytesHex().toCharArray())); 36 | socketClient.uploadFile(expectedValues.getFile()); 37 | while (mockServer.getLastFileReceived() == null 38 | || !mockServer.getLastFileReceived().equals( 39 | socketClient.normalizeFilename(expectedValues 40 | .getFile().getAbsolutePath()))) { 41 | Thread.sleep(10); 42 | } 43 | assertEquals(expectedValues.getBytesHex(), String.valueOf(Hex 44 | .encodeHex(mockServer.getReceivedBytes().array()))); 45 | } catch (Exception e) { 46 | e.printStackTrace(); 47 | fail(e.getMessage()); 48 | } 49 | } 50 | } 51 | 52 | private List createTestSet() { 53 | List testSet = new ArrayList(); 54 | 55 | testSet.add(new ExpectedValues() 56 | .setFile(getFileFromTestResources("testset-UKR/1/1.cpp")) 57 | .setBytesLength(2418) 58 | .setBytesHex( 59 | "23 69 6E 63 6C 75 64 65 3C 69 6F 73 74 72 65 61 6D 3E 0D 0A 23 69 6E 63 6C 75 64 65 3C 73 74 64 69 6F 2E 68 3E 0D 0A 23 70 72 61 67 6D 61 20 77 61 72 6E 69 6E 67 20 28 64 69 73 61 62 6C 65 3A 20 34 39 39 36 29 0D 0A 75 73 69 6E 67 20 6E 61 6D 65 73 70 61 63 65 20 73 74 64 3B 0D 0A 46 49 4C 45 20 2A 64 61 74 61 2C 20 2A 6F 75 74 70 75 74 3B 2F 2F EF EE EA E0 E7 F7 E8 EA E8 20 ED E0 20 E2 F5 B3 E4 ED E8 E9 20 F2 E0 20 E2 E8 F5 B3 E4 ED E8 E9 20 F4 E0 E9 EB E8 0D 0A 63 68 61 72 20 69 6E 6E 61 6D 65 5B 32 30 5D 2C 20 6F 75 74 6E 61 6D 65 5B 32 30 5D 3B 2F 2F ED E0 E7 E2 E8 20 E2 F5 B3 E4 ED EE E3 EE 20 F2 E0 20 E2 E8 F5 B3 E4 ED EE E3 EE 20 F4 E0 E9 EB B3 E2 0D 0A 69 6E 74 20 6D 73 75 6D 5B 31 30 30 5D 5B 31 30 30 5D 3B 0D 0A 69 6E 74 20 6D 69 6E 74 5B 31 30 30 5D 5B 31 30 30 5D 3B 0D 0A 69 6E 74 20 6E 2C 20 6D 3B 0D 0A 76 6F 69 64 20 77 6F 72 6B 28 29 3B 0D 0A 76 6F 69 64 20 76 69 64 70 28 29 3B 0D 0A 76 6F 69 64 20 69 6E 74 65 6E 64 28 29 3B 0D 0A 69 6E 6C 69 6E 65 20 76 6F 69 64 20 73 75 6D 69 73 28 69 6E 74 20 69 31 2C 20 69 6E 74 20 6A 31 29 3B 0D 0A 76 6F 69 64 20 69 6E 74 65 6E 64 28 69 6E 74 20 69 31 2C 20 69 6E 74 20 6A 31 2C 20 69 6E 74 20 6C 31 29 3B 0D 0A 76 6F 69 64 20 66 69 6C 65 77 72 69 74 65 31 28 29 3B 0D 0A 76 6F 69 64 20 66 69 6C 65 77 72 69 74 65 32 28 29 3B 0D 0A 69 6E 74 20 6D 61 69 6E 28 29 0D 0A 7B 0D 0A 09 63 6F 75 74 20 3C 3C 20 22 4C 61 62 6F 72 61 74 6F 72 79 20 77 6F 72 6B 20 23 31 20 27 44 65 6D 6F 6E 73 74 72 61 74 69 6F 6E 20 6F 66 20 67 72 61 70 68 73 27 22 20 3C 3C 20 65 6E 64 6C 3B 0D 0A 09 77 6F 72 6B 28 29 3B 0D 0A 09 66 69 6C 65 77 72 69 74 65 31 28 29 3B 0D 0A 09 66 69 6C 65 77 72 69 74 65 32 28 29 3B 0D 0A 09 73 79 73 74 65 6D 28 22 70 61 75 73 65 22 29 3B 0D 0A 7D 0D 0A 76 6F 69 64 20 77 6F 72 6B 28 29 0D 0A 7B 0D 0A 09 63 6F 75 74 20 3C 3C 20 22 49 6E 70 75 74 20 6E 61 6D 65 20 6F 66 20 79 6F 75 72 20 64 61 74 61 20 66 69 6C 65 21 22 20 3C 3C 20 65 6E 64 6C 3B 0D 0A 09 63 69 6E 20 3E 3E 20 69 6E 6E 61 6D 65 3B 0D 0A 09 64 61 74 61 20 3D 20 66 6F 70 65 6E 28 69 6E 6E 61 6D 65 2C 20 22 72 22 29 3B 0D 0A 09 69 66 20 28 64 61 74 61 20 3D 3D 20 4E 55 4C 4C 29 0D 0A 09 09 63 6F 75 74 20 3C 3C 20 22 46 69 6C 65 20 6E 6F 74 20 66 6F 75 6E 64 21 22 20 3C 3C 20 65 6E 64 6C 3B 0D 0A 09 65 6C 73 65 0D 0A 09 09 76 69 64 70 28 29 3B 0D 0A 7D 0D 0A 76 6F 69 64 20 76 69 64 70 28 29 0D 0A 7B 0D 0A 09 69 6E 74 20 69 2C 20 6A 3B 0D 0A 09 63 68 61 72 20 6E 75 6D 62 5B 31 30 5D 3B 0D 0A 09 66 67 65 74 73 28 6E 75 6D 62 2C 20 31 30 2C 20 64 61 74 61 29 3B 0D 0A 09 73 73 63 61 6E 66 28 6E 75 6D 62 2C 20 22 25 64 20 25 64 22 2C 20 26 6E 2C 20 26 6D 29 3B 0D 0A 09 66 6F 72 20 28 69 6E 74 20 6C 20 3D 20 30 3B 20 6C 20 3C 20 6E 3B 20 6C 2B 2B 29 0D 0A 09 7B 0D 0A 09 09 66 6F 72 20 28 69 6E 74 20 73 20 3D 20 30 3B 20 73 20 3C 20 6E 3B 20 73 2B 2B 29 0D 0A 09 09 7B 0D 0A 09 09 09 6D 73 75 6D 5B 6C 5D 5B 73 5D 20 3D 20 30 3B 0D 0A 09 09 7D 0D 0A 09 09 63 6F 75 74 20 3C 3C 20 65 6E 64 6C 3B 0D 0A 09 7D 0D 0A 09 66 6F 72 20 28 69 6E 74 20 6C 20 3D 20 30 3B 20 6C 20 3C 20 6D 3B 20 6C 2B 2B 29 0D 0A 09 7B 0D 0A 09 09 66 67 65 74 73 28 6E 75 6D 62 2C 20 31 30 2C 20 64 61 74 61 29 3B 0D 0A 09 09 73 73 63 61 6E 66 28 6E 75 6D 62 2C 20 22 25 64 20 25 64 22 2C 20 26 69 2C 20 26 6A 29 3B 0D 0A 09 09 73 75 6D 69 73 28 69 2C 20 6A 29 3B 0D 0A 09 09 69 6E 74 65 6E 64 28 69 2C 20 6A 2C 20 6C 29 3B 0D 0A 09 7D 0D 0A 09 66 6F 72 20 28 69 6E 74 20 6C 20 3D 20 30 3B 20 6C 20 3C 20 6E 3B 20 6C 2B 2B 29 0D 0A 09 7B 0D 0A 09 09 66 6F 72 20 28 69 6E 74 20 73 20 3D 20 30 3B 20 73 20 3C 20 6E 3B 20 73 2B 2B 29 0D 0A 09 09 7B 0D 0A 09 09 09 63 6F 75 74 20 3C 3C 20 6D 73 75 6D 5B 6C 5D 5B 73 5D 3B 0D 0A 09 09 7D 0D 0A 09 09 63 6F 75 74 20 3C 3C 20 65 6E 64 6C 3B 0D 0A 09 7D 0D 0A 09 66 6F 72 20 28 69 6E 74 20 6C 20 3D 20 30 3B 20 6C 20 3C 20 6E 3B 20 6C 2B 2B 29 0D 0A 09 7B 0D 0A 09 09 66 6F 72 20 28 69 6E 74 20 73 20 3D 20 30 3B 20 73 20 3C 20 6D 3B 20 73 2B 2B 29 0D 0A 09 09 7B 0D 0A 09 09 09 63 6F 75 74 20 3C 3C 20 6D 69 6E 74 5B 6C 5D 5B 73 5D 3B 0D 0A 09 09 7D 0D 0A 09 09 63 6F 75 74 20 3C 3C 20 65 6E 64 6C 3B 0D 0A 09 7D 0D 0A 09 66 63 6C 6F 73 65 28 64 61 74 61 29 3B 0D 0A 7D 0D 0A 69 6E 6C 69 6E 65 20 76 6F 69 64 20 73 75 6D 69 73 28 69 6E 74 20 69 31 2C 20 69 6E 74 20 6A 31 29 0D 0A 7B 0D 0A 09 6D 73 75 6D 5B 69 31 20 2D 20 31 5D 5B 6A 31 20 2D 20 31 5D 20 3D 20 31 3B 0D 0A 7D 0D 0A 76 6F 69 64 20 69 6E 74 65 6E 64 28 69 6E 74 20 69 31 2C 20 69 6E 74 20 6A 31 2C 20 69 6E 74 20 6C 31 29 0D 0A 7B 0D 0A 09 69 66 20 28 69 31 20 3D 3D 20 6A 31 29 0D 0A 09 09 6D 69 6E 74 5B 69 31 2D 31 5D 5B 6C 31 2D 31 5D 20 3D 20 32 3B 0D 0A 09 65 6C 73 65 0D 0A 09 7B 0D 0A 09 09 6D 69 6E 74 5B 69 31 2D 31 5D 5B 6C 31 2D 31 5D 20 3D 20 2D 31 3B 0D 0A 09 09 6D 69 6E 74 5B 6A 31 2D 31 5D 5B 6C 31 2D 31 5D 20 3D 20 31 3B 0D 0A 09 7D 0D 0A 7D 0D 0A 76 6F 69 64 20 66 69 6C 65 77 72 69 74 65 31 28 29 0D 0A 7B 0D 0A 09 63 68 61 72 20 66 69 6C 65 6E 61 6D 65 5B 32 30 5D 3B 0D 0A 09 63 6F 75 74 20 3C 3C 20 22 45 6E 74 65 72 20 6E 61 6D 65 20 6F 66 20 66 69 6C 65 20 66 6F 72 20 63 6F 6D 70 65 69 72 20 74 61 62 6C 65 22 20 3C 3C 20 65 6E 64 6C 3B 0D 0A 09 63 69 6E 20 3E 3E 20 66 69 6C 65 6E 61 6D 65 3B 0D 0A 09 6F 75 74 70 75 74 20 3D 20 66 6F 70 65 6E 28 66 69 6C 65 6E 61 6D 65 2C 20 22 77 22 29 3B 0D 0A 09 69 66 20 28 6F 75 74 70 75 74 20 3D 3D 20 4E 55 4C 4C 29 0D 0A 09 09 63 6F 75 74 20 3C 3C 20 22 45 72 72 6F 72 20 6F 66 20 66 69 6C 65 20 63 72 65 61 74 69 6E 67 21 22 20 3C 3C 20 65 6E 64 6C 3B 0D 0A 09 65 6C 73 65 0D 0A 09 7B 0D 0A 09 09 66 6F 72 20 28 69 6E 74 20 6C 20 3D 20 30 3B 20 6C 20 3C 20 6E 3B 20 6C 2B 2B 29 0D 0A 09 09 7B 0D 0A 09 09 09 66 6F 72 20 28 69 6E 74 20 73 20 3D 20 30 3B 20 73 20 3C 20 6E 3B 20 73 2B 2B 29 0D 0A 09 09 09 7B 0D 0A 09 09 09 09 66 70 72 69 6E 74 66 28 6F 75 74 70 75 74 2C 20 22 25 64 22 2C 20 6D 73 75 6D 5B 6C 5D 5B 73 5D 29 3B 0D 0A 09 09 09 7D 0D 0A 09 09 09 66 70 75 74 63 28 27 5C 6E 27 2C 20 6F 75 74 70 75 74 29 3B 0D 0A 09 09 7D 0D 0A 09 09 66 63 6C 6F 73 65 28 6F 75 74 70 75 74 29 3B 0D 0A 09 7D 0D 0A 7D 0D 0A 76 6F 69 64 20 66 69 6C 65 77 72 69 74 65 32 28 29 0D 0A 7B 0D 0A 09 63 68 61 72 20 66 69 6C 65 6E 61 6D 65 5B 32 30 5D 3B 0D 0A 09 63 6F 75 74 20 3C 3C 20 22 45 6E 74 65 72 20 6E 61 6D 65 20 6F 66 20 66 69 6C 65 20 66 6F 72 20 69 6E 74 65 6E 64 65 6E 74 20 74 61 62 6C 65 22 20 3C 3C 20 65 6E 64 6C 3B 0D 0A 09 63 69 6E 20 3E 3E 20 66 69 6C 65 6E 61 6D 65 3B 0D 0A 09 6F 75 74 70 75 74 20 3D 20 66 6F 70 65 6E 28 66 69 6C 65 6E 61 6D 65 2C 20 22 77 22 29 3B 0D 0A 09 69 66 20 28 6F 75 74 70 75 74 20 3D 3D 20 4E 55 4C 4C 29 0D 0A 09 09 63 6F 75 74 20 3C 3C 20 22 45 72 72 6F 72 20 6F 66 20 66 69 6C 65 20 63 72 65 61 74 69 6E 67 21 22 20 3C 3C 20 65 6E 64 6C 3B 0D 0A 09 65 6C 73 65 0D 0A 09 7B 0D 0A 09 09 66 6F 72 20 28 69 6E 74 20 6C 20 3D 20 30 3B 20 6C 20 3C 20 6E 3B 20 6C 2B 2B 29 0D 0A 09 09 7B 0D 0A 09 09 09 66 6F 72 20 28 69 6E 74 20 73 20 3D 20 30 3B 20 73 20 3C 20 6D 3B 20 73 2B 2B 29 0D 0A 09 09 09 7B 0D 0A 09 09 09 09 66 70 72 69 6E 74 66 28 6F 75 74 70 75 74 2C 20 22 25 64 22 2C 20 6D 69 6E 74 5B 6C 5D 5B 73 5D 29 3B 0D 0A 09 09 09 7D 0D 0A 09 09 09 66 70 75 74 63 28 27 5C 6E 27 2C 20 6F 75 74 70 75 74 29 3B 0D 0A 09 09 7D 0D 0A 09 09 66 63 6C 6F 73 65 28 6F 75 74 70 75 74 29 3B 0D 0A 09 7D 0D 0A 7D")); 60 | 61 | return testSet; 62 | } 63 | 64 | private File getFileFromTestResources(String filePath) { 65 | ClassLoader classLoader = getClass().getClassLoader(); 66 | File file = new File(classLoader.getResource(filePath).getFile()); 67 | return file; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | it.zielke 5 | moji 6 | 1.0.2 7 | MOJI 8 | 9 | MOJI is an unofficial client for the MOSS plagiarism detection service. It allows you to check source codes of students for plagiarism or collusion. 10 | MOJI has the following key features: 11 | - Pure Java implementation with few dependencies 12 | - Cross-platform support 13 | 14 | 2012 15 | http://www.zielke.it/moji/ 16 | 17 | UTF-8 18 | UTF-8 19 | 20 | 21 | 22 | commons-io 23 | commons-io 24 | 2.3 25 | 26 | 27 | junit 28 | junit 29 | 4.11 30 | test 31 | 32 | 33 | com.google.code.findbugs 34 | annotations 35 | 2.0.1 36 | 37 | 38 | commons-codec 39 | commons-codec 40 | 1.10 41 | test 42 | 43 | 44 | 45 | 46 | 47 | org.codehaus.mojo 48 | findbugs-maven-plugin 49 | 2.5.2 50 | 51 | Max 52 | Low 53 | true 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-surefire-report-plugin 59 | 2.16 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-javadoc-plugin 64 | 2.9 65 | 66 | 67 | 68 | javadoc 69 | 70 | 71 | 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-project-info-reports-plugin 76 | 2.7 77 | 78 | 79 | 80 | index 81 | dependencies 82 | project-team 83 | summary 84 | license 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | MIT License 94 | LICENSE.txt 95 | repo 96 | 97 | 98 | 99 | 100 | Bjoern Zielke 101 | http://www.zielke.it 102 | +2 103 | 104 | 105 | 106 | scm:git:git@github.com:nordicway/moji.git 107 | scm:git:git@github.com:nordicway/moji.git 108 | git@github.com:nordicway/moji.git 109 | 110 | 111 | 112 | 113 | maven-site-plugin 114 | 3.4 115 | 116 | 117 | attach-descriptor 118 | 119 | attach-descriptor 120 | 121 | 122 | 123 | 124 | 125 | org.apache.maven.plugins 126 | maven-source-plugin 127 | 2.3 128 | 129 | 130 | attach-sources 131 | 132 | jar 133 | 134 | 135 | 136 | 137 | 138 | org.apache.maven.plugins 139 | maven-javadoc-plugin 140 | 2.10.1 141 | 142 | 143 | attach-javadocs 144 | 145 | jar 146 | 147 | 148 | 149 | 150 | 151 | org.codehaus.mojo 152 | findbugs-maven-plugin 153 | 2.5.2 154 | 155 | 156 | maven-antrun-plugin 157 | 1.7 158 | 159 | 160 | site 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | run 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /src/main/java/it/zielke/moji/SocketClient.java: -------------------------------------------------------------------------------- 1 | package it.zielke.moji; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.IOException; 6 | import java.io.InputStreamReader; 7 | import java.io.OutputStream; 8 | import java.net.MalformedURLException; 9 | import java.net.Socket; 10 | import java.net.URL; 11 | import java.net.UnknownHostException; 12 | import java.text.Normalizer; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | import java.util.Locale; 16 | import java.util.Vector; 17 | 18 | import org.apache.commons.io.Charsets; 19 | import org.apache.commons.io.FileUtils; 20 | import org.apache.commons.io.FilenameUtils; 21 | 22 | /** 23 | * Client for communicating with the MOSS server socket. Handles the 24 | * configuration, sending of files and receiving results. 25 | */ 26 | public class SocketClient { 27 | private static final String MESSAGE_UNKNOWN_LANGUAGE = "MOSS Server does not recognize this programming language"; 28 | private static final String DEFAULT_LANGUAGE = "java"; 29 | private static final int STARTING_SETID = 1; 30 | 31 | private Socket socket; 32 | private Stage currentStage = Stage.DISCONNECTED; 33 | 34 | private String server; 35 | private int port; 36 | private String userID; 37 | private String language; 38 | private int setID = STARTING_SETID; 39 | private long optM = 10; 40 | private int optD = 1; 41 | private int optX = 0; 42 | private long optN = 250; 43 | private String optC = ""; 44 | private URL resultURL; 45 | private List supportedLanguages = Arrays.asList("c", "cc", "java", 46 | "ml", "pascal", "ada", "lisp", "schema", "haskell", "fortran", 47 | "ascii", "vhdl", "perl", "matlab", "python", "mips", "prolog", 48 | "spice", "vb", "csharp", "modula2", "a8086", "javascript", "plsql"); 49 | 50 | private OutputStream out; 51 | private BufferedReader in; 52 | 53 | /** 54 | * Construct socket client with default host, port and programming language 55 | * of the source files to be uploaded. Default language is Java. 56 | */ 57 | public SocketClient() { 58 | this.server = "moss.stanford.edu"; 59 | this.port = 7690; 60 | this.language = DEFAULT_LANGUAGE; 61 | } 62 | 63 | /** 64 | * Construct socket client by setting a server and port. 65 | * 66 | * @param server 67 | * host name or IP of the MOSS server 68 | * @param port 69 | * port of the MOSS server 70 | */ 71 | public SocketClient(String server, int port) { 72 | this(); 73 | this.server = server; 74 | this.port = port; 75 | } 76 | 77 | /** 78 | * Construct socket client by setting a server name and port as well as the 79 | * programming language of the source files. 80 | * 81 | * @param server 82 | * host name or IP of the MOSS server 83 | * @param port 84 | * port of the MOSS server 85 | * @param language 86 | * programming language of all source files 87 | */ 88 | public SocketClient(String server, int port, String language) { 89 | this(server, port); 90 | this.language = language; 91 | } 92 | 93 | /** 94 | * Close connection to the MOSS server gracefully. 95 | */ 96 | public void close() { 97 | /* 98 | * do not check stage here so clients can close a connection in any 99 | * situation. 100 | */ 101 | try { 102 | sendCommand("end\n"); 103 | out.close(); 104 | in.close(); 105 | socket.close(); 106 | } catch (MossException e) { 107 | } catch (IOException e2) { 108 | } finally { 109 | currentStage = Stage.DISCONNECTED; 110 | } 111 | 112 | } 113 | 114 | /** 115 | * Connect to the MOSS server and set the input and output streams for 116 | * communication. 117 | * 118 | * @throws UnknownHostException 119 | * if host resolution fails 120 | * @throws IOException 121 | * if setting up the input streams fails 122 | * @throws SecurityException 123 | * if a connection is disallowed by your security manager 124 | */ 125 | public void connect() throws UnknownHostException, IOException, 126 | SecurityException { 127 | if (currentStage != Stage.DISCONNECTED) { 128 | throw new RuntimeException("Client is already connected"); 129 | } 130 | socket = new Socket(this.server, this.port); 131 | socket.setKeepAlive(true); 132 | out = socket.getOutputStream(); 133 | in = new BufferedReader(new InputStreamReader(socket.getInputStream(), 134 | Charsets.US_ASCII)); 135 | currentStage = Stage.AWAITING_INITIALIZATION; 136 | } 137 | 138 | /** 139 | * @return the current internal stage of the client. 140 | */ 141 | public Stage getCurrentStage() { 142 | return currentStage; 143 | } 144 | 145 | /** 146 | * Increment the set ID internally. Do not increment this manually unless 147 | * you have a good reason. Might be removed in the future. 148 | * 149 | * @return the last set ID, before this increment. 150 | */ 151 | @Deprecated 152 | public int getIncSetID() { 153 | return setID++; 154 | } 155 | 156 | public String getLanguage() { 157 | return language; 158 | } 159 | 160 | public String getOptC() { 161 | return optC; 162 | } 163 | 164 | public int getOptD() { 165 | return optD; 166 | } 167 | 168 | public long getOptM() { 169 | return optM; 170 | } 171 | 172 | public long getOptN() { 173 | return optN; 174 | } 175 | 176 | public int getOptX() { 177 | return optX; 178 | } 179 | 180 | public int getPort() { 181 | return port; 182 | } 183 | 184 | /** 185 | * @return the URL which points to the plagiarism report, located on the 186 | * MOSS web server. 187 | */ 188 | public URL getResultURL() { 189 | return resultURL; 190 | } 191 | 192 | public String getServer() { 193 | return server; 194 | } 195 | 196 | public int getSetID() { 197 | return setID; 198 | } 199 | 200 | public Socket getSocket() { 201 | return socket; 202 | } 203 | 204 | /** 205 | * @return a list of supported programming languages by the MOSS server. 206 | */ 207 | public List getSupportedLanguages() { 208 | return supportedLanguages; 209 | } 210 | 211 | public String getUserID() { 212 | return userID; 213 | } 214 | 215 | /** 216 | * Read a single line of the server's response. 217 | * 218 | * @return the line read 219 | * @throws IOException 220 | * if it was not possible to read from the socket 221 | */ 222 | public String readFromServer() throws IOException { 223 | return in.readLine(); 224 | } 225 | 226 | /** 227 | * Shortcut method to connect to the MOSS server, send all initialization 228 | * parameters and set the programming language of all source files. Call 229 | * this before uploading any files. 230 | * 231 | * @throws MossException 232 | * if the MOSS server returns an error 233 | * @throws IOException 234 | * if there is a (network) error while reading the server 235 | * response 236 | * @throws UnknownHostException 237 | * if host resolution fails 238 | */ 239 | public void run() throws MossException, IOException, UnknownHostException { 240 | connect(); 241 | sendInitialization(); 242 | sendLanguage(); 243 | } 244 | 245 | /** 246 | * Generic function for sending all kinds of commands to the server. 247 | * 248 | * @param objects 249 | * object array consisting of parameters which should be sent to 250 | * the server 251 | * @return the generated string which was actually sent to the server 252 | * @throws MossException 253 | * if the server response is unexpected 254 | */ 255 | private String sendCommand(Object... objects) throws MossException { 256 | // TODO test 257 | Vector commandStrings = new Vector(); 258 | String[] commandArray = new String[commandStrings.size()]; 259 | for (Object o : objects) { 260 | String s; 261 | s = o.toString(); 262 | 263 | commandStrings.add(s); 264 | } 265 | return sendCommandStrings(commandStrings.toArray(commandArray)); 266 | } 267 | 268 | /** 269 | * Generic function for sending all kinds of commands to the server. 270 | * 271 | * @param strings 272 | * string array consisting of parameters which should be sent to 273 | * the server 274 | * @return the generated string which was actually sent to the server 275 | * @throws MossException 276 | */ 277 | private String sendCommandStrings(String... strings) throws MossException { 278 | if (strings == null || strings.length == 0) { 279 | throw new MossException( 280 | "Failed to send command because it was empty."); 281 | } 282 | StringBuilder sb = new StringBuilder(); 283 | for (int i = 0; i < strings.length; i++) { 284 | String s = strings[i]; 285 | sb.append(s); 286 | if (i != strings.length - 1) { 287 | sb.append(" "); 288 | } 289 | } 290 | sb.append('\n'); 291 | try { 292 | byte[] bytes = (sb.toString()).getBytes(Charsets.US_ASCII); 293 | out.write(bytes); 294 | out.flush(); 295 | } catch (IOException e) { 296 | throw new MossException("Failed to send command: " + e.getMessage()); 297 | } 298 | 299 | return sb.toString(); 300 | } 301 | 302 | /** 303 | * Sends initialization commands to the server. Needs to be called after 304 | * connecting to the server but before sending any files. 305 | * 306 | * @throws MossException 307 | */ 308 | public void sendInitialization() throws MossException { 309 | if (currentStage != Stage.AWAITING_INITIALIZATION) { 310 | throw new RuntimeException( 311 | "Cannot send initialization. Client is either already initialized or not connected yet."); 312 | } 313 | sendCommand("moss", userID); 314 | sendCommand("directory", optD); 315 | sendCommand("X", optX); 316 | sendCommand("maxmatches", optM); 317 | sendCommand("show", optN); 318 | currentStage = Stage.AWAITING_LANGUAGE; 319 | } 320 | 321 | // this client only uses directory mode right now. 322 | // public void setOptD(int optD) { 323 | // this.optD = optD; 324 | // } 325 | 326 | /** 327 | * Sends a command to the server to define the programming language of all 328 | * source files. The language must be set beforehand using setLanguage(). 329 | * 330 | * @see it.zielke.moji.SocketClient#setLanguage(String) 331 | * @throws MossException 332 | * if this language is not supported by the MOSS server 333 | * @throws IOException 334 | * if there is an error while reading the server answer 335 | */ 336 | public void sendLanguage() throws MossException, IOException { 337 | if (currentStage != Stage.AWAITING_LANGUAGE) { 338 | throw new RuntimeException( 339 | "Language already sent or client is not initialized yet."); 340 | } 341 | sendCommand("language", language); 342 | // confirm valid language server-side 343 | String serverString; 344 | serverString = readFromServer(); 345 | if (serverString == null 346 | || !serverString.trim().toLowerCase(Locale.ENGLISH) 347 | .equals("yes")) { 348 | throw new MossException(MESSAGE_UNKNOWN_LANGUAGE); 349 | } 350 | currentStage = Stage.AWAITING_FILES; 351 | } 352 | 353 | /** 354 | * Sends a command to the server to define the programming language of all 355 | * source files. 356 | * 357 | * @param language 358 | * the programming language of all source files 359 | * @throws MossException 360 | * if this language is not supported by the MOSS server 361 | * @throws IOException 362 | * if there is an error while reading the server answer 363 | */ 364 | public void sendLanguage(String language) throws MossException, IOException { 365 | setLanguage(language); 366 | sendLanguage(); 367 | } 368 | 369 | /** 370 | * Sends a command to tell the server that all files have been uploaded and 371 | * the search for plagiarism can start. Waits for an answer from the server 372 | * which may take some minutes. 373 | * 374 | * @throws MossException 375 | * if there is an error when receiving results from the MOSS 376 | * server 377 | * @throws IOException 378 | * if the server communication fails 379 | */ 380 | public void sendQuery() throws MossException, IOException { 381 | if (currentStage != Stage.AWAITING_QUERY) { 382 | throw new RuntimeException( 383 | "Cannot send query at this time. Connection is either not initialized or already closed"); 384 | } 385 | if (setID == 1) { 386 | throw new MossException("You did not upload any files yet"); 387 | } 388 | sendCommand(String.format(Locale.ENGLISH, "%s %d %s", "query", 0, optC)); 389 | currentStage = Stage.AWAITING_RESULTS; 390 | // Query submitted, waiting for server's response 391 | String result = readFromServer(); 392 | if (null != result 393 | && result.toLowerCase(Locale.ENGLISH).startsWith("http")) { 394 | try { 395 | this.resultURL = new URL(result.trim()); 396 | } catch (MalformedURLException e) { 397 | throw new MossException( 398 | "MOSS submission failed. The server did not return a valid URL with detection results.", 399 | e); 400 | } 401 | currentStage = Stage.AWAITING_END; 402 | } else { 403 | throw new MossException( 404 | "MOSS submission failed. The server did not return a valid URL with detection results."); 405 | } 406 | } 407 | 408 | /** 409 | * Set the programming language of all source files. 410 | * 411 | * @param language 412 | * the programming language in which source file is written in. 413 | * Valid programming languages are: c, cc, java, ml, pascal, ada, 414 | * lisp, schema, haskell, fortran, ascii, vhdl, perl, matlab, 415 | * python, mips, prolog, spice, vb, csharp, modula2, a8086, 416 | * javascript and plsql. 417 | */ 418 | public void setLanguage(String language) throws MossException { 419 | if (!supportedLanguages.contains(language)) { 420 | throw new MossException(MESSAGE_UNKNOWN_LANGUAGE); 421 | } 422 | this.language = language; 423 | } 424 | 425 | /** 426 | * Sets a comment string which is attached to the MOSS report. Individual 427 | * reports can then easily be distinguished. 428 | * 429 | * @param optC 430 | * comment string 431 | */ 432 | public void setOptC(String optC) { 433 | this.optC = optC; 434 | } 435 | 436 | /** 437 | * Set the maximum amount of times a source code passage may appear before 438 | * it is ignored and treated as base code. This is a good way to detect 439 | * base/template code usage within students' solutions while uploading base 440 | * code is unsupported by this client. Corresponds to the -m parameter of 441 | * the original client. 442 | * 443 | * @param optM 444 | * times a passage may appear before it is ignored 445 | */ 446 | public void setOptM(long optM) { 447 | this.optM = optM; 448 | } 449 | 450 | /** 451 | * Sets how many matches should be displayed in the MOSS results. Default is 452 | * 250. 453 | * 454 | * @param optN 455 | * how many matches to display 456 | */ 457 | public void setOptN(long optN) { 458 | this.optN = optN; 459 | } 460 | 461 | /** 462 | * Setting this value to 1 enables the experimental MOSS server. This is 463 | * pretty much untested. Default is 0. 464 | * 465 | * @param optX 466 | * 0 or 1. 0: regular MOSS server, 1: experimental MOSS server. 467 | */ 468 | public void setOptX(int optX) { 469 | this.optX = optX; 470 | } 471 | 472 | public void setPort(int port) { 473 | this.port = port; 474 | } 475 | 476 | public void setServer(String server) { 477 | this.server = server; 478 | } 479 | 480 | public void setSocket(Socket socket) { 481 | this.socket = socket; 482 | } 483 | 484 | /** 485 | * Set the MOSS user id for authentication with the MOSS server. 486 | * 487 | * @param userID 488 | * your personalized ID to authenticate with. It should look like 489 | * /[0-9]+/ and is available on the MOSS page located at 490 | * http://theory.stanford.edu/~aiken/moss/ 491 | */ 492 | public void setUserID(String userID) { 493 | this.userID = userID; 494 | } 495 | 496 | /** 497 | * Uploads a single file to the MOSS server. 498 | * 499 | * @param file 500 | * the source code file to be uploaded 501 | * @throws IOException 502 | * if the file could not be read 503 | */ 504 | public void uploadFile(File file) throws IOException { 505 | uploadFile(file, false); 506 | } 507 | 508 | /** 509 | * Uploads a single base file to the MOSS server. 510 | * 511 | * @param file 512 | * the base file to be uploaded 513 | * @throws IOException 514 | * if the file could not be read 515 | */ 516 | public void uploadBaseFile(File file) throws IOException { 517 | uploadFile(file, true); 518 | } 519 | 520 | /** 521 | * Uploads a single file to the MOSS server. 522 | * 523 | * @param file 524 | * the source code file to be uploaded 525 | * @param isBaseFile 526 | * true is base file. false otherwise. 527 | * @throws IOException 528 | * if the file could not be read 529 | */ 530 | @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "VA_FORMAT_STRING_USES_NEWLINE", justification = "We do want platform-independent newline here.") 531 | public void uploadFile(File file, boolean isBaseFile) throws IOException { 532 | if (currentStage != Stage.AWAITING_FILES 533 | && currentStage != Stage.AWAITING_QUERY) { 534 | throw new RuntimeException( 535 | "Cannot upload file. Client is either not initialized properly or the connection is already closed"); 536 | } 537 | byte[] fileBytes = FileUtils.readFileToByteArray(file); 538 | String filename = normalizeFilename(file.getAbsolutePath()); 539 | String uploadString = String.format(Locale.ENGLISH, 540 | "file %d %s %d %s\n", // format: 541 | isBaseFile ? 0 : getIncSetID(), // 1. setID 542 | language, // 2. language 543 | fileBytes.length, // 3. size 544 | /* 545 | * Use Unix-style path to remain consistent. TODO test this with 546 | * non-local files, e.g. on network shares 547 | */ 548 | filename); // 4. file path 549 | System.out.println("uploading file: " + filename); 550 | out.write(uploadString.getBytes(Charsets.US_ASCII)); 551 | out.write(fileBytes); 552 | 553 | currentStage = Stage.AWAITING_QUERY; 554 | 555 | } 556 | 557 | public String normalizeFilename(String filename) { 558 | String result = Normalizer.normalize(filename, Normalizer.Form.NFD); 559 | result = FilenameUtils.normalizeNoEndSeparator(result, true) 560 | .replaceAll("[^\\p{ASCII}]", ""); 561 | return result; 562 | } 563 | 564 | } 565 | --------------------------------------------------------------------------------