Use provider interfaces for the parser; enhance test case.
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 9 Jun 2011 10:33:49 +0000 (12:33 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 9 Jun 2011 10:34:11 +0000 (12:34 +0200)
This fixes #124.

src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/Options.java
src/main/java/net/pterodactylus/sone/core/PostProvider.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/core/SoneProvider.java [new file with mode: 0644]
src/main/java/net/pterodactylus/sone/text/SoneTextParser.java
src/main/java/net/pterodactylus/sone/text/TextFilter.java
src/main/java/net/pterodactylus/sone/web/WebInterface.java
src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java [new file with mode: 0644]

index 1e68c63..1c67231 100644 (file)
@@ -65,7 +65,7 @@ import freenet.keys.FreenetURI;
  *
  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
  */
-public class Core implements IdentityListener, UpdateListener {
+public class Core implements IdentityListener, UpdateListener, SoneProvider, PostProvider {
 
        /**
         * Enumeration for the possible states of a {@link Sone}.
@@ -352,6 +352,7 @@ public class Core implements IdentityListener, UpdateListener {
         * @return The Sone with the given ID, or {@code null} if there is no such
         *         Sone
         */
+       @Override
        public Sone getSone(String id, boolean create) {
                if (isLocalSone(id)) {
                        return getLocalSone(id);
@@ -574,6 +575,7 @@ public class Core implements IdentityListener, UpdateListener {
         *            exists, {@code false} to return {@code null}
         * @return The post, or {@code null} if there is no such post
         */
+       @Override
        public Post getPost(String postId, boolean create) {
                synchronized (posts) {
                        Post post = posts.get(postId);
index 7392da2..1bdf21f 100644 (file)
@@ -193,6 +193,7 @@ public class Options {
                /**
                 * {@inheritDoc}
                 */
+               @Override
                public boolean validate(T value) {
                        return (validator == null) || (value == null) || validator.validate(value);
                }
diff --git a/src/main/java/net/pterodactylus/sone/core/PostProvider.java b/src/main/java/net/pterodactylus/sone/core/PostProvider.java
new file mode 100644 (file)
index 0000000..edede44
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Sone - PostProvider.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core;
+
+import net.pterodactylus.sone.data.Post;
+
+/**
+ * Interface for objects that can provide {@link Post}s by their ID.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface PostProvider {
+
+       /**
+        * Returns the post with the given ID, if it exists. If it does not exist
+        * and {@code create} is {@code false}, {@code null} is returned; otherwise,
+        * a new post with the given ID is created and returned.
+        *
+        * @param postId
+        *            The ID of the post to return
+        * @param create
+        *            {@code true} to create a new post if no post with the given ID
+        *            exists, {@code false} to return {@code null} instead
+        * @return The post with the given ID, or {@code null}
+        */
+       public Post getPost(String postId, boolean create);
+
+}
diff --git a/src/main/java/net/pterodactylus/sone/core/SoneProvider.java b/src/main/java/net/pterodactylus/sone/core/SoneProvider.java
new file mode 100644 (file)
index 0000000..dcf0f61
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Sone - SoneProvider.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.core;
+
+import net.pterodactylus.sone.data.Sone;
+
+/**
+ * Interface for objects that can provide {@link Sone}s by their ID.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface SoneProvider {
+
+       /**
+        * Returns the Sone with the given ID, if it exists. If it does not exist
+        * and {@code create} is {@code false}, {@code null} is returned; otherwise,
+        * a new Sone with the given ID is created and returned.
+        *
+        * @param soneId
+        *            The ID of the Sone to return
+        * @param create
+        *            {@code true} to create a new Sone if no Sone with the given ID
+        *            exists, {@code false} to return {@code null} instead
+        * @return The Sone with the given ID, or {@code null}
+        */
+       public Sone getSone(String soneId, boolean create);
+
+}
index 98bb800..94c3db4 100644 (file)
@@ -26,7 +26,8 @@ import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import net.pterodactylus.sone.core.Core;
+import net.pterodactylus.sone.core.PostProvider;
+import net.pterodactylus.sone.core.SoneProvider;
 import net.pterodactylus.sone.data.Post;
 import net.pterodactylus.sone.data.Sone;
 import net.pterodactylus.util.logging.Logging;
@@ -78,17 +79,23 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
 
        }
 
-       /** The core. */
-       private final Core core;
+       /** The Sone provider. */
+       private final SoneProvider soneProvider;
+
+       /** The post provider. */
+       private final PostProvider postProvider;
 
        /**
         * Creates a new freenet link parser.
         *
-        * @param core
-        *            The core
+        * @param soneProvider
+        *            The Sone provider
+        * @param postProvider
+        *            The post provider
         */
-       public SoneTextParser(Core core) {
-               this.core = core;
+       public SoneTextParser(SoneProvider soneProvider, PostProvider postProvider) {
+               this.soneProvider = soneProvider;
+               this.postProvider = postProvider;
        }
 
        //
@@ -174,7 +181,7 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                        }
                                        if (line.length() >= (next + 7 + 43)) {
                                                String soneId = line.substring(next + 7, next + 50);
-                                               Sone sone = core.getSone(soneId, false);
+                                               Sone sone = soneProvider.getSone(soneId, false);
                                                if (sone != null) {
                                                        parts.add(new SonePart(sone));
                                                } else {
@@ -193,7 +200,7 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                        }
                                        if (line.length() >= (next + 7 + 36)) {
                                                String postId = line.substring(next + 7, next + 43);
-                                               Post post = core.getPost(postId, false);
+                                               Post post = postProvider.getPost(postId, false);
                                                if ((post != null) && (post.getSone() != null)) {
                                                        String postText = post.getText();
                                                        postText = postText.substring(0, Math.min(postText.length(), 20)) + "…";
@@ -218,7 +225,9 @@ public class SoneTextParser implements Parser<SoneTextParserContext> {
                                        if (!lastLineEmpty && lineComplete) {
                                                parts.add(new PlainTextPart("\n" + line.substring(0, next)));
                                        } else {
-                                               parts.add(new PlainTextPart(line.substring(0, next)));
+                                               if (next > 0) {
+                                                       parts.add(new PlainTextPart(line.substring(0, next)));
+                                               }
                                        }
                                        String link = line.substring(next, nextSpace);
                                        String name = link;
index 5744368..9e3587c 100644 (file)
 
 package net.pterodactylus.sone.text;
 
-import java.util.logging.Logger;
-
-import net.pterodactylus.util.logging.Logging;
-
 /**
  * Filter for newly inserted text. This filter strips HTTP links to the local
  * node of identifying marks, e.g. a link to “http://localhost:8888/KSK@gpl.txt”
@@ -32,9 +28,6 @@ import net.pterodactylus.util.logging.Logging;
  */
 public class TextFilter {
 
-       /** The logger. */
-       private static final Logger logger = Logging.getLogger(TextFilter.class);
-
        /**
         * Filters the given text, stripping the host header part for links to the
         * local node.
index 8eb0122..c9547fb 100644 (file)
@@ -196,7 +196,7 @@ public class WebInterface implements CoreListener {
        public WebInterface(SonePlugin sonePlugin) {
                this.sonePlugin = sonePlugin;
                formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword();
-               soneTextParser = new SoneTextParser(getCore());
+               soneTextParser = new SoneTextParser(getCore(), getCore());
 
                templateContextFactory = new TemplateContextFactory();
                templateContextFactory.addAccessor(Object.class, new ReflectionAccessor());
@@ -668,6 +668,15 @@ public class WebInterface implements CoreListener {
                }
        }
 
+       /**
+        * Returns all {@link Core#isLocalSone(Sone) local Sone}s that are
+        * referenced by {@link SonePart}s in the given text (after parsing it using
+        * {@link SoneTextParser}).
+        *
+        * @param text
+        *            The text to parse
+        * @return All mentioned local Sones
+        */
        private Set<Sone> getMentionedSones(String text) {
                /* we need no context to find mentioned Sones. */
                Set<Sone> mentionedSones = new HashSet<Sone>();
diff --git a/src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java b/src/test/java/net/pterodactylus/sone/text/SoneTextParserTest.java
new file mode 100644 (file)
index 0000000..0343f05
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Sone - SoneTextParserTest.java - Copyright © 2011 David Roden
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.sone.text;
+
+import java.io.IOException;
+import java.io.StringReader;
+
+import junit.framework.TestCase;
+
+/**
+ * JUnit test case for {@link SoneTextParser}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class SoneTextParserTest extends TestCase {
+
+       //
+       // ACTIONS
+       //
+
+       /**
+        * Tests basic plain-text operation of the parser.
+        *
+        * @throws IOException
+        *             if an I/O error occurs
+        */
+       public void testPlainText() throws IOException {
+               SoneTextParser soneTextParser = new SoneTextParser(null, null);
+               Iterable<Part> parts;
+
+               /* check basic operation. */
+               parts = soneTextParser.parse(null, new StringReader("Test."));
+               assertNotNull("Parts", parts);
+               assertEquals("Part Text", "Test.", convertText(parts, PlainTextPart.class));
+
+               /* check empty lines at start and end. */
+               parts = soneTextParser.parse(null, new StringReader("\nTest.\n\n"));
+               assertNotNull("Parts", parts);
+               assertEquals("Part Text", "Test.", convertText(parts, PlainTextPart.class));
+
+               /* check duplicate empty lines in the text. */
+               parts = soneTextParser.parse(null, new StringReader("\nTest.\n\n\nTest."));
+               assertNotNull("Parts", parts);
+               assertEquals("Part Text", "Test.\n\nTest.", convertText(parts, PlainTextPart.class));
+       }
+
+       /**
+        * Tests parsing of KSK links.
+        *
+        * @throws IOException
+        *             if an I/O error occurs
+        */
+       public void testKSKLinks() throws IOException {
+               SoneTextParser soneTextParser = new SoneTextParser(null, null);
+               Iterable<Part> parts;
+
+               /* check basic links. */
+               parts = soneTextParser.parse(null, new StringReader("KSK@gpl.txt"));
+               assertNotNull("Parts", parts);
+               assertEquals("Part Text", "[KSK@gpl.txt|gpl.txt|gpl.txt]", convertText(parts, FreenetLinkPart.class));
+
+               /* check embedded links. */
+               parts = soneTextParser.parse(null, new StringReader("Link is KSK@gpl.txt\u200b."));
+               assertNotNull("Parts", parts);
+               assertEquals("Part Text", "Link is [KSK@gpl.txt|gpl.txt|gpl.txt]\u200b.", convertText(parts, PlainTextPart.class, FreenetLinkPart.class));
+
+               /* check embedded links and line breaks. */
+               parts = soneTextParser.parse(null, new StringReader("Link is KSK@gpl.txt\nKSK@test.dat\n"));
+               assertNotNull("Parts", parts);
+               assertEquals("Part Text", "Link is [KSK@gpl.txt|gpl.txt|gpl.txt]\n[KSK@test.dat|test.dat|test.dat]", convertText(parts, PlainTextPart.class, FreenetLinkPart.class));
+       }
+
+       //
+       // PRIVATE METHODS
+       //
+
+       /**
+        * Converts all given {@link Part}s into a string, validating that the
+        * part’s classes match only the expected classes.
+        *
+        * @param parts
+        *            The parts to convert to text
+        * @param validClasses
+        *            The valid classes; if no classes are given, all classes are
+        *            valid
+        * @return The converted text
+        */
+       private String convertText(Iterable<Part> parts, Class<?>... validClasses) {
+               StringBuilder text = new StringBuilder();
+               for (Part part : parts) {
+                       assertNotNull("Part", part);
+                       boolean classValid = validClasses.length == 0;
+                       for (Class<?> validClass : validClasses) {
+                               if (validClass.isAssignableFrom(part.getClass())) {
+                                       classValid = true;
+                                       break;
+                               }
+                       }
+                       if (!classValid) {
+                               assertEquals("Part’s Class", null, part.getClass());
+                       }
+                       if (part instanceof PlainTextPart) {
+                               text.append(((PlainTextPart) part).getText());
+                       } else if (part instanceof FreenetLinkPart) {
+                               FreenetLinkPart freenetLinkPart = (FreenetLinkPart) part;
+                               text.append('[').append(freenetLinkPart.getLink()).append('|').append(freenetLinkPart.isTrusted() ? "trusted|" : "").append(freenetLinkPart.getTitle()).append('|').append(freenetLinkPart.getText()).append(']');
+                       } else if (part instanceof LinkPart) {
+                               LinkPart linkPart = (LinkPart) part;
+                               text.append('[').append(linkPart.getLink()).append('|').append(linkPart.getTitle()).append('|').append(linkPart.getText()).append(']');
+                       }
+               }
+               return text.toString();
+       }
+
+}