Commits

Stephen McKamey committed 8cedfc2

- adding code gen for global var data
- adding tests
- fixing generated whitespace

Comments (0)

Files changed (5)

src/org/duelengine/duel/DataEncoder.java

 
 import java.io.*;
 import java.util.*;
+import java.util.Map.Entry;
 
 /**
  * Utility for writing data as JavaScript literals
 	public void write(Appendable output, Object data)
 		throws IOException {
 
+		if (data instanceof SparseMap) {
+			return;
+		}
 		this.write(output, data, 0);
 	}
 
 		boolean hasChildren = singleAttr;
 		boolean needsDelim = false;
 		for (Object item : data) {
+			if (data instanceof SparseMap) {
+				continue;
+			}
+
 			if (singleAttr) {
 				if (this.prettyPrint) {
 					output.append(' ');
 		boolean hasChildren = singleAttr;
 		boolean needsDelim = false;
 		for (Map.Entry<?, ?> property : (Set<Map.Entry<?, ?>>)properties) {
+			Object value = property.getValue();
+			if (value instanceof SparseMap) {
+				continue;
+			}
+
 			if (singleAttr) {
 				if (this.prettyPrint) {
 					output.append(' ');
 			} else {
 				output.append(':');
 			}
-			this.write(output, property.getValue(), depth);
+			this.write(output, value, depth);
 		}
 
 		depth--;
 	 * @param output
 	 * @param namespaces
 	 * @param ident
+	 * @return true if namespaces were emitted
 	 * @throws IOException
 	 */
-	public void writeNamespace(Appendable output, List<String> namespaces, String ident)
+	public boolean writeNamespace(Appendable output, List<String> namespaces, String ident)
 		throws IOException {
 
 		if (!JSUtility.isValidIdentifier(ident, true)) {
 			throw new IllegalArgumentException("Invalid identifier: "+ident);
 		}
 
-		boolean needsNewline = false;
+		boolean wroteNS = false;
 		boolean isRoot = true;
 		int nextDot = ident.indexOf('.');
 		while (nextDot > -1) {
 			}
 			namespaces.add(ns);
 
-			this.writeln(output, 0);
 			if (isRoot) {
 				output.append("var ");
 				isRoot = false;
 			}
 
 			output.append(ns);
-			output.append(" = ");
+			if (this.prettyPrint) {
+				output.append(' ');
+			}
+			output.append('=');
+			if (this.prettyPrint) {
+				output.append(' ');
+			}
 			output.append(ns);
-			output.append(" || {};");
+			if (this.prettyPrint) {
+				output.append(' ');
+			}
+			output.append("||");
+			if (this.prettyPrint) {
+				output.append(' ');
+			}
+			output.append("{};");
 
 			// next iteration
 			nextDot = ident.indexOf('.', nextDot+1);
-			needsNewline = true;
+			this.writeln(output, 0);
+			wroteNS = true;
 		}
 
-		if (needsNewline) {
-			this.writeln(output, 0);
-		}
+		return wroteNS;
 	}
 
 	/**
 
 		output.append(this.newline);
 
-		for (int i=depth; i>0; i--) {
+		while (depth-- > 0) {
 			output.append(this.indent);
 		}
 	}
 			return true;
 		}
 	}
+
+	/**
+	 * Serializes the items as JavaScript variable
+	 * @param output 
+	 * @param items Variables to serialize
+	 * @throws IOException
+	 */
+	public void writeVars(Appendable output, SparseMap items)
+		throws IOException {
+
+		// begin by flattening the heirarchy whereever a SparseMap is encountered
+		Map<String, Object> vars = new LinkedHashMap<String, Object>();
+		this.accumulateVars(items, vars, new StringBuilder());
+
+		// emit as a code block of var declarations
+		List<String> namespaces = new ArrayList<String>();
+		for (Entry<String, Object> globalVar : vars.entrySet()) {
+			String key = globalVar.getKey();
+			this.writeNamespace(output, namespaces, key);
+
+			if (key.indexOf('.') < 0) {
+				output.append("var ");
+			}
+			output.append(key);
+			if (this.prettyPrint) {
+				output.append(' ');
+			}
+			output.append('=');
+			if (this.prettyPrint) {
+				output.append(' ');
+			}
+			this.write(output, globalVar.getValue());
+			output.append(';');
+			this.writeln(output, 0);
+		}
+	}
+
+	private void accumulateVars(Object data, Map<String, Object> vars, StringBuilder buffer)
+		throws IOException {
+
+		if (data == null) {
+			return;
+		}
+
+		final String ROOT = "window";
+		int length = buffer.length();
+		boolean emptyBuffer = (length < 1);
+		Class<?> dataType = data.getClass();
+
+		if (Map.class.isAssignableFrom(dataType)) {
+			boolean isSparseMap = SparseMap.class.equals(dataType);
+			for (Entry<?,?> child : ((Map<?,?>)data).entrySet()) {
+				String key = DuelData.coerceString(child.getKey());
+				if (JSUtility.isValidIdentifier(key, false)) {
+					if (!emptyBuffer) {
+						buffer.append('.');
+					}
+					buffer.append(key);
+				} else {
+					if (emptyBuffer) {
+						buffer.append(ROOT);
+					}
+					buffer.append('[');
+					this.writeString(buffer, key);
+					buffer.append(']');
+				}
+				Object value = child.getValue();
+				if (isSparseMap && !(value instanceof SparseMap)) {
+					vars.put(buffer.toString(), value);
+				}
+				this.accumulateVars(value, vars, buffer);
+				buffer.setLength(length);
+			}
+
+		} else if (DuelData.isArray(dataType)) {
+			int i = 0;
+			for (Object child : DuelData.coerceCollection(data)) {
+				if (emptyBuffer) {
+					buffer.append(ROOT);
+				}
+				buffer.append('[');
+				this.writeNumber(buffer, i++);
+				buffer.append(']');
+				this.accumulateVars(child, vars, buffer);
+				buffer.setLength(length);
+			}
+		}
+	}
 }

src/org/duelengine/duel/SparseMap.java

 /**
  * Represents sparsely populated graph of data to be
  * serialized over the top of existing JavaScript Objects
+ * Does not get serialized directly.
  */
 @SuppressWarnings("serial")
 public class SparseMap extends LinkedHashMap<String, Object> {

src/org/duelengine/duel/codegen/ClientCodeGen.java

 			if (view == null) {
 				continue;
 			}
+			this.writeln(output, 0);
+
 			// prepend the client-side prefix
 			String viewName = this.settings.getFullName(view.getName());
 			try {
-				this.encoder.writeNamespace(output, namespaces, viewName);
+				if (this.encoder.writeNamespace(output, namespaces, viewName)) {
+					this.writeln(output, 0);
+				}
 			} catch (IllegalArgumentException ex) {
 				throw new InvalidNodeException("Invalid view name: "+viewName, view.getAttribute("name"), ex);
 			}
 	private void writeView(Appendable output, VIEWCommandNode view, String viewName)
 		throws IOException {
 
-		this.writeln(output, 0);
-
 		// prepend the client-side prefix
 		if (viewName.indexOf('.') < 0) {
 			output.append("var ");
 
 		output.append(this.settings.getNewline());
 
-		for (int i=depth; i>0; i--) {
-			output.append(this.settings.getIndent());
+		String indent = this.settings.getIndent();
+		while (depth-- > 0) {
+			output.append(indent);
 		}
 	}
 }

test/org/duelengine/duel/SparseMapTests.java

 public class SparseMapTests {
 
 	@Test
+	public void putPrimitiveSingleTest() throws IOException {
+		SparseMap input = SparseMap.asSparseMap(
+			"isDebug", false);
+
+		String expected =
+			"var isDebug = false;\n";
+
+		StringBuilder output = new StringBuilder();
+		new DataEncoder("\n", "\t").writeVars(output, input);
+		String actual = output.toString();
+
+		assertEquals(expected, actual);
+	}
+
+	@Test
 	public void putPrimitivesTest() throws IOException {
-		Object input = SparseMap.asSparseMap(
+		SparseMap input = SparseMap.asSparseMap(
+			"simple", "Hello",
+			"nested", DuelData.asMap(
+				"foo", 42,
+				"bar", true));
+
+		String expected =
+			"var simple = \"Hello\";\n"+
+			"var nested = {\n"+
+			"\tfoo : 42,\n"+
+			"\tbar : true\n"+
+			"};\n";
+
+		StringBuilder output = new StringBuilder();
+		new DataEncoder("\n", "\t").writeVars(output, input);
+		String actual = output.toString();
+
+		assertEquals(expected, actual);
+	}
+
+	@Test
+	public void putPrimitivesSparseTest() throws IOException {
+		SparseMap input = SparseMap.asSparseMap(
 			"simple", "Hello",
 			"nested.foo", 42,
 			"nested.bar", true);
 
-		String expected = "{simple:\"Hello\",nested:{foo:42,bar:true}}";
+		String expected =
+			"var simple = \"Hello\";\n"+
+			"var nested = nested || {};\n"+
+			"nested.foo = 42;\n"+
+			"nested.bar = true;\n";
 
 		StringBuilder output = new StringBuilder();
-		new DataEncoder().write(output, input);
+		new DataEncoder("\n", "\t").writeVars(output, input);
 		String actual = output.toString();
 
 		assertEquals(expected, actual);
 		// extend
 		input.putSparse("nested.baz", DuelData.asList(1, 2, 3));
 
-		String expected = "{simple:\"Hello\",nested:{foo:42,bar:true,baz:[1,2,3]}}";
+		String expected =
+			"var simple = \"Hello\";\n"+
+			"var nested = nested || {};\n"+
+			"nested.foo = 42;\n"+
+			"nested.bar = true;\n"+
+			"nested.baz = [\n"+
+			"\t1,\n"+
+			"\t2,\n"+
+			"\t3\n"+
+			"];\n";
 
 		StringBuilder output = new StringBuilder();
-		new DataEncoder().write(output, input);
+		new DataEncoder("\n", "\t").writeVars(output, input);
 		String actual = output.toString();
 
 		assertEquals(expected, actual);
 		// overwrite
 		input.putSparse("nested", DuelData.asList(1, 2, 3));
 
-		String expected = "{simple:\"Hello\",nested:[1,2,3]}";
+		String expected =
+			"var simple = \"Hello\";\n"+
+			"var nested = [\n"+
+			"\t1,\n"+
+			"\t2,\n"+
+			"\t3\n"+
+			"];\n";
 
 		StringBuilder output = new StringBuilder();
-		new DataEncoder().write(output, input);
+		new DataEncoder("\n", "\t").writeVars(output, input);
 		String actual = output.toString();
 
 		assertEquals(expected, actual);
 			"nested.foo", null,
 			"nested.bar", new Object());
 
-		String expected = "{simple:\"Hello\",nested:{foo:null,bar:{}}}";
+		String expected =
+			"var simple=\"Hello\";"+
+			"var nested=nested||{};"+
+			"nested.foo=null;"+
+			"nested.bar={};";
 
 		StringBuilder output = new StringBuilder();
-		new DataEncoder().write(output, input);
+		new DataEncoder().writeVars(output, input);
+		String actual = output.toString();
+
+		assertEquals(expected, actual);
+	}
+
+	@Test
+	public void putObjectPrettyPrintTest() throws IOException {
+		SparseMap input = SparseMap.asSparseMap(
+			"simple", "Hello",
+			"nested.foo", null,
+			"nested.bar", new Object());
+
+		String expected =
+			"var simple = \"Hello\";\n"+
+			"var nested = nested || {};\n"+
+			"nested.foo = null;\n"+
+			"nested.bar = {};\n";
+
+		StringBuilder output = new StringBuilder();
+		new DataEncoder("\n", "\t").writeVars(output, input);
 		String actual = output.toString();
 
 		assertEquals(expected, actual);
 		input.putSparse("nested.foo", DuelData.asList(1, 2, 3));
 		input.putSparse("nested.bar", true);
 
-		String expected = "{simple:\"Hello\",nested:{foo:[1,2,3],bar:true}}";
+		String expected =
+			"var simple = \"Hello\";\n"+
+			"var nested = {\n"+
+			"\tfoo : [\n"+
+			"\t\t1,\n"+
+			"\t\t2,\n"+
+			"\t\t3\n"+
+			"\t],\n"+
+			"\tbar : true\n"+
+			"};\n";
 
 		StringBuilder output = new StringBuilder();
-		new DataEncoder().write(output, input);
+		new DataEncoder("\n", "\t").writeVars(output, input);
 		String actual = output.toString();
 
 		assertEquals(expected, actual);
 		// add expando properties
 		input.putSparse("nested.many.$levels.deep", DuelData.asList(1, 2, 3));
 
-		String expected = "{nested:{many:{$levels:{$:true,\"\":false,deep:[1,2,3]}}}}";
+		String expected =
+			"var nested=nested||{};"+
+			"nested.many=nested.many||{};"+
+			"nested.many.$levels={"+
+			"$:true,"+
+			"\"\":false,"+
+			"deep:["+
+			"1,"+
+			"2,"+
+			"3"+
+			"]"+
+			"};";
 
 		StringBuilder output = new StringBuilder();
-		new DataEncoder().write(output, input);
+		new DataEncoder().writeVars(output, input);
 		String actual = output.toString();
-System.out.println(expected);
-System.err.println(actual);
+
+		assertEquals(expected, actual);
+	}
+
+	@Test
+	public void putMapDeepNestPrettyPrintTest() throws IOException {
+		SparseMap input = SparseMap.asSparseMap(
+			"nested.many.$levels", DuelData.asMap("$", true, "", false));
+
+		// add expando properties
+		input.putSparse("nested.many.$levels.deep", DuelData.asList(1, 2, 3));
+
+		String expected =
+			"var nested = nested || {};\n"+
+			"nested.many = nested.many || {};\n"+
+			"nested.many.$levels = {\n"+
+			"\t$ : true,\n"+
+			"\t\"\" : false,\n"+
+			"\tdeep : [\n"+
+			"\t\t1,\n"+
+			"\t\t2,\n"+
+			"\t\t3\n"+
+			"\t]\n"+
+			"};\n";
+
+		StringBuilder output = new StringBuilder();
+		new DataEncoder("\n", "\t").writeVars(output, input);
+		String actual = output.toString();
+
 		assertEquals(expected, actual);
 	}
 

test/org/duelengine/duel/codegen/ClientCodeGenTests.java

 package org.duelengine.duel.codegen;
 
 import java.io.IOException;
-
 import org.junit.Test;
 import static org.junit.Assert.*;
 import org.duelengine.duel.ast.*;
 		StringBuilder output = new StringBuilder();
 		new ClientCodeGen().write(output, input);
 		String actual = output.toString();
-		
+
 		assertEquals(expected, actual);
 	}