package name.graf.emanuel.testfileeditor.model; import java.util.HashSet; import java.util.Observable; import java.util.Observer; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Position; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.MarkerUtilities; import name.graf.emanuel.testfileeditor.Activator; import name.graf.emanuel.testfileeditor.model.node.Class; import name.graf.emanuel.testfileeditor.model.node.Expected; import name.graf.emanuel.testfileeditor.model.node.File; import name.graf.emanuel.testfileeditor.model.node.Language; import name.graf.emanuel.testfileeditor.model.node.Selection; import name.graf.emanuel.testfileeditor.model.node.Test; public class TestFile extends Observable { private static final String CONFIG_FILE_STRING = ".config"; private static final String MARKER_LINES_STRING = "markerLines="; private static final String CONFIG_TEXT_STRING_REGEX = "(//@\\.config\\r?\\n(\\s*^(?!//@).*\\r?\\n)*?\\s*markerLines=((\\d+,)*\\d+)?)"; private static final String MARKER_ID_DUPLICATE_TEST = "name.graf.emanuel.testfileeditor.markers.DuplicateTestMarker"; private final String fName; private final Set<Test> fTests; private final Set<Problem> fProblems; private final IDocumentProvider fProvider; private FileEditorInput fInput; public static final String PARTITION_TEST_CLASS = "__rts_class"; public static final String PARTITION_TEST_COMMENT = "__rts_comment"; public static final String PARTITION_TEST_EXPECTED = "__rts_expected"; public static final String PARTITION_TEST_FILE = "__rts_file"; public static final String PARTITION_TEST_LANGUAGE = "__rts_language"; public static final String PARTITION_TEST_NAME = "__rts_name"; public static final String PARTITION_TEST_SELECTION = "__rts_selection"; //@formatter:off public static final String[] PARTITION_TYPES = new String[] { PARTITION_TEST_CLASS, PARTITION_TEST_COMMENT, PARTITION_TEST_EXPECTED, PARTITION_TEST_FILE, PARTITION_TEST_LANGUAGE, PARTITION_TEST_NAME, PARTITION_TEST_SELECTION }; //@formatter:on public TestFile(final FileEditorInput input, final IDocumentProvider provider) { fInput = input; fProvider = provider; fName = input.getName(); fTests = new HashSet<>(); fProblems = new HashSet<>(); setupPositionCategories(); } /** * @author tstauber * * Appends the passed relativeLineNo to the markerLines line in the * .config file associated with the <code>Test</code> which contains * the absolute lineNo passed. If no <code>Test</code> or markerList * could be found the function will not change the file and returns * -1. * * @param int * lineNo Absolute line number in the file * @param int * relativeLineNo Relative line number to the virtual file * @return int The length difference from before to after. */ public int addLineNoToMarkerList(final int lineNo, final int relativeLineNo) { final IDocument document = fProvider.getDocument(fInput); int deltaLength = 0; try { final int offset = document.getLineOffset(lineNo); final Test test = getTest(offset); if (test == null) { return deltaLength; } deltaLength += addConfigFileIfNotExists(test); deltaLength += addMarkerLinePropertyIfNotExists(test); deltaLength += addVirtLineNumToMarkerLines(test, relativeLineNo); } catch (final IllegalStateException | BadLocationException e) { } finally { parse(); } return deltaLength; } private Test getTest(final int offset) { for (final Test test : fTests) { if (test.containsOffset(offset)) { return test; } } return null; } private String getDocText(final Position position) throws BadLocationException { return fProvider.getDocument(fInput).get(position.getOffset(), position.getLength()); } /* * @author tstauber * * Adds the relativeLineNo to the markerLines. * */ private int addVirtLineNumToMarkerLines(final Test test, final int relativeLineNo) throws BadLocationException { final File file = test.getFile(CONFIG_FILE_STRING); if (file == null) { return 0; } final String fileText = getDocText(file.getPosition()); final Pattern pattern = Pattern.compile(TestFile.CONFIG_TEXT_STRING_REGEX, Pattern.MULTILINE); final Matcher matcher = pattern.matcher(fileText); if (matcher.find()) { final int offset = file.getPosition().getOffset() + matcher.end(); String insertText = Integer.toString(relativeLineNo); int additionalLength = insertText.length(); if (matcher.group(3) != null) { insertText = ",".concat(insertText); additionalLength++; } fProvider.getDocument(fInput).replace(offset, 0, insertText); adjustPostitionLength(file, additionalLength); adjustPostitionLength(test, additionalLength); return additionalLength; } return 0; } /* * @author tstauber * * Checks if a //@.config file exists and creates one for this Test in case * it does not exist. * * return additional length * */ private int addConfigFileIfNotExists(final Test test) throws BadLocationException { if (!test.containsFile(CONFIG_FILE_STRING)) { insertFileIntoTest(test, CONFIG_FILE_STRING, 0); return Tokens.FILE.concat(CONFIG_FILE_STRING).concat("\n").length(); } return 0; } /* * @author tstauber * * Checks if a markerLines= property exists for this Test's //@.config file * and creates one in case it does not exist. * * returns the additional length * */ private int addMarkerLinePropertyIfNotExists(final Test test) throws BadLocationException { if (test.containsFile(CONFIG_FILE_STRING)) { final File file = test.getFile(CONFIG_FILE_STRING); final String fileText = getDocText(file.getPosition()); final Pattern pattern = Pattern.compile(MARKER_LINES_STRING); final Matcher matcher = pattern.matcher(fileText); if (!matcher.find()) { final String insertText = MARKER_LINES_STRING.concat("\n"); final int offset = file.getPosition().getOffset() + file.getHeadPosition().getLength(); fProvider.getDocument(fInput).replace(offset, 0, insertText); final int additionalLength = insertText.length(); adjustPostitionLength(file, additionalLength); adjustPostitionLength(test, additionalLength); return additionalLength; } } return 0; } /* * @author tstauber * * Adjusts the size of the Test's Positions. * */ private static void adjustPostitionLength(final Test test, final int additionalLength) { test.getPosition().setLength(test.getPosition().getLength() + additionalLength); } private static void adjustPostitionLength(final File file, final int additionalLength) { file.getPosition().setLength(file.getPosition().getLength() + additionalLength); } /* * @author tstauber * * Checks if a markerLines= property exists for this Test's //@.config file * and creates one in case it does not exist. * */ private void insertFileIntoTest(final Test test, final String name, final int offsetInTestBody) throws BadLocationException { final int insertOffset = test.getPosition().getOffset() + test.getHeadPosition().getLength() + offsetInTestBody; final String insertText = Tokens.FILE.concat(name).concat("\n"); final Position pos = new Position(insertOffset, insertText.length()); final Position headPos = new Position(insertOffset, insertText.length()); test.addFile(new File(name, pos, headPos, test)); adjustPostitionLength(test, insertText.length()); fProvider.getDocument(fInput).replace(insertOffset, 0, insertText); } private void setupPositionCategories() { final IDocument document = fProvider.getDocument(fInput); for (final String partition : PARTITION_TYPES) { document.addPositionCategory(partition); } } public TestFile(final FileEditorInput input, final IDocumentProvider provider, final Observer observer) { this(input, provider); addObserver(observer); } @Override public String toString() { return fName; } public Test[] getTests() { return fTests.toArray(new Test[0]); } public Set<Problem> getProblems() { return fProblems; } @Override public int hashCode() { return fName.hashCode(); } @Override public boolean equals(final Object obj) { return hashCode() == obj.hashCode(); } public void parse() { try { doParse(); notifyObservers(); } catch (BadLocationException | BadPositionCategoryException | CoreException e) { Activator.logError(e, 0); } } private void doParse() throws BadLocationException, BadPositionCategoryException, CoreException { final IDocument document = fProvider.getDocument(fInput); final int NOF_LINES = document.getNumberOfLines(); fTests.clear(); fProblems.clear(); cleanMarkers(); Test currentTest = null; Test previousTest = null; File currentFile = null; File previousFile = null; ParseState currentState = ParseState.INIT; int currentSelectionStart = 0; for (int currentLine = 0; currentLine < NOF_LINES; ++currentLine) { final int lineOffset = document.getLineOffset(currentLine); final int lineLength = document.getLineLength(currentLine); final String lineContent = document.get(lineOffset, lineLength); if (lineContent.length() > 3) { switch (lineContent.substring(0, Tokens.TOKENLENGTH)) { case Tokens.TEST: final Position headPos_TEST = new Position(lineOffset, lineLength); final Position pos_TEST = new Position(lineOffset, document.getLength() - lineOffset); document.addPosition(PARTITION_TEST_NAME, headPos_TEST); currentTest = new Test(lineContent.trim().substring(Tokens.TOKENLENGTH), pos_TEST, headPos_TEST, this); if (previousTest != null) { previousTest.getPosition().setLength(lineOffset - previousTest.getPosition().getOffset()); } previousTest = currentTest; if (fTests.contains(currentTest)) { final Problem duplicateTest = new DuplicateTest(currentTest.toString(), currentLine + 1, headPos_TEST); reportProblem(duplicateTest); fProblems.add(new DuplicateTest(currentTest.toString(), currentLine + 1, headPos_TEST)); } else { fTests.add(currentTest); } currentState = ParseState.TEST; break; case Tokens.LANGUAGE: if (previousFile != null) { previousFile.getPosition().setLength(lineOffset - previousFile.getPosition().getOffset()); previousFile = null; } if (currentTest != null) { final Position pos_LANG = new Position(lineOffset, lineLength); document.addPosition(PARTITION_TEST_LANGUAGE, pos_LANG); currentTest.setLang( new Language(lineContent.trim().substring(Tokens.TOKENLENGTH), pos_LANG, currentTest)); } break; case Tokens.EXPECTED: if (previousFile != null) { previousFile.getPosition().setLength(lineOffset - previousFile.getPosition().getOffset()); previousFile = null; } switch (currentState) { case SELECTION: final Position pos_SEL_OPEN = new Position(currentSelectionStart, lineOffset - currentSelectionStart); document.addPosition(PARTITION_TEST_EXPECTED, pos_SEL_OPEN); currentFile.setSelection(new Selection(pos_SEL_OPEN, currentFile)); currentState = ParseState.FILE; case FILE: if (currentTest != null) { final Position pos_FILE = new Position(lineOffset, lineLength); document.addPosition(PARTITION_TEST_EXPECTED, pos_FILE); currentTest.setExpected(new Expected(currentTest, lineContent.trim().substring(Tokens.TOKENLENGTH), pos_FILE)); } break; default: } break; case Tokens.FILE: if (currentTest != null) { if (currentState == ParseState.SELECTION) { final Position pos_SEL_OPEN = new Position(currentSelectionStart, lineOffset - currentSelectionStart); document.addPosition(PARTITION_TEST_SELECTION, pos_SEL_OPEN); currentFile.setSelection(new Selection(pos_SEL_OPEN, currentFile)); } final Position headPos_FILE = new Position(lineOffset, lineLength); final Position pos_FILE = new Position(lineOffset, lineLength); document.addPosition(PARTITION_TEST_FILE, headPos_FILE); currentFile = new File(lineContent.trim().substring(Tokens.TOKENLENGTH), pos_FILE, headPos_FILE, currentTest); if (previousFile != null) { previousFile.getPosition().setLength(lineOffset - previousFile.getPosition().getOffset()); } previousFile = currentFile; currentTest.addFile(currentFile); currentState = ParseState.FILE; } break; case Tokens.CLASS: if (previousFile != null) { previousFile.getPosition().setLength(lineOffset - previousFile.getPosition().getOffset()); previousFile = null; } if (currentState == ParseState.TEST) { final Position pos_CLASS = new Position(lineOffset, lineLength); document.addPosition(PARTITION_TEST_CLASS, pos_CLASS); currentTest.setClassname( new Class(lineContent.trim().substring(Tokens.TOKENLENGTH), pos_CLASS, currentTest)); } break; case Tokens.SELECTION_OPEN: if (currentState == ParseState.FILE) { currentState = ParseState.SELECTION; currentSelectionStart = lineOffset + lineContent.indexOf(Tokens.SELECTION_CLOSE); } break; } } if (lineContent.contains(Tokens.SELECTION_CLOSE) && currentState == ParseState.SELECTION) { final Position pos_SEL_CLOSE = new Position(currentSelectionStart, lineOffset + lineContent.indexOf(Tokens.SELECTION_CLOSE) - currentSelectionStart); document.addPosition(PARTITION_TEST_SELECTION, pos_SEL_CLOSE); currentFile.setSelection(new Selection(pos_SEL_CLOSE, currentFile)); currentState = ParseState.FILE; } } setChanged(); } private void reportProblem(final Problem problem) throws CoreException { final IFile file = fInput.getFile(); final IMarker marker = file.createMarker(MARKER_ID_DUPLICATE_TEST); MarkerUtilities.setCharStart(marker, problem.getPosition().offset); MarkerUtilities.setCharEnd(marker, problem.getPosition().offset + problem.getPosition().length); MarkerUtilities.setLineNumber(marker, problem.getLineNumber()); marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); marker.setAttribute(IMarker.MESSAGE, problem.getDescription()); } private void cleanMarkers() { try { final IMarker[] currentMarkers = fInput.getFile().findMarkers(MARKER_ID_DUPLICATE_TEST, false, IFile.DEPTH_INFINITE); for (final IMarker marker : currentMarkers) { final int offset = MarkerUtilities.getCharStart(marker); final int length = MarkerUtilities.getCharEnd(marker) - offset; final Position position = new Position(offset, length); Problem found = null; for (final Problem problem : fProblems) { if (problem.getPosition().equals(position)) { found = problem; break; } } if (found == null) { marker.delete(); } else { fProblems.remove(found); } } } catch (final CoreException e) { Activator.logError(e, 0); } } private enum ParseState { INIT, TEST, FILE, SELECTION } public void setInput(final FileEditorInput input) { fInput = input; setupPositionCategories(); } }