diff --git a/SanityCheck.jar b/SanityCheck.jar index 1cb9790d50fd09037c2cbb81229ed229ad171df0..bfde3d7a88d36b4a699b06f936bf3abd37a3d929 100644 Binary files a/SanityCheck.jar and b/SanityCheck.jar differ diff --git a/devTools/javaSanityCheck/src/Main.java b/devTools/javaSanityCheck/src/Main.java index 776e4430c37070a5bf5333be0f091f1382f45d35..024f1626d4c13b8028dc3f23c7b0cdeeede6cdcc 100644 --- a/devTools/javaSanityCheck/src/Main.java +++ b/devTools/javaSanityCheck/src/Main.java @@ -3,10 +3,15 @@ package org.arkerthan.sanityCheck; import org.arkerthan.sanityCheck.element.AngleBracketElement; import org.arkerthan.sanityCheck.element.AtElement; import org.arkerthan.sanityCheck.element.Element; +import org.arkerthan.sanityCheck.element.KnownHtmlElement; +import org.arkerthan.sanityCheck.tag.HtmlTag; +import org.arkerthan.sanityCheck.tag.Tag; import java.io.*; import java.nio.charset.Charset; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -14,7 +19,7 @@ import java.util.Stack; public class Main { - public static StringSearchTree htmlTags; + public static TagSearchTree<HtmlTag> htmlTags; private static String currentFile; private static int currentLine, currentPosition; private static Stack<Element> stack; @@ -24,22 +29,8 @@ public class Main { public static void main(String[] args) { setupExclude(); setupHtmlTags(); - - // replace this with a known encoding if possible - Charset encoding = Charset.defaultCharset(); - for (String filename : args) { - if (!excluded(filename)) { - currentFile = filename; - currentLine = 1; - stack = new Stack<>(); - File file = new File(filename); - try { - handleFile(file, encoding); - } catch (IOException e) { - System.err.println("Couldn't read " + filename); - } - } - } + Path workingDir = Paths.get("").toAbsolutePath(); + runSanityCheckInDirectory(workingDir, new File("src/")); //handle errors for (SyntaxError e : @@ -48,26 +39,76 @@ public class Main { } } + + /** + * Goes through the whole directory including subdirectories and runs + * sanitycheck() on all .tw files + * + * @param dir to be checked + */ + private static void runSanityCheckInDirectory(Path workingDir, File dir) { + + //get all the files from a directory + try { + for (File file : dir.listFiles()) { + if (file.isFile()) { + String path = file.getAbsolutePath(); + if (path.endsWith(".tw")) { + sanityCheck(workingDir.relativize(file.toPath())); + } + } else if (file.isDirectory()) { + runSanityCheckInDirectory(workingDir, file.getAbsoluteFile()); + } + } + } catch (NullPointerException e) { + System.err.println("Couldn't find directory " + dir.getPath()); + } + } + + private static void sanityCheck(Path path) { + File file = path.toFile(); + + // replace this with a known encoding if possible + Charset encoding = Charset.defaultCharset(); + + if (!excluded(file.getPath())) { + try { + handleFile(file, encoding); + } catch (IOException e) { + System.err.println("Couldn't read " + file); + } + } + } + + private static void setupHtmlTags() { //preparing excluding folders - List<String> htmlTagsList = new ArrayList<>(); + List<Tag> htmlTagsList = new ArrayList<>(); try { Files.lines(new File("devTools/javaSanityCheck/htmlTags").toPath()).map(String::trim) .filter(s -> !s.startsWith("#")) - .forEach(htmlTagsList::add); + .forEach(s -> htmlTagsList.add(parseTag(s))); } catch (IOException e) { System.err.println("Couldn't read devTools/javaSanityCheck/htmlTags"); } try { - htmlTags = StringSearchTree.generateTree(htmlTagsList); + htmlTags = new TagSearchTree(htmlTagsList); } catch (ArrayIndexOutOfBoundsException e) { System.err.println("Illegal Character in devTools/javaSanityCheck/htmlTags"); System.exit(-1); } } + private static HtmlTag parseTag(String s) { + String[] st = s.split(";"); + if (st.length > 1 && st[1].equals("1")) { + return new HtmlTag(st[0], false); + } + return new HtmlTag(st[0], true); + } + private static void setupExclude() { //preparing excluding folders List<String> excludedList = new ArrayList<>(); @@ -91,8 +132,11 @@ public class Main { return false; } - private static void handleFile(File file, Charset encoding) throws IOException { + currentFile = file.getPath(); + currentLine = 1; + stack = new Stack<>(); + try (InputStream in = new FileInputStream(file); Reader reader = new InputStreamReader(in, encoding); // buffer for efficiency @@ -122,28 +166,51 @@ public class Main { try { change = stack.peek().handleChar(c); } catch (SyntaxError e) { - e.setFile(currentFile); - e.setLine(currentLine); - e.setPosition(currentPosition); change = e.getChange(); - errors.add(e); + addError(e); } if (change > 0) { if (change == 2) { stack.pop(); } + if (change == 3) { + KnownHtmlElement k = stack.pop().getKnownElement(); + if (k.isOpening()) { + stack.push(k); + } else if (stack.empty()) { + addError(new SyntaxError("Closed HTML tag \"" + k.getStatement() + "\" without having any open tags.", -1)); + } else if (stack.peek() instanceof KnownHtmlElement) { + KnownHtmlElement kFirst = (KnownHtmlElement) stack.peek(); + if (!kFirst.isMatchingElement(k)) { + addError(new SyntaxError("Opening HTML tag \"" + kFirst.getStatement() + + "\" does not match closing tag \"" + k.getStatement() + "\".", -1)); + } + stack.pop(); + } else { + addError(new SyntaxError("Closing HTML tag \"" + k.getStatement() + "\" inside " + + "another tag: " + stack.peek().getShortDescription(), -1, true)); + } + } return; } } + //innermost element was uninterested, trying to find matching element switch (c) { case '@': - stack.push(new AtElement()); + stack.push(new AtElement(currentLine, currentPosition)); break; case '<': - stack.push(new AngleBracketElement()); + stack.push(new AngleBracketElement(currentLine, currentPosition)); break; } } + + private static void addError(SyntaxError e) { + e.setFile(currentFile); + e.setLine(currentLine); + e.setPosition(currentPosition); + errors.add(e); + } } diff --git a/devTools/javaSanityCheck/src/StringSearchTree.java b/devTools/javaSanityCheck/src/StringSearchTree.java deleted file mode 100644 index b1458b397bfc48525c47a9010964ae4165b46c15..0000000000000000000000000000000000000000 --- a/devTools/javaSanityCheck/src/StringSearchTree.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.arkerthan.sanityCheck; - -import java.util.List; - -public class StringSearchTree { - private static final int SIZE = 128; - private String element = null; - private StringSearchTree[] branches; - - private StringSearchTree() { - branches = new StringSearchTree[SIZE]; - } - - - public static StringSearchTree generateTree(List<String> list) { - StringSearchTree tree = new StringSearchTree(); - - for (String s : - list) { - tree.add(s, 0); - } - - return tree; - } - - private void add(String s, int index) { - if (s.length() == index) { - element = s; - } else { - char c = s.charAt(index); - if (branches[c] == null) { - branches[c] = new StringSearchTree(); - } - branches[c].add(s, index + 1); - } - } - - public StringSearchTree getBranch(char c) { - if (c >= SIZE) return null; - return branches[c]; - } - - public String getElement() { - return element; - } - -} diff --git a/devTools/javaSanityCheck/src/SyntaxError.java b/devTools/javaSanityCheck/src/SyntaxError.java index ed2f1f3da76aa4195050c6f20014e3dc8bfb6259..90d9db7333c3b82728fc1c21dad4591e2c9d16bf 100644 --- a/devTools/javaSanityCheck/src/SyntaxError.java +++ b/devTools/javaSanityCheck/src/SyntaxError.java @@ -4,7 +4,7 @@ public class SyntaxError extends Exception { private String file; private int line, position; private String description; - private int change; + private int change; //see Element for values; -1 means not thrown private boolean warning = false; public SyntaxError(String description, int change) { diff --git a/devTools/javaSanityCheck/src/TagSearchTree.java b/devTools/javaSanityCheck/src/TagSearchTree.java new file mode 100644 index 0000000000000000000000000000000000000000..567f33f35d7d4aef7bd2b2db70f5b49c0ea6b01a --- /dev/null +++ b/devTools/javaSanityCheck/src/TagSearchTree.java @@ -0,0 +1,50 @@ +package org.arkerthan.sanityCheck; + +import org.arkerthan.sanityCheck.tag.Tag; + +import java.util.List; + +public class TagSearchTree<E extends Tag> { + private static final int SIZE = 128; + private E element = null; + private TagSearchTree<E>[] branches; + private String path; + + private TagSearchTree() { + branches = new TagSearchTree[SIZE]; + } + + public TagSearchTree(List<E> list) { + this(); + for (E e : + list) { + this.add(e, 0); + } + } + + private void add(E e, int index) { + path = e.tag.substring(0,index); + if (e.tag.length() == index) { + element = e; + } else { + char c = e.tag.charAt(index); + if (branches[c] == null) { + branches[c] = new TagSearchTree<>(); + } + branches[c].add(e, index + 1); + } + } + + public TagSearchTree<E> getBranch(char c) { + if (c >= SIZE) return null; + return branches[c]; + } + + public E getElement() { + return element; + } + + public String getPath() { + return path; + } +} diff --git a/devTools/javaSanityCheck/src/UnknownState.java b/devTools/javaSanityCheck/src/UnknownState.java deleted file mode 100644 index 3344f09d8f08bd838c127276321c02a774cd8106..0000000000000000000000000000000000000000 --- a/devTools/javaSanityCheck/src/UnknownState.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.arkerthan.sanityCheck; - -public class UnknownState extends RuntimeException { - - public UnknownState(int state) { - super(String.valueOf(state)); - } -} diff --git a/devTools/javaSanityCheck/src/UnknownStateException.java b/devTools/javaSanityCheck/src/UnknownStateException.java new file mode 100644 index 0000000000000000000000000000000000000000..dceb35bc3d5b57fa3d710e9e61d3ce427a3499c3 --- /dev/null +++ b/devTools/javaSanityCheck/src/UnknownStateException.java @@ -0,0 +1,8 @@ +package org.arkerthan.sanityCheck; + +public class UnknownStateException extends RuntimeException { + + public UnknownStateException(int state) { + super(String.valueOf(state)); + } +} diff --git a/devTools/javaSanityCheck/src/element/AngleBracketElement.java b/devTools/javaSanityCheck/src/element/AngleBracketElement.java index 3453835add6620de387b422bbae1031d456a53d8..35877dec434f5795fa0b81be43847b0a4e061104 100644 --- a/devTools/javaSanityCheck/src/element/AngleBracketElement.java +++ b/devTools/javaSanityCheck/src/element/AngleBracketElement.java @@ -1,14 +1,13 @@ package org.arkerthan.sanityCheck.element; import org.arkerthan.sanityCheck.Main; -import org.arkerthan.sanityCheck.StringSearchTree; import org.arkerthan.sanityCheck.SyntaxError; -import org.arkerthan.sanityCheck.UnknownState; +import org.arkerthan.sanityCheck.TagSearchTree; +import org.arkerthan.sanityCheck.UnknownStateException; +import org.arkerthan.sanityCheck.tag.HtmlTag; public class AngleBracketElement extends Element { private int state = 0; - private StringSearchTree tree; - /* -1 - </ 0 - initial: < @@ -20,8 +19,15 @@ public class AngleBracketElement extends Element { -4 - trying to complete HTML tag: </tag> 5 - waiting for > -5 - expecting > + 6 - waiting for > with KnownHtmlElement */ + private TagSearchTree<HtmlTag> htmlTree; + + public AngleBracketElement(int line, int pos) { + super(line, pos); + } + @Override public int handleChar(char c) throws SyntaxError { switch (state) { @@ -43,7 +49,7 @@ public class AngleBracketElement extends Element { default: try { state = 4; - tree = Main.htmlTags; + htmlTree = Main.htmlTags; return handleOpeningHTML(c); } catch (SyntaxError e) { state = 1; @@ -55,7 +61,7 @@ public class AngleBracketElement extends Element { throw new SyntaxError("Empty Statement?", 2, true); } state = -4; - tree = Main.htmlTags; + htmlTree = Main.htmlTags; return handleClosingHTML(c); case 1: if (c == '<') { @@ -98,8 +104,14 @@ public class AngleBracketElement extends Element { if (c == '>') return 2; throw new SyntaxError("Closing \">\" missing [2]", 2); + case 6: + if (c == '>') + return 3; + if (c == '@') //@ inside HTML tags is allowed + return 1; + break; default: - throw new UnknownState(state); + throw new UnknownStateException(state); } return 0; } @@ -107,22 +119,31 @@ public class AngleBracketElement extends Element { private int handleOpeningHTML(char c) throws SyntaxError { if (c == ' ') { state = 5; - if (tree.getElement() == null) { + if (htmlTree.getElement() == null) { throw new SyntaxError("Unknown HTML tag", 1); } + if (!htmlTree.getElement().single) { + k = new KnownHtmlElement(line, pos, true, htmlTree.getElement().tag); + state = 6; + return 1; + } return 1; } if (c == '>') { - if (tree.getElement() == null) { + if (htmlTree.getElement() == null) { throw new SyntaxError("Unknown HTML tag", 2); } + if (!htmlTree.getElement().single) { + k = new KnownHtmlElement(line, pos, true, htmlTree.getElement().tag); + return 3; + } return 2; } - tree = tree.getBranch(c); - if (tree == null) { + htmlTree = htmlTree.getBranch(c); + if (htmlTree == null) { state = 5; - throw new SyntaxError("Unknown HTML tag or closing \">\" missing, found "+c, 1); + throw new SyntaxError("Unknown HTML tag or closing \">\" missing, found " + c, 1); } return 1; @@ -130,18 +151,63 @@ public class AngleBracketElement extends Element { private int handleClosingHTML(char c) throws SyntaxError { if (c == '>') { - if (tree.getElement() == null) { + if (htmlTree.getElement() == null) { throw new SyntaxError("Unknown HTML tag", 2); } - return 2; + if (htmlTree.getElement().single) { + throw new SyntaxError("Single HTML tag used as closing Tag: " + htmlTree.getElement().tag, 2); + } + k = new KnownHtmlElement(line, pos, false, htmlTree.getElement().tag); + return 3; } - tree = tree.getBranch(c); - if (tree == null) { + htmlTree = htmlTree.getBranch(c); + if (htmlTree == null) { state = -5; - throw new SyntaxError("Unknown HTML tag or closing \">\" missing, found "+c, 1); + throw new SyntaxError("Unknown HTML tag or closing \">\" missing, found " + c, 1); } return 1; } + + @Override + public String getShortDescription() { + StringBuilder builder = new StringBuilder(); + builder.append(line).append(":").append(pos).append(" "); + switch (state) { + case 0: + builder.append("<"); + break; + case 1: + builder.append("<<"); + break; + case -1: + builder.append("</"); + break; + case 2: + builder.append("<<???"); + break; + case 3: + builder.append("<<???>"); + break; + case 4: + builder.append("<").append(htmlTree.getPath()); + break; + case -4: + builder.append("</").append(htmlTree.getPath()); + break; + case 5: + builder.append("<").append(htmlTree.getPath()).append(" ???"); + break; + case -5: + builder.append("</").append(htmlTree.getPath()); + break; + case 6: + builder.append("<").append(htmlTree.getPath()).append(" ???"); + break; + default: + throw new UnknownStateException(state); + } + return builder.toString(); + } } diff --git a/devTools/javaSanityCheck/src/element/AtElement.java b/devTools/javaSanityCheck/src/element/AtElement.java index cedf439542f43b2bed0459508f4836ad0a7e0bde..0dad04247c50f2a9a77bb9ce584ded8de0377ed2 100644 --- a/devTools/javaSanityCheck/src/element/AtElement.java +++ b/devTools/javaSanityCheck/src/element/AtElement.java @@ -1,7 +1,7 @@ package org.arkerthan.sanityCheck.element; import org.arkerthan.sanityCheck.SyntaxError; -import org.arkerthan.sanityCheck.UnknownState; +import org.arkerthan.sanityCheck.UnknownStateException; public class AtElement extends Element { private int state = 0; @@ -14,6 +14,10 @@ public class AtElement extends Element { // example: @@.red;some text@@ + public AtElement(int line, int pos) { + super(line, pos); + } + @Override public int handleChar(char c) throws SyntaxError { switch (state) { @@ -65,8 +69,37 @@ public class AtElement extends Element { throw new SyntaxError("Closing \"@\" missing.", 2); } default: - throw new UnknownState(state); + throw new UnknownStateException(state); } return 0; } + + @Override + public String getShortDescription() { + StringBuilder builder = new StringBuilder(); + builder.append(line).append(":").append(pos).append(" "); + switch (state) { + case 0: + builder.append("@"); + break; + case 1: + builder.append("@@"); + break; + case 2: + builder.append("@@."); + break; + case 3: + builder.append("@@.???"); + break; + case 4: + builder.append("@@???"); + break; + case 5: + builder.append("@@???@"); + break; + default: + throw new UnknownStateException(state); + } + return builder.toString(); + } } diff --git a/devTools/javaSanityCheck/src/element/Element.java b/devTools/javaSanityCheck/src/element/Element.java index ed289d2decf21a6e461b1cbafadf061a7706a4d8..5393426861853b5397f67fdb6cd85926d8481c72 100644 --- a/devTools/javaSanityCheck/src/element/Element.java +++ b/devTools/javaSanityCheck/src/element/Element.java @@ -3,15 +3,39 @@ package org.arkerthan.sanityCheck.element; import org.arkerthan.sanityCheck.SyntaxError; public abstract class Element { + protected KnownHtmlElement k; + protected int line, pos; + + /** + * + * @param line Line the instance was created + * @param pos Position in line the instance was created + */ + protected Element(int line, int pos) { + this.line = line; + this.pos = pos; + } /** * Parses a Char and returns an int depending on the state of the element * 0 - the Element did nothing * 1 - the Element changed state * 2 - the Element is finished + * 3 - the Element is finished and a KnownHtmlElement was generated + * * @param c * @return * @throws Error */ public abstract int handleChar(char c) throws SyntaxError; + + public KnownHtmlElement getKnownElement() { + return k; + } + + /** + * + * @return a short description usually based on state and position of the Element + */ + public abstract String getShortDescription(); } diff --git a/devTools/javaSanityCheck/src/element/KnownElement.java b/devTools/javaSanityCheck/src/element/KnownHtmlElement.java similarity index 56% rename from devTools/javaSanityCheck/src/element/KnownElement.java rename to devTools/javaSanityCheck/src/element/KnownHtmlElement.java index 34390b7ef3efb04d784a1ccbdfaf86b5edf10e55..b3b136733787bcea1261e15684fac14770e8ea66 100644 --- a/devTools/javaSanityCheck/src/element/KnownElement.java +++ b/devTools/javaSanityCheck/src/element/KnownHtmlElement.java @@ -1,24 +1,33 @@ package org.arkerthan.sanityCheck.element; -import org.arkerthan.sanityCheck.SyntaxError; - -public class KnownElement extends Element { +public class KnownHtmlElement extends Element { private boolean opening; private String statement; - public KnownElement(boolean opening, String statement) { + public KnownHtmlElement(int line, int pos, boolean opening, String statement) { + super(line, pos); this.opening = opening; this.statement = statement; } @Override - public int handleChar(char c) throws SyntaxError { + public int handleChar(char c) { return 0; } + @Override + public String getShortDescription() { + StringBuilder builder = new StringBuilder(); + builder.append(line).append(":").append(pos).append(" <"); + if(!opening){ + builder.append("/"); + } + return builder.append(statement).toString(); + } + /** - * @return true, if it needs another Known Element to close it. + * @return true, if it needs another Known Element to close it, false if it closes another element. */ public boolean isOpening() { return opening; @@ -28,7 +37,7 @@ public class KnownElement extends Element { * @param k Element to be checked * @return true if given Element closes Element */ - public boolean isClosingElement(KnownElement k) { + public boolean isMatchingElement(KnownHtmlElement k) { return k.statement.equals(this.statement); } diff --git a/devTools/javaSanityCheck/src/tag/HtmlTag.java b/devTools/javaSanityCheck/src/tag/HtmlTag.java new file mode 100644 index 0000000000000000000000000000000000000000..70bff2c5a32d27804cb2e95839eddf595fb4693c --- /dev/null +++ b/devTools/javaSanityCheck/src/tag/HtmlTag.java @@ -0,0 +1,7 @@ +package org.arkerthan.sanityCheck.tag; + +public class HtmlTag extends Tag { + public HtmlTag(String tag, boolean single) { + super(tag, single); + } +} diff --git a/devTools/javaSanityCheck/src/tag/Tag.java b/devTools/javaSanityCheck/src/tag/Tag.java new file mode 100644 index 0000000000000000000000000000000000000000..386292b48eb43f2eeec4721addc62f5fbc328159 --- /dev/null +++ b/devTools/javaSanityCheck/src/tag/Tag.java @@ -0,0 +1,11 @@ +package org.arkerthan.sanityCheck.tag; + +public abstract class Tag { + public final String tag; + public final boolean single; + + protected Tag(String tag, boolean single) { + this.tag = tag; + this.single = single; + } +} diff --git a/sanityCheck-java b/sanityCheck-java index c564d01dfc659224509727bfa9098774f886dea2..455bbfbc243ec664f5aac2daa2ad74903d104962 100755 --- a/sanityCheck-java +++ b/sanityCheck-java @@ -1 +1 @@ -git ls-files "src/*.tw" | xargs java -jar SanityCheck.jar +java -jar SanityCheck.jar diff --git a/sanityCheck-java.bat b/sanityCheck-java.bat new file mode 100644 index 0000000000000000000000000000000000000000..455bbfbc243ec664f5aac2daa2ad74903d104962 --- /dev/null +++ b/sanityCheck-java.bat @@ -0,0 +1 @@ +java -jar SanityCheck.jar