Submission.java 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. package de.jplag;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.nio.file.Files;
  5. import java.util.Collection;
  6. import java.util.Collections;
  7. import java.util.HashSet;
  8. import java.util.List;
  9. import java.util.Objects;
  10. import org.slf4j.Logger;
  11. import org.slf4j.LoggerFactory;
  12. /**
  13. * Represents a single submission. A submission can contain multiple files.
  14. */
  15. public class Submission implements Comparable<Submission> {
  16. private static final Logger logger = LoggerFactory.getLogger(Submission.class);
  17. /**
  18. * Directory name for storing submission files with parse errors if so requested.
  19. */
  20. private static final String ERROR_FOLDER = "errors";
  21. /**
  22. * Identification of the submission (often a directory or file name).
  23. */
  24. private final String name;
  25. /**
  26. * Root of the submission files (including the subdir if used).
  27. */
  28. private final File submissionRootFile;
  29. /**
  30. * Whether the submission is new. That is, must be checked for plagiarism.
  31. */
  32. private final boolean isNew;
  33. /**
  34. * Files of the submission.
  35. */
  36. private final Collection<File> files;
  37. /**
  38. * Whether an error occurred during parsing the submission files.
  39. */
  40. private boolean hasErrors;
  41. /**
  42. * Parse result, tokens from all files.
  43. */
  44. private List<Token> tokenList;
  45. /**
  46. * Base code comparison
  47. */
  48. private JPlagComparison baseCodeComparison;
  49. private final Language language;
  50. /**
  51. * Creates a submission.
  52. * @param name Identification of the submission (directory or filename).
  53. * @param submissionRootFile is the submission file, or the root of the submission itself.
  54. * @param isNew states whether the submission must be checked for plagiarism.
  55. * @param files are the files of the submissions, if the root is a single file it should just contain one file.
  56. * @param language is the language of the submission.
  57. */
  58. public Submission(String name, File submissionRootFile, boolean isNew, Collection<File> files, Language language) {
  59. this.name = name;
  60. this.submissionRootFile = submissionRootFile;
  61. this.isNew = isNew;
  62. this.files = files;
  63. this.language = language;
  64. }
  65. @Override
  66. public int compareTo(Submission other) {
  67. return name.compareTo(other.name);
  68. }
  69. @Override
  70. public boolean equals(Object obj) {
  71. if (obj == this) {
  72. return true;
  73. }
  74. if (!(obj instanceof Submission otherSubmission)) {
  75. return false;
  76. }
  77. return otherSubmission.getName().equals(name);
  78. }
  79. @Override
  80. public int hashCode() {
  81. return Objects.hash(name);
  82. }
  83. /**
  84. * @return base code comparison
  85. */
  86. public JPlagComparison getBaseCodeComparison() {
  87. return baseCodeComparison;
  88. }
  89. /**
  90. * @return a list of files this submission consists of.
  91. */
  92. public Collection<File> getFiles() {
  93. return files;
  94. }
  95. /**
  96. * @return name of the submission (directory or file name).
  97. */
  98. public String getName() {
  99. return name;
  100. }
  101. /**
  102. * @return Number of tokens in the parse result.
  103. */
  104. public int getNumberOfTokens() {
  105. if (tokenList == null) {
  106. return 0;
  107. }
  108. return tokenList.size();
  109. }
  110. /**
  111. * @return the unique file of the submission, which is either in a root folder or a subfolder of root folder when the
  112. * subdirectory option is used.
  113. */
  114. public File getRoot() {
  115. return submissionRootFile;
  116. }
  117. /**
  118. * @param subtractBaseCode If true subtract basecode matches if possible.
  119. * @return Similarity divisor for the submission.
  120. */
  121. int getSimilarityDivisor(boolean subtractBaseCode) {
  122. int divisor = getNumberOfTokens() - getFiles().size();
  123. if (subtractBaseCode && baseCodeComparison != null) {
  124. divisor -= baseCodeComparison.getNumberOfMatchedTokens();
  125. }
  126. return divisor;
  127. }
  128. /**
  129. * @return unmodifiable list of tokens generated by parsing the submission.
  130. */
  131. public List<Token> getTokenList() {
  132. return tokenList == null ? null : Collections.unmodifiableList(tokenList);
  133. }
  134. /**
  135. * @return Whether a comparison between the submission and the base code is available.
  136. */
  137. public boolean hasBaseCodeMatches() {
  138. return baseCodeComparison != null;
  139. }
  140. /**
  141. * @return true if at least one error occurred while parsing this submission; false otherwise.
  142. */
  143. public boolean hasErrors() {
  144. return hasErrors;
  145. }
  146. /**
  147. * @return whether the submission is new, That is, must be checked for plagiarism.
  148. */
  149. public boolean isNew() {
  150. return isNew;
  151. }
  152. /**
  153. * Sets the base code comparison
  154. * @param baseCodeComparison is submissions matches with the base code
  155. */
  156. public void setBaseCodeComparison(JPlagComparison baseCodeComparison) {
  157. this.baseCodeComparison = baseCodeComparison;
  158. }
  159. /**
  160. * Sets the tokens that have been parsed from the files this submission consists of.
  161. * @param tokenList is the list of these tokens.
  162. */
  163. public void setTokenList(List<Token> tokenList) {
  164. this.tokenList = tokenList;
  165. }
  166. /**
  167. * String representation of the code files contained in this submission, annotated with all tokens.
  168. * @return the annotated code as string.
  169. */
  170. public String getTokenAnnotatedSourceCode() {
  171. return TokenPrinter.printTokens(tokenList, submissionRootFile);
  172. }
  173. @Override
  174. public String toString() {
  175. return name;
  176. }
  177. /**
  178. * This method is used to copy files that can not be parsed to a special folder.
  179. */
  180. private void copySubmission() {
  181. File rootDirectory = submissionRootFile.getParentFile();
  182. assert rootDirectory != null;
  183. File submissionDirectory = createSubdirectory(rootDirectory, ERROR_FOLDER, language.getIdentifier(), name);
  184. for (File file : files) {
  185. try {
  186. Files.copy(file.toPath(), new File(submissionDirectory, file.getName()).toPath());
  187. } catch (IOException exception) {
  188. logger.error("Error copying file: " + exception.getMessage(), exception);
  189. }
  190. }
  191. }
  192. private static File createSubdirectory(File parent, String... subdirectoryNames) {
  193. File subdirectory = parent;
  194. for (String name : subdirectoryNames) {
  195. subdirectory = new File(subdirectory, name);
  196. }
  197. if (!subdirectory.exists()) {
  198. subdirectory.mkdirs();
  199. }
  200. return subdirectory;
  201. }
  202. /* package-private */ void markAsErroneous() {
  203. hasErrors = true;
  204. }
  205. /**
  206. * Parse files of the submission.
  207. * @return Whether parsing was successful.
  208. */
  209. /* package-private */ boolean parse(boolean debugParser) {
  210. if (files == null || files.isEmpty()) {
  211. logger.error("ERROR: nothing to parse for submission \"{}\"", name);
  212. tokenList = null;
  213. hasErrors = true; // invalidate submission
  214. return false;
  215. }
  216. try {
  217. tokenList = language.parse(new HashSet<>(files));
  218. } catch (ParsingException e) {
  219. logger.warn("Failed to parse submission {} with error {}", this, e);
  220. tokenList = null;
  221. hasErrors = true;
  222. if (debugParser) {
  223. copySubmission();
  224. }
  225. return false;
  226. }
  227. if (tokenList.size() < 3) {
  228. logger.error("Submission \"{}\" is too short!", name);
  229. tokenList = null;
  230. hasErrors = true; // invalidate submission
  231. return false;
  232. }
  233. return true;
  234. }
  235. }