You are viewing a plain text version of this content. The canonical link for it is here.
Posted to easyant-commits@incubator.apache.org by hi...@apache.org on 2011/02/17 17:01:56 UTC

svn commit: r1071697 [42/42] - in /incubator/easyant: buildtypes/ buildtypes/trunk/ buildtypes/trunk/build-osgi-bundle-java/ buildtypes/trunk/build-osgi-bundle-java/src/ buildtypes/trunk/build-osgi-bundle-java/src/main/ buildtypes/trunk/build-osgi-bund...

Added: incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/main/java/org/apache/easyant/menu/XookiMenuGenerator.java
URL: http://svn.apache.org/viewvc/incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/main/java/org/apache/easyant/menu/XookiMenuGenerator.java?rev=1071697&view=auto
==============================================================================
--- incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/main/java/org/apache/easyant/menu/XookiMenuGenerator.java (added)
+++ incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/main/java/org/apache/easyant/menu/XookiMenuGenerator.java Thu Feb 17 17:01:07 2011
@@ -0,0 +1,227 @@
+/* 
+ *  Copyright 2008-2010 the EasyAnt project
+ * 
+ *  See the NOTICE file distributed with this work for additional information
+ *  regarding copyright ownership. 
+ * 
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *  Unless required by applicable law or agreed to in writing, software 
+ *  distributed under the License is distributed on an "AS IS" BASIS, 
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and 
+ *  limitations under the License.
+ */
+package org.apache.easyant.menu;
+
+import org.apache.easyant.core.menu.MenuGenerator;
+import org.apache.tools.ant.util.FileUtils;
+
+import java.io.*;
+import java.util.NoSuchElementException;
+
+/**
+ * Generates a <a href="http://xooki.sourceforge.net/">xooki</a> JSON menu file.
+ */
+public class XookiMenuGenerator implements MenuGenerator<XookiMenuGenerator> {
+
+    private boolean closed;
+
+    private String id;
+    private File path;
+    private Writer fileWriter;
+
+    private Block currentBlock;
+
+    public void startMenu(String title, String location) throws IOException {
+        if (fileWriter != null) {
+            throw new IllegalStateException("Menu has already been opened for writing at " + path.getAbsolutePath());
+        }
+        closed = false;
+        
+        path = new File(location);
+        fileWriter = new BufferedWriter(new FileWriter(path));
+        currentBlock = new Block();
+
+        //start the toplevel menu object.
+        currentBlock = currentBlock.startObject();
+
+        if (title == null) {
+            id = toId(location);
+            currentBlock.appendAttribute("id", id);
+        } else {
+            id = toId(title);
+            currentBlock
+                 .appendAttribute("id", id)
+                 .appendAttribute("title", title);
+        }
+
+        //add all child entries to an array called "children"
+        currentBlock = currentBlock.startArray("children");
+    }
+
+    public void addSubMenu(String title, XookiMenuGenerator subMenu) throws IOException {
+        assertOpen();
+        subMenu.assertOpen();
+
+        currentBlock
+            .startObject()
+                .appendAttribute("title", title)
+                .appendAttribute("importNode", subMenu.id)
+                .appendAttribute("importRoot", computeSubMenuPath(subMenu))
+            .end();
+    }
+
+    public void addEntry(String title, String targetLink) throws IOException {
+        assertOpen();
+
+        currentBlock
+            .startObject()
+                .appendAttribute("id", targetLink)
+                .appendAttribute("title", title)
+            .end();
+    }
+
+    public void endMenu() throws IOException {
+        assertOpen();
+        try {
+            currentBlock
+                 .end()  //end "children" array
+                 .end(); //end toplevel {} block
+        } finally {
+            try {
+                fileWriter.close();
+            } finally {
+                fileWriter = null;
+                closed = true;
+                currentBlock = null;
+            }
+        }
+    }
+
+    /**
+     * Convert the filename for the given submenu into a path relative to this menu.
+     * @throws IOException if the conversion fails for any reason
+     */
+    private String computeSubMenuPath(XookiMenuGenerator subMenu) throws IOException {
+
+        File basePath = this.path.getParentFile();
+        File subPath = subMenu.path.getParentFile();
+
+        String path;
+        try {
+            path = FileUtils.getRelativePath(basePath, subPath);
+        } catch (Exception e) {
+            //getRelativePath throws java.lang.Exception, for no clear reason, but we have to handle it.
+            IOException ioe = new IOException("Error computing relative path for submenu " + subMenu.id);
+            ioe.initCause(e);
+            throw ioe;
+        }
+
+        if (path == null)
+            throw new FileNotFoundException("Unable to compute relative path for submenu " + subMenu.id);
+
+        return path;
+    }
+
+    private void assertOpen() throws IOException {
+        if (closed) {
+            throw new IllegalStateException("The menu at " + path.getAbsolutePath() + " has already been closed");
+        }
+        if (fileWriter == null) {
+            throw new IllegalStateException("This menu has never been opened");
+        }
+    }
+
+    private String toId(String title) {
+        return title.replaceAll("\\W+", "_");
+    }
+
+    /**
+     * Represents a single block-level element in JSON, e.g. an Array or an Object.  A simple API is provided
+     * to add nested blocks and attributes.
+     */
+    private class Block {
+
+        private static final String FIRST_ENTRY_SEP = "\n";
+        private static final String NEXT_ENTRY_SEP = ",\n";
+
+        private Block parent; //points up the context stack
+        private char close;   //character to write when this block is ended
+        private String indent; //current indent level
+        private String entrySeparator = FIRST_ENTRY_SEP; //separator string between entries of this block
+
+        /** constructor for the root block */
+        public Block() {
+            this.parent = null;
+            this.indent = "";
+            this.entrySeparator = "";
+        }
+
+        /** constructor for a nested block */
+        private Block(Block parent, char close) {
+            this.parent = parent;
+            this.indent = parent.indent + "\t";
+            this.close = close;
+        }
+
+        /** add a JavaScript attribute to this block */
+        public Block appendAttribute(String name, String value) throws IOException {
+            nextEntry().append('\"').append(name).append("\":");
+            return appendLiteral(value);
+        }
+
+        /** begin a nested array block with the given name  */
+        public Block startArray(String name) throws IOException {
+            nextEntry().append('\"').append(name).append("\": ");
+            fileWriter.append('[');
+            return new Block(this, ']');
+        }
+
+        /** begin a nested object block  */
+        public Block startObject() throws IOException {
+            nextEntry().append('{');
+            return new Block(this, '}');
+        }
+
+        /** close the current block, returning a reference to the parent. */
+        public Block end() throws IOException {
+            if (parent == null) {
+                throw new NoSuchElementException("Cannot pop the root element");
+            }
+            fileWriter.append('\n');
+            return parent.endBlock(close); //return reference to parent, popping the stack
+        }
+
+        /** end a child block using the given terminator */
+        private Block endBlock(char close) throws IOException {
+            fileWriter.append(indent).append(close);
+            return this;
+        }
+
+        /**
+         * start a new entry in the current block, including a separator from
+         * any previous entries and indenting whitespace
+         */
+        private Writer nextEntry() throws IOException {
+            fileWriter.append(entrySeparator).append(indent);
+            entrySeparator = NEXT_ENTRY_SEP;
+            return fileWriter;
+        }
+
+        /** append a quoted, escaped string literal to this block */
+        private Block appendLiteral(String value) throws IOException {
+            //escape any ' or " so that they don't screw up our syntax
+            value = value.replaceAll("(['\"])", "\\\\$1");
+            //enclose the value in quotes to include any whitespace
+            fileWriter.append("\"").append(value).append("\"");
+            return this;
+        }
+
+    }
+
+}

Propchange: incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/main/java/org/apache/easyant/menu/XookiMenuGenerator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/main/java/org/apache/easyant/menu/XookiMenuGenerator.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision Author HeadURL Id

Propchange: incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/main/java/org/apache/easyant/menu/XookiMenuGenerator.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/java/org/apache/easyant/menu/XookiMenuGeneratorTest.java
URL: http://svn.apache.org/viewvc/incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/java/org/apache/easyant/menu/XookiMenuGeneratorTest.java?rev=1071697&view=auto
==============================================================================
--- incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/java/org/apache/easyant/menu/XookiMenuGeneratorTest.java (added)
+++ incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/java/org/apache/easyant/menu/XookiMenuGeneratorTest.java Thu Feb 17 17:01:07 2011
@@ -0,0 +1,177 @@
+/* 
+ *  Copyright 2008-2010 the EasyAnt project
+ * 
+ *  See the NOTICE file distributed with this work for additional information
+ *  regarding copyright ownership. 
+ * 
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ * 
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ *  Unless required by applicable law or agreed to in writing, software 
+ *  distributed under the License is distributed on an "AS IS" BASIS, 
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and 
+ *  limitations under the License.
+ */
+package org.apache.easyant.menu;
+
+import static org.junit.Assert.*;
+
+import org.apache.tools.ant.util.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+
+public class XookiMenuGeneratorTest {
+
+    private File menuFile;
+    private XookiMenuGenerator generator;
+
+    @Before
+    public void setUp() throws IOException {
+        menuFile = File.createTempFile("XookiMenuGeneratorTest", ".json");
+        menuFile.deleteOnExit();
+        generator = new XookiMenuGenerator();
+    }
+
+    @After
+    public void tearDown() throws IOException {
+        generator = null;
+        if (menuFile.exists()) {
+            assertTrue(menuFile.delete());
+        }
+        menuFile = null;
+    }
+
+    /** test behavior of calling generator methods out-of-order */
+    @Test
+    public void testMenuLifecycle() throws IOException {
+        //should not be able to append an unopened menu.
+        try {
+            generator.addEntry("should", "fail");
+            fail("should not be able to add an entry to a menu that has not been started");
+        } catch (IllegalStateException expected) {}
+
+        //should not be able to append an unopened menu.
+        XookiMenuGenerator subMenu = new XookiMenuGenerator();
+        try {
+            generator.addSubMenu("unopened", subMenu);
+            fail("should not be able to add a submenu to a menu that has not been started");
+        } catch (IllegalStateException expected) {}
+
+        //should not be able to end an unopened menu
+        try {
+            generator.endMenu();
+            fail("should not be able to end a menu that has not been started");
+        } catch (IllegalStateException expected) {}
+
+        //start the menu.
+        generator.startMenu("Test", menuFile.getAbsolutePath());
+
+        //should not be able to add an unopened submenu
+        try {
+            generator.addSubMenu("unopened", subMenu);
+            fail("should not be able to add a submenu that is not yet opened");
+        } catch (IllegalStateException expected) {}
+
+        //should not be able to start a menu twice
+        File dupFile = new File(menuFile.getParentFile(), menuFile.getPath() + ".dup");
+        try {
+            generator.startMenu("Test", dupFile.getAbsolutePath());
+            fail("should not be able to start a menu twice");
+        } catch (IllegalStateException expected) {}
+        assertFalse("duplicate menu file should not have been created", dupFile.exists());
+
+        //add an entry, close the menu
+        generator.addEntry("lonely", "link");
+        generator.endMenu();
+
+        try {
+            generator.endMenu();
+            fail("should not be able to end a menu twice");
+        } catch (IllegalStateException expected) {}
+
+        //verify that the menu has the correct content.
+        assertEquals("menu contains only data delivered in correct order",
+                slurpMenu("testMenuLifecycle.json"), slurpMenu());
+    }
+
+    @Test
+    public void testEmptyMenu() throws IOException {
+        generator.startMenu("empty", menuFile.getAbsolutePath());
+        generator.endMenu();
+        assertEquals("empty menu is well-formed",
+                slurpMenu("testEmptyMenu.json"), slurpMenu());
+    }
+
+    @Test
+    public void testMultipleEntries() throws IOException {
+        generator.startMenu("three", menuFile.getAbsolutePath());
+        //also test escaping of title characters.
+        generator.addEntry("one", "path/to/one");
+        generator.addEntry("'two'", "path/to/two");
+        generator.addEntry("item \"three\"", "path/to/three");
+
+        generator.endMenu();
+        assertEquals("complex menu is well-formed",
+                slurpMenu("testMultipleEntries.json"), slurpMenu());
+    }
+
+    @Test
+    public void testSubMenu() throws IOException {
+        generator.startMenu("parent", menuFile.getAbsolutePath());
+        generator.addEntry("one", "path/to/one");
+
+        File subDir = new File(System.getProperty("java.io.tmpdir"), "XookiMenuGeneratorTestSub");
+        subDir.mkdir();
+        File subfile = File.createTempFile("XookiMenuGeneratorTest-sub", ".json", subDir);
+
+        try {
+            XookiMenuGenerator subMenu = new XookiMenuGenerator();
+            subMenu.startMenu("child", subfile.getAbsolutePath());
+
+            generator.addSubMenu("Child Menu", subMenu);
+            generator.addEntry("item \"three\"", "path/to/three");
+
+            generator.endMenu();
+            assertEquals("menu with submenu reference is well-formed",
+                    slurpMenu("testSubMenu.json"), slurpMenu());
+
+            subMenu.endMenu();
+        } finally {
+            subfile.delete();
+            subDir.delete();
+        }
+    }
+
+    /** read the content of the current test menu */
+    private String slurpMenu() throws IOException {
+        FileReader reader = new FileReader(menuFile);
+        try {
+            return FileUtils.readFully(reader);
+        } finally {
+            reader.close();
+        }
+    }
+
+    /** read the given menu resource to verify test results */
+    private String slurpMenu(String resource) throws IOException {
+        URL url = getClass().getResource(resource);
+        assertNotNull("found classpath resource " + resource, url);
+        InputStreamReader reader = new InputStreamReader(url.openStream());
+        try {
+            return FileUtils.readFully(reader);
+        } finally {
+            reader.close();
+        }
+    }
+}

Propchange: incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/java/org/apache/easyant/menu/XookiMenuGeneratorTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/java/org/apache/easyant/menu/XookiMenuGeneratorTest.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision Author HeadURL Id

Propchange: incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/java/org/apache/easyant/menu/XookiMenuGeneratorTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testEmptyMenu.json
URL: http://svn.apache.org/viewvc/incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testEmptyMenu.json?rev=1071697&view=auto
==============================================================================
--- incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testEmptyMenu.json (added)
+++ incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testEmptyMenu.json Thu Feb 17 17:01:07 2011
@@ -0,0 +1,6 @@
+{
+	"id":"empty",
+	"title":"empty",
+	"children": [
+	]
+}
\ No newline at end of file

Added: incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testMenuLifecycle.json
URL: http://svn.apache.org/viewvc/incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testMenuLifecycle.json?rev=1071697&view=auto
==============================================================================
--- incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testMenuLifecycle.json (added)
+++ incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testMenuLifecycle.json Thu Feb 17 17:01:07 2011
@@ -0,0 +1,10 @@
+{
+	"id":"Test",
+	"title":"Test",
+	"children": [
+		{
+			"id":"link",
+			"title":"lonely"
+		}
+	]
+}
\ No newline at end of file

Added: incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testMultipleEntries.json
URL: http://svn.apache.org/viewvc/incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testMultipleEntries.json?rev=1071697&view=auto
==============================================================================
--- incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testMultipleEntries.json (added)
+++ incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testMultipleEntries.json Thu Feb 17 17:01:07 2011
@@ -0,0 +1,18 @@
+{
+	"id":"three",
+	"title":"three",
+	"children": [
+		{
+			"id":"path/to/one",
+			"title":"one"
+		},
+		{
+			"id":"path/to/two",
+			"title":"\'two\'"
+		},
+		{
+			"id":"path/to/three",
+			"title":"item \"three\""
+		}
+	]
+}
\ No newline at end of file

Added: incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testSubMenu.json
URL: http://svn.apache.org/viewvc/incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testSubMenu.json?rev=1071697&view=auto
==============================================================================
--- incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testSubMenu.json (added)
+++ incubator/easyant/tasks/trunk/xooki-menu-generator/trunk/src/test/resources/org/apache/easyant/menu/testSubMenu.json Thu Feb 17 17:01:07 2011
@@ -0,0 +1,19 @@
+{
+	"id":"parent",
+	"title":"parent",
+	"children": [
+		{
+			"id":"path/to/one",
+			"title":"one"
+		},
+		{
+			"title":"Child Menu",
+			"importNode":"child",
+			"importRoot":"XookiMenuGeneratorTestSub"
+		},
+		{
+			"id":"path/to/three",
+			"title":"item \"three\""
+		}
+	]
+}
\ No newline at end of file