Commits

Stephen McKamey committed b891b53

- splitting runtime classes into own project

Comments (0)

Files changed (45)

+relre:duel/duel-runtime/target/*
 relre:duel/duel-compiler/target/*
 relre:duel/duel-js/target/*
 relre:duel/target/*

duel/duel-compiler/pom.xml

 
 	<dependencies>
 		<dependency>
+			<groupId>org.duelengine.duel</groupId>
+			<artifactId>duel-runtime</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
 			<groupId>rhino</groupId>
 			<artifactId>js</artifactId>
 			<version>1.7R3</version>
 			<systemPath>${basedir}/lib/rhino/js-1.7R3pre.jar</systemPath> -->
 		</dependency>
 		<dependency>
-			<groupId>javax.ws.rs</groupId>
-			<artifactId>jsr311-api</artifactId>
-			<version>1.0</version>
-			<scope>provided</scope>
-		</dependency>
-		<dependency>
 			<groupId>junit</groupId>
 			<artifactId>junit</artifactId>
 			<version>4.8.2</version>

duel/duel-compiler/src/main/java/org/duelengine/duel/ArrayIterable.java

-package org.duelengine.duel;
-
-import java.lang.reflect.Array;
-import java.util.*;
-
-/**
- * Adapts Array to partially implemented List without performing a copy.
- * Implements only iterator(), size() and get()
- */
-class ArrayIterable extends AbstractList<Object> {
-
-	private class ArrayIterator implements Iterator<Object> {
-
-		private final Object array;
-		private final int last;
-		private int index = -1;
-
-		public ArrayIterator(Object array, int length) {
-			this.array = array;
-			this.last = length-1;
-		}
-
-		@Override
-		public boolean hasNext() {
-			return (this.index < this.last);
-		}
-
-		@Override
-		public Object next() {
-			if (this.index > this.last) {
-				// JavaScript style out of bounds
-				return null;
-			}
-
-			return Array.get(this.array, ++this.index);
-		}
-
-		@Override
-		public void remove() {
-			throw new UnsupportedOperationException();
-		}
-	}
-	
-	private final Object array;
-	private final int length;
-	
-	public ArrayIterable(Object array) {
-		this.array = array;
-		this.length = Array.getLength(this.array);
-	}
-
-	@Override
-	public Iterator<Object> iterator() {
-		if (array == null) {
-			throw new NullPointerException("array");
-		}
-
-		return new ArrayIterator(this.array, this.length);
-	}
-
-	@Override
-	public int size() {
-		return this.length;
-	}
-
-	@Override
-	public boolean isEmpty() {
-		return this.length > 0;
-	}
-
-	@Override
-	public Object[] toArray() {
-		// this will only work for Object arrays
-		return (Object[])this.array;
-	}
-
-	@Override
-	public Object get(int index) {
-		if (index < 0 || index >= this.length) {
-			// JavaScript style out of bounds
-			return null;
-		}
-
-		return Array.get(this.array, index);
-	}
-}

duel/duel-compiler/src/main/java/org/duelengine/duel/ClientIDStrategy.java

-package org.duelengine.duel;
-
-public interface ClientIDStrategy {
-
-	public String nextID();
-}

duel/duel-compiler/src/main/java/org/duelengine/duel/ClockClientIDStrategy.java

-package org.duelengine.duel;
-
-import java.util.*;
-
-public class ClockClientIDStrategy implements ClientIDStrategy {
-
-	private final String prefix;
-
-	public ClockClientIDStrategy() {
-		this("_");
-	}
-
-	public ClockClientIDStrategy(String prefix) {
-		this.prefix = prefix;
-	}
-
-	@Override
-	public String nextID() {
-		return this.prefix + new Date().getTime();
-	}
-}

duel/duel-compiler/src/main/java/org/duelengine/duel/DataEncoder.java

-package org.duelengine.duel;
-
-import java.io.*;
-import java.util.*;
-
-/**
- * Utility for writing data as JavaScript literals
- * Inherently thread-safe as contains no mutable instance data.
- */
-public class DataEncoder {
-
-	public static class Snippet {
-		private final String snippet;
-
-		public Snippet(String snippet) {
-			this.snippet = snippet;
-		}
-
-		public String getSnippet() {
-			return this.snippet;
-		}
-	}
-
-	public static Snippet asSnippet(String text) {
-		return new Snippet(text);
-	}
-
-	private final boolean prettyPrint;
-	private final String indent;
-	private final String newline;
-
-	public DataEncoder() {
-		this(null, null);
-	}
-
-	public DataEncoder(String newline, String indent) {
-		this.newline = (newline != null) ? newline : "";
-		this.indent = (indent != null) ? indent : "";
-		this.prettyPrint = (this.indent.length() > 0) || (this.newline.length() > 0);
-	}
-
-	public boolean isPrettyPrint() {
-		return this.prettyPrint;
-	}
-
-	/**
-	 * Serializes the data as JavaScript literals
-	 * @param output
-	 * @param data Data to serialize
-	 * @throws IOException
-	 */
-	public void write(Appendable output, Object data)
-		throws IOException {
-
-		if (data instanceof SparseMap) {
-			return;
-		}
-		this.write(output, data, 0);
-	}
-
-	/**
-	 * Serializes the data as JavaScript literals
-	 * @param output 
-	 * @param data Data to serialize
-	 * @param depth Starting indentation depth
-	 * @throws IOException
-	 */
-	public void write(Appendable output, Object data, int depth)
-		throws IOException {
-
-		if (data == null) {
-			output.append("null");
-			return;
-		}
-
-		Class<?> dataType = data.getClass();
-
-		if (Snippet.class.equals(dataType)) {
-			output.append(((Snippet)data).getSnippet());
-
-		} else if (String.class.equals(dataType)) {
-			this.writeString(output, (String)data);
-
-		} else if (DuelData.isNumber(dataType)) {
-			this.writeNumber(output, data);
-
-		} else if (Date.class.equals(dataType)) {
-			this.writeDate(output, (Date)data);
-
-		} else if (DuelData.isBoolean(dataType)) {
-			this.writeBoolean(output, DuelData.coerceBoolean(data));
-
-		} else if (DuelData.isArray(dataType)) {
-			this.writeArray(output, DuelData.coerceCollection(data), depth);
-
-		} else if (Date.class.equals(dataType)) {
-			this.writeDate(output, (Date)data);
-
-			// need to also serialize RegExp literals
-
-		} else {
-			this.writeObject(output, DuelData.coerceMap(data), depth);
-		}
-	}
-
-	private void writeBoolean(Appendable output, boolean data)
-		throws IOException {
-
-		output.append(data ? "true" : "false");
-	}
-
-	private void writeNumber(Appendable output, Object data)
-		throws IOException {
-
-		// format like JavaScript
-		double number = ((Number)data).doubleValue();
-
-		Class<?> dataType = data.getClass();
-
-		// TODO: support BigDecimal and BigInteger
-		if (Long.class.equals(dataType) || long.class.equals(dataType)) {
-			long numberLong = ((Number)data).longValue();
-
-			// if overflows IEEE-754 precision then emit as String
-			if (invalidIEEE754(numberLong)) {
-				this.writeString(output, Long.toString(numberLong));
-			} else {
-				output.append(Long.toString(numberLong));
-			}
-		}
-
-		else if (number == (double)((long)number)) {
-			// integers should be formatted without trailing decimals
-			output.append(Long.toString((long)number));
-
-		} else {
-			// correctly prints NaN, Infinity, -Infinity
-			output.append(Double.toString(number));
-		}
-	}
-
-	private void writeDate(Appendable output, Date data)
-		throws IOException {
-
-		output.append("new Date(");
-
-		if (false) {
-			// TODO: allow formatting in browser's timezone
-			// new Date(yyyy, M, d, H, m, s, ms)
-		} else {
-			// format as UTC
-			output.append(Long.toString(data.getTime()));
-		}
-		output.append(")");
-	}
-
-	private void writeArray(Appendable output, Collection<?> data, int depth)
-		throws IOException {
-
-		output.append('[');
-		depth++;
-
-		boolean singleAttr = (data.size() == 1);
-		boolean hasChildren = singleAttr;
-		boolean needsDelim = false;
-		for (Object item : data) {
-			if (data instanceof SparseMap) {
-				continue;
-			}
-
-			if (singleAttr) {
-				if (this.prettyPrint) {
-					output.append(' ');
-				}
-			} else {
-				// property delimiter
-				if (needsDelim) {
-					output.append(',');
-				} else {
-					hasChildren = needsDelim = true;
-				}
-
-				if (this.prettyPrint) {
-					this.writeln(output, depth);
-				}
-			}
-
-			this.write(output, item, depth);
-		}
-
-		depth--;
-		if (this.prettyPrint) {
-			if (singleAttr) {
-				output.append(' ');
-			} else if (hasChildren) {
-				this.writeln(output, depth);
-			}
-		}
-		output.append(']');
-	}
-
-	@SuppressWarnings("unchecked")
-	private void writeObject(Appendable output, Map<?, ?> data, int depth)
-		throws IOException {
-
-		output.append('{');
-		depth++;
-
-		Set<?> properties = data.entrySet();
-		boolean singleAttr = (properties.size() == 1);
-		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 {
-				// property delimiter
-				if (needsDelim) {
-					output.append(',');
-				} else {
-					hasChildren = needsDelim = true;
-				}
-
-				if (this.prettyPrint) {
-					this.writeln(output, depth);
-				}
-			}
-
-			this.writePropertyName(output, property.getKey());
-			if (this.prettyPrint) {
-				output.append(" : ");
-			} else {
-				output.append(':');
-			}
-			this.write(output, value, depth);
-		}
-
-		depth--;
-		if (this.prettyPrint) {
-			if (singleAttr) {
-				output.append(' ');
-			} else if (hasChildren) {
-				this.writeln(output, depth);
-			}
-		}
-		output.append('}');
-	}
-
-	private void writePropertyName(Appendable output, Object data)
-		throws IOException {
-
-		String name = DuelData.coerceString(data);
-
-		if (JSUtility.isValidIdentifier(name, false)) {
-			output.append(name);
-		} else {
-			this.writeString(output, name);
-		}
-	}
-
-	private void writeString(Appendable output, String data)
-		throws IOException {
-
-		if (data == null) {
-			output.append("null");
-			return;
-		}
-
-		int start = 0,
-			length = data.length();
-
-		output.append('\"');
-
-		for (int i=start; i<length; i++) {
-			String escape;
-
-			char ch = data.charAt(i);
-			switch (ch) {
-				case '\"':
-					escape = "\\\"";
-					break;
-				case '\\':
-					escape = "\\\\";
-					break;
-				case '\t':
-					escape = "\\t";
-					break;
-				case '\n':
-					escape = "\\n";
-					break;
-				case '\r':
-					escape = "\\r";
-					break;
-				case '\f':
-					escape = "\\f";
-					break;
-				case '\b':
-					escape = "\\b";
-					break;
-				default:
-					if (ch >= ' ' && ch < '\u007F') {
-						// no need to escape ASCII chars
-						continue;
-					}
-
-					escape = String.format("\\u%04X", data.codePointAt(i));
-					break;
-			}
-
-			if (i > start) {
-				output.append(data, start, i);
-			}
-			start = i+1;
-
-			output.append(escape);
-		}
-
-		if (length > start) {
-			output.append(data, start, length);
-		}
-
-		output.append('\"');
-	}
-
-	/**
-	 * Produces more compact namespace declarations.
-	 * @param output
-	 * @param namespaces
-	 * @param ident
-	 * @return true if namespaces were emitted
-	 * @throws IOException
-	 */
-	public boolean writeNamespace(Appendable output, List<String> namespaces, String ident)
-		throws IOException {
-
-		if (!JSUtility.isValidIdentifier(ident, true)) {
-			throw new IllegalArgumentException("Invalid identifier: "+ident);
-		}
-
-		boolean wroteNS = false;
-		boolean isRoot = true;
-		int nextDot = ident.indexOf('.');
-		while (nextDot > -1) {
-			String ns = ident.substring(0, nextDot);
-
-			// check if already exists
-			if ((isRoot && JSUtility.isGlobalIdent(ns)) || namespaces.contains(ns)) {
-				// next iteration
-				nextDot = ident.indexOf('.', nextDot+1);
-				isRoot = false;
-				continue;
-			}
-			namespaces.add(ns);
-
-			if (isRoot) {
-				output.append("var ");
-				isRoot = false;
-			}
-
-			output.append(ns);
-			if (this.prettyPrint) {
-				output.append(' ');
-			}
-			output.append('=');
-			if (this.prettyPrint) {
-				output.append(' ');
-			}
-			output.append(ns);
-			if (this.prettyPrint) {
-				output.append(' ');
-			}
-			output.append("||");
-			if (this.prettyPrint) {
-				output.append(' ');
-			}
-			output.append("{};");
-
-			// next iteration
-			nextDot = ident.indexOf('.', nextDot+1);
-			this.writeln(output, 0);
-			wroteNS = true;
-		}
-
-		return wroteNS;
-	}
-
-	/**
-	 * Produces more verbose but technically more correct namespace declarations
-	 * @param output
-	 * @param namespaces
-	 * @param ident
-	 * @throws IOException
-	 */
-	@Deprecated
-	public void writeNamespaceAlt(Appendable output, List<String> namespaces, String ident)
-		throws IOException {
-
-		if (!JSUtility.isValidIdentifier(ident, true)) {
-			throw new IllegalArgumentException("Invalid identifier: "+ident);
-		}
-
-		boolean needsNewline = false;
-		boolean isRoot = true;
-		int nextDot = ident.indexOf('.');
-		while (nextDot > -1) {
-			String ns = ident.substring(0, nextDot);
-
-			// check if already exists
-			if ((isRoot && JSUtility.isGlobalIdent(ns)) || namespaces.contains(ns)) {
-				// next iteration
-				nextDot = ident.indexOf('.', nextDot+1);
-				isRoot = false;
-				continue;
-			}
-			namespaces.add(ns);
-
-			if (isRoot) {
-				this.writeln(output, 0);
-				output.append("var ");
-				output.append(ns);
-				output.append(';');
-				isRoot = false;
-			}
-
-			this.writeln(output, 0);
-			output.append("if (typeof ");
-			output.append(ns);
-			output.append(" === \"undefined\") {");
-			this.writeln(output, 1);
-			output.append(ns);
-			output.append(" = {};");
-			this.writeln(output, 0);
-			output.append('}');
-
-			// next iteration
-			nextDot = ident.indexOf('.', nextDot+1);
-			needsNewline = true;
-		}
-
-		if (needsNewline) {
-			this.writeln(output, 0);
-		}
-	}
-
-	private void writeln(Appendable output, int depth)
-		throws IOException {
-
-		output.append(this.newline);
-
-		while (depth-- > 0) {
-			output.append(this.indent);
-		}
-	}
-
-	/**
-	 * Checks if Number cannot be represented in JavaScript without changing
-	 * http://stackoverflow.com/questions/1601646
-	 * http://stackoverflow.com/questions/4349155
-	 */
-	private static boolean invalidIEEE754(long value) {
-		if (Long.MAX_VALUE == value || Long.MIN_VALUE == value) {
-			// these are technically valid IEEE-754 but JavaScript truncates
-			return true;
-		}
-
-		try {
-			return ((long)((double)value)) != value;
-		} catch (Exception ex) {
-			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 whenever 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 (Map.Entry<String, Object> externalVar : vars.entrySet()) {
-			String key = externalVar.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, externalVar.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 (Map.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);
-			}
-		}
-	}
-}

duel/duel-compiler/src/main/java/org/duelengine/duel/DuelContext.java

-package org.duelengine.duel;
-
-/**
- * Maintains context state for a single binding/render cycle.
- * DuelContext is NOT thread-safe and not intended to be reusable.
- */
-public class DuelContext {
-
-	private enum ExternalsState {
-
-		/**
-		 * No external data
-		 */
-		NONE,
-
-		/**
-		 * Never emitted
-		 */
-		PENDING,
-
-		/**
-		 * Emitted
-		 */
-		EMITTED,
-
-		/**
-		 * Changed after emitted 
-		 */
-		DIRTY
-	}
-
-	private Appendable output;
-	private ClientIDStrategy clientID;
-	private DataEncoder encoder;
-	private String newline;
-	private String indent;
-	private boolean encodeNonASCII;
-
-	private ExternalsState externalsState = ExternalsState.NONE;
-	private SparseMap externals;
-	private SparseMap dirty;
-
-	public DuelContext() {
-	}
-
-	public DuelContext(Appendable output) {
-		if (output == null) {
-			throw new NullPointerException("output");
-		}
-
-		this.output = output;
-	}
-
-	public Appendable getOutput() {
-		if (this.output == null) {
-			this.output = new StringBuilder();
-		}
-
-		return this.output;
-	}
-
-	public DuelContext setOutput(Appendable output) {
-		if (output == null) {
-			throw new NullPointerException("output");
-		}
-
-		this.output = output;
-		return this;
-	}
-
-	ClientIDStrategy getClientID() {
-		return this.clientID;
-	}
-
-	public DuelContext setClientID(ClientIDStrategy value) {
-		this.clientID = value;
-		return this;
-	}
-
-	String getNewline() {
-		return this.newline;
-	}
-
-	public DuelContext setNewline(String value) {
-		this.newline = value;
-		return this;
-	}
-
-	String getIndent() {
-		return this.indent;
-	}
-
-	public DuelContext setIndent(String value) {
-		this.indent = value;
-		return this;
-	}
-
-	boolean getEncodeNonASCII() {
-		return this.encodeNonASCII;
-	}
-
-	public DuelContext setEncodeNonASCII(boolean value) {
-		this.encodeNonASCII = value;
-		return this;
-	}
-
-	public DuelContext putExternal(String ident, Object value) {
-		if (ident == null) {
-			throw new NullPointerException("ident");
-		}
-
-		if (this.externals == null) {
-			this.externals = new SparseMap();
-		}
-		this.externals.putSparse(ident, value);
-
-		switch (this.externalsState) {
-			case NONE:
-			case PENDING:
-				this.externalsState = ExternalsState.PENDING;
-				break;
-			case EMITTED:
-			case DIRTY:
-				this.externalsState = ExternalsState.DIRTY;
-				if (this.dirty == null) {
-					this.dirty = new SparseMap();
-				}
-				// also add to dirty set
-				this.dirty.putSparse(ident, value);
-				break;
-		}
-		return this;
-	}
-
-	boolean hasExternals(String... idents) {
-		if (this.externals == null) {
-			return false;
-		}
-
-		if (idents != null) {
-			for (String ident : idents) {
-				if (!this.externals.containsKey(ident)) {
-					return false;
-				}
-			}
-		}
-
-		return true;
-	}
-
-	Object getExternal(String ident) {
-		if (ident == null) {
-			throw new NullPointerException("ident");
-		}
-
-		if (this.externals == null || !this.externals.containsKey(ident)) {
-			return null;
-		}
-
-		return this.externals.get(ident);
-	}
-
-	SparseMap getPendingExternals() {
-
-		SparseMap sparseMap = (this.externalsState == ExternalsState.DIRTY) ? this.dirty : this.externals;
-		this.externalsState = ExternalsState.EMITTED;
-		this.dirty = null;
-		return sparseMap;
-	}
-
-	boolean hasExternalsPending() {
-		switch (this.externalsState) {
-			case PENDING:
-			case DIRTY:
-				return true;
-			default:
-				return false;
-		}
-	}
-
-	DataEncoder getEncoder() {
-		if (this.encoder == null) {
-			this.encoder = new DataEncoder(this.newline, this.indent);
-		}
-
-		return this.encoder;
-	}
-
-	String nextID() {
-		if (this.clientID == null) {
-			this.clientID = new IncClientIDStrategy();
-		}
-
-		return this.clientID.nextID();
-	}
-}

duel/duel-compiler/src/main/java/org/duelengine/duel/DuelData.java

-package org.duelengine.duel;
-
-import java.lang.reflect.Array;
-import java.math.*;
-import java.util.*;
-
-public final class DuelData {
-	
-	private static final Double ZERO = Double.valueOf(0.0);
-	private static final Double NaN = Double.valueOf(Double.NaN);
-
-	// static class
-	private DuelData() {}
-
-	/**
-	 * Builds a mutable Map from an interlaced sequence of key-value pairs
-	 * @param pairs
-	 * @return
-	 */
-	public static Map<String, Object> asMap(Object... pairs) {
-		if (pairs == null || pairs.length < 1) {
-			return new LinkedHashMap<String, Object>(0);
-		}
-
-		int length = pairs.length/2;
-		Map<String, Object> map = new LinkedHashMap<String, Object>(length+2);
-		for (int i=0; i<length; i++) {
-			String key = coerceString(pairs[2*i]);
-			Object value = pairs[2*i+1];
-			map.put(key, value);
-		}
-		return map;
-	}
-
-	/**
-	 * Builds a mutable List from a sequence of items
-	 * @param items
-	 * @return
-	 */
-	public static <T> List<T> asList(T... items) {
-		if (items == null || items.length < 1) {
-			return new ArrayList<T>(0);
-		}
-
-		return Arrays.asList(items);
-	}
-
-	public static boolean isPrimitive(Class<?> dataType) {
-		return (dataType.isPrimitive() ||
-				String.class.equals(dataType) ||
-				Number.class.isAssignableFrom(dataType) ||
-				Date.class.equals(dataType) ||
-				Boolean.class.equals(dataType) ||
-				Character.class.isAssignableFrom(dataType));
-	}
-
-	public static boolean isBoolean(Class<?> exprType) {
-		return (Boolean.class.equals(exprType) ||
-				boolean.class.equals(exprType));
-	}
-
-	public static boolean isNumber(Class<?> exprType) {
-		return (Number.class.isAssignableFrom(exprType) ||
-				int.class.isAssignableFrom(exprType) ||
-				double.class.isAssignableFrom(exprType) ||
-				float.class.isAssignableFrom(exprType) ||
-				long.class.isAssignableFrom(exprType) ||
-				short.class.isAssignableFrom(exprType) ||
-				byte.class.isAssignableFrom(exprType) ||
-				BigInteger.class.isAssignableFrom(exprType) ||
-				BigDecimal.class.isAssignableFrom(exprType));
-	}
-
-	public static boolean isString(Class<?> exprType) {
-		return (String.class.equals(exprType));
-	}
-
-	public static boolean isArray(Class<?> exprType) {
-		return (exprType.isArray() ||
-				Iterable.class.isAssignableFrom(exprType));
-	}
-
-	/**
-	 * Coerces any Object to a JS-style Boolean
-	 * @param data
-	 * @return
-	 */
-	public static boolean coerceBoolean(Object data) {
-		if (data == null || Boolean.FALSE.equals(data) || "".equals(data)) {
-			return false;
-		}
-
-		if (data instanceof Number) {
-			data = ((Number)data).doubleValue();
-			return !(ZERO.equals(data) || NaN.equals(data));
-		}
-
-		return true;
-	}
-
-	/**
-	 * Coerces any Object to a JS-style Number (double)
-	 * @param data
-	 * @return
-	 */
-	public static double coerceNumber(Object data) {
-		if (data instanceof Number) {
-			return ((Number)data).doubleValue();
-		}
-
-		if (data instanceof Boolean) {
-			return ((Boolean)data).booleanValue() ? 1.0 : 0.0;
-		}
-
-		return coerceBoolean(data) ? Double.NaN : 0.0;
-	}
-
-	/**
-	 * Coerces any Object to a JS-style String 
-	 * @param data
-	 * @return
-	 */
-	public static String coerceString(Object data) {
-		if (data == null) {
-			return "";
-		}
-
-		Class<?> dataType = data.getClass();
-
-		if (String.class.equals(dataType)) {
-			return (String)data;
-		}
-
-		if (Date.class.equals(dataType)) {
-			// YYYY-MM-DD HH:mm:ss Z
-			//return String.format("%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS.%1$tLZ", data);
-			return String.format("%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS Z", data);
-		}
-
-		if (isNumber(dataType)) {
-			// format like JavaScript
-			double number = ((Number)data).doubleValue();
-
-			// integers formatted without trailing decimals
-			if (number == (double)((long)number)) {
-				return Long.toString((long)number);
-			}
-
-			// correctly prints NaN, Infinity, -Infinity
-			return Double.toString(number);
-		}
-
-		if (Iterable.class.isAssignableFrom(dataType)) {
-			// flatten into simple list
-			StringBuilder buffer = new StringBuilder();
-			boolean needsDelim = false;
-			for (Object item : (Iterable<?>)data) {
-				if (needsDelim) {
-					buffer.append(", ");
-				} else {
-					needsDelim = true;
-				}
-				buffer.append(coerceString(item));
-			}
-			return buffer.toString();
-		}
-
-		if (dataType.isArray()) {
-			// flatten into simple list
-			StringBuilder buffer = new StringBuilder();
-			boolean needsDelim = false;
-			for (int i=0, length=Array.getLength(data); i<length; i++) {
-				if (needsDelim) {
-					buffer.append(", ");
-				} else {
-					needsDelim = true;
-				}
-				buffer.append(coerceString(Array.get(data, i)));
-			}
-			return buffer.toString();
-		}
-
-		if (Map.class.isAssignableFrom(dataType)) {
-			// format JSON-like
-			Map<?,?> map = (Map<?,?>)data;
-			Iterator<?> iterator = map.entrySet().iterator();
-			StringBuilder buffer = new StringBuilder().append('{');
-
-			boolean needsDelim = false;
-			while (iterator.hasNext()) {
-				if (needsDelim) {
-					buffer.append(", ");
-				} else {
-					needsDelim = true;
-				}
-
-				Map.Entry<?,?> entry = (Map.Entry<?,?>)iterator.next();
-				buffer
-					.append(coerceString(entry.getKey()))
-					.append('=')
-					.append(coerceString(entry.getValue()));
-			}
-
-			return buffer.append('}').toString();
-		}
-
-		return data.toString();
-	}
-
-	/**
-	 * Coerces any Object to a JS-style Array (List)
-	 * @param data
-	 * @return
-	 */
-	public static Collection<?> coerceCollection(Object data) {
-		if (data == null) {
-			return Collections.EMPTY_LIST;
-		}
-
-		Class<?> dataType = data.getClass();
-
-		if (Collection.class.isAssignableFrom(dataType)) {
-			// already correct type
-			return (Collection<?>)data;
-		}
-
-		if (dataType.isArray()) {
-			return new ArrayIterable(data);
-		}
-
-		if (Iterable.class.isAssignableFrom(dataType)) {
-			// unfortunate but we need the size
-			List<Object> list = new LinkedList<Object>();
-			for (Object item : (Iterable<?>)data) {
-				list.add(item);
-			}
-			return list;
-		}
-
-		return new SingleIterable(data);
-	}
-
-	/**
-	 * Coerces any Object to a JS-style Object (Map)
-	 * @param data
-	 * @return
-	 */
-	public static Map<?,?> coerceMap(Object data) {
-		if (data == null) {
-			return Collections.EMPTY_MAP;
-		}
-
-		Class<?> dataType = data.getClass();
-
-		if (Map.class.isAssignableFrom(dataType)) {
-			return (Map<?,?>)data;
-		}
-
-		if (isPrimitive(dataType)) {
-			// doesn't make sense to coerce to Map
-			// this will give results similar to client-side
-			return asMap("", data);
-		}
-
-		if (isArray(dataType)) {
-			int i = 0;
-			Collection<?> array = coerceCollection(data);
-			LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(array.size());
-			for (Object item : array) {
-				map.put(Integer.toString(i++), item);
-			}
-			return map;
-		}
-
-		return new ProxyMap(data, true);
-	}
-
-	/**
-	 * Ensures a data object is easily walked
-	 * @param data
-	 * @return
-	 */
-	static Object asProxy(Object data, boolean readonly) {
-		if (data == null) {
-			return null;
-		}
-
-		Class<?> dataType = data.getClass();
-		if (isPrimitive(dataType) ||
-			isArray(dataType) ||
-			Map.class.isAssignableFrom(dataType)) {
-
-			return data;
-		}
-
-		// wrap for easy access
-		return new ProxyMap(data, readonly);
-	}
-}

duel/duel-compiler/src/main/java/org/duelengine/duel/DuelPart.java

-package org.duelengine.duel;
-
-public abstract class DuelPart extends DuelView {
-
-	public abstract String getPartName();
-}

duel/duel-compiler/src/main/java/org/duelengine/duel/DuelView.java

-package org.duelengine.duel;
-
-import java.io.*;
-import java.lang.reflect.Array;
-import java.util.*;
-
-/**
- * The skeletal implementation of DUEL view runtime.
- * Inherently thread-safe as contains no mutable instance data.
- */
-public abstract class DuelView {
-
-	private static final Object DEFAULT_DATA = Collections.EMPTY_MAP;
-	private static final int DEFAULT_INDEX = 0;
-	private static final int DEFAULT_COUNT = 1;
-	private static final String DEFAULT_KEY = null;
-	
-	private static final HTMLFormatter formatter = new HTMLFormatter();
-	private Map<String, DuelPart> parts = null;
-
-	protected DuelView() {
-		// allow view to define child views
-		this.init();
-	}
-
-	protected DuelView(DuelPart... parts) {
-
-		// first allow view to define child views and default parts
-		this.init();
-
-		// then allow caller to replace any parts by name
-		if (parts != null && parts.length > 0) {
-			if (this.parts == null) {
-				this.parts = new HashMap<String, DuelPart>(parts.length);
-			}
-
-			for (DuelPart part : parts) {
-				if (part == null || part.getPartName() == null) {
-					continue;
-				}
-
-				this.parts.put(part.getPartName(), part);
-			}
-		}
-	}
-
-	/**
-	 * Initialization of parts and child-views
-	 */
-	protected void init() {}
-
-	/**
-	 * Renders the view to the output
-	 * @param output
-	 */
-	public void render(Appendable output)
-		throws IOException {
-
-		if (output == null) {
-			throw new NullPointerException("output");
-		}
-
-		this.render(new DuelContext(output), DEFAULT_DATA, DEFAULT_INDEX, DEFAULT_COUNT, DEFAULT_KEY);
-	}
-
-	/**
-	 * Binds the view to the data and renders the view to the output
-	 * @param output
-	 * @param data
-	 */
-	public void render(Appendable output, Object data)
-		throws IOException {
-
-		if (output == null) {
-			throw new NullPointerException("output");
-		}
-
-		this.render(new DuelContext(output), DuelData.asProxy(data, true), DEFAULT_INDEX, DEFAULT_COUNT, DEFAULT_KEY);
-	}
-
-	/**
-	 * Renders the view to the output
-	 * @param context
-	 */
-	public void render(DuelContext context)
-		throws IOException {
-
-		if (context == null) {
-			throw new NullPointerException("context");
-		}
-
-		this.render(context, DEFAULT_DATA, DEFAULT_INDEX, DEFAULT_COUNT, DEFAULT_KEY);
-	}
-
-	/**
-	 * Binds the view to the data and renders the view to the output
-	 * @param context
-	 * @param data
-	 */
-	public void render(DuelContext context, Object data)
-		throws IOException {
-
-		if (context == null) {
-			throw new NullPointerException("context");
-		}
-
-		this.render(context, DuelData.asProxy(data, true), DEFAULT_INDEX, DEFAULT_COUNT, DEFAULT_KEY);
-	}
-
-	/**
-	 * The entry point into the view tree
-	 * @param context
-	 * @param data
-	 * @param index
-	 * @param count
-	 * @param key
-	 */
-	protected abstract void render(DuelContext context, Object data, int index, int count, String key)
-		throws IOException;
-
-	/**
-	 * Sets the partial view for a named area
-	 * @param part
-	 */
-	protected void addPart(DuelPart part) {
-		if (part == null || part.getPartName() == null) {
-			return;
-		}
-
-		if (this.parts == null) {
-			this.parts = new HashMap<String, DuelPart>(4);
-		}
-
-		this.parts.put(part.getPartName(), part);
-	}
-
-	/**
-	 * Renders a named partial view
-	 * @param partName
-	 * @param context
-	 * @param data
-	 * @param index
-	 * @param count
-	 * @param key
-	 */
-	protected void renderPart(DuelContext context, String partName, Object data, int index, int count, String key)
-		throws IOException {
-
-		if (this.parts == null || !this.parts.containsKey(partName)) {
-			return;
-		}
-
-		DuelPart part = this.parts.get(partName);
-		if (part == null) {
-			return;
-		}
-
-		part.render(context, data, index, count, key);
-	}
-
-	/**
-	 * Allows one view to render another
-	 * @param view
-	 * @param context
-	 * @param data
-	 * @param index
-	 * @param count
-	 * @param key
-	 */
-	protected void renderView(DuelContext context, DuelView view, Object data, int index, int count, String key)
-		throws IOException {
-
-		view.render(context, data, index, count, key);
-	}
-
-	/**
-	 * Writes the value to the output
-	 * @param context
-	 * @param value
-	 * @throws IOException
-	 */
-	protected void write(DuelContext context, Object value)
-		throws IOException {
-
-		if (value == null) {
-			return;
-		}
-
-		context.getOutput().append(DuelData.coerceString(value));
-	}
-
-	/**
-	 * Writes the value to the output
-	 * @param context
-	 * @param value
-	 * @throws IOException
-	 */
-	protected void write(DuelContext context, char value)
-		throws IOException {
-
-		context.getOutput().append(value);
-	}
-
-	/**
-	 * Writes the value to the output
-	 * @param context
-	 * @param value
-	 * @throws IOException
-	 */
-	protected void write(DuelContext context, String value)
-		throws IOException {
-
-		context.getOutput().append(value);
-	}
-
-	/**
-	 * Ensures the value is properly encoded as HTML text
-	 * @param context
-	 * @param value
-	 * @throws IOException
-	 */
-	protected void htmlEncode(DuelContext context, Object value)
-		throws IOException {
-
-		if (value == null) {
-			return;
-		}
-
-		if (value instanceof Boolean || value instanceof Number) {
-			// no need to encode non-text primitives
-			context.getOutput().append(DuelData.coerceString(value));
-
-		} else {
-			formatter.writeLiteral(context.getOutput(), DuelData.coerceString(value), context.getEncodeNonASCII());
-		}
-	}
-
-	/**
-	 * Emits data object as a graph of JavaScript literals
-	 * @param context
-	 * @param data
-	 * @param depth
-	 * @throws IOException
-	 */
-	protected void dataEncode(DuelContext context, Object data, int depth)
-		throws IOException {
-
-		context.getEncoder().write(context.getOutput(), data, depth);
-	}
-
-	protected Object getExternal(DuelContext context, String ident) {
-		return context.getExternal(ident);
-	}
-
-	protected void putExternal(DuelContext context, String ident, Object value) {
-		context.putExternal(ident, value);
-	}
-
-	protected boolean hasExternals(DuelContext context, String... idents) {
-		return context.hasExternals(idents);
-	}
-
-	protected void writeExternals(DuelContext context, boolean needsTags)
-		throws IOException {
-
-		if (!context.hasExternalsPending()) {
-			return;
-		}
-
-		Appendable output = context.getOutput();
-		if (needsTags) {
-			formatter
-				.writeOpenElementBeginTag(output, "script")
-				.writeAttribute(output, "type", "text/javascript")
-				.writeCloseElementBeginTag(output);
-		}
-		context.getEncoder().writeVars(output, context.getPendingExternals());
-		if (needsTags) {
-			formatter.writeElementEndTag(output, "script");
-		}
-	}
-
-	protected String nextID(DuelContext context) {
-		return context.nextID();
-	}
-
-	/**
-	 * Retrieves the value of a property from the data object
-	 * @param data
-	 * @return
-	 */
-	protected Object getProperty(Object data, Object property) {
-		if (data == null || property == null) {
-			// technically "undefined" or error if data is null
-			return null;
-		}
-
-		Class<?> dataType = data.getClass(); 
-		String key = DuelData.coerceString(property);
-
-		if (DuelData.isString(dataType)) {
-			String str = DuelData.coerceString(data);
-
-			if ("length".equals(key)) {
-				return str.length();
-			}
-
-			if (DuelData.isNumber(property.getClass())) {
-				int index = ((Number)DuelData.coerceNumber(property)).intValue();
-				if ((index < 0) || (index >= str.length())) {
-					// technically "undefined"
-					return null;
-				}
-				return str.charAt(index);
-			}
-
-			// technically "undefined"
-			return null;
-		}
-
-		if (DuelData.isArray(dataType)) {
-			Collection<?> array = DuelData.coerceCollection(data);
-
-			if ("length".equals(key)) {
-				return array.size();
-			}
-
-			if (DuelData.isNumber(property.getClass()) &&
-				array instanceof List<?>) {
-				int index = ((Number)DuelData.coerceNumber(property)).intValue();
-				if ((index < 0) || (index >= array.size())) {
-					// technically "undefined"
-					return null;
-				}
-				return DuelData.asProxy(((List<?>)array).get(index), true);
-			}
-
-			// technically "undefined"
-			return null;
-		}
-
-		Map<?,?> map = DuelData.coerceMap(data);
-		if (map == null || !map.containsKey(key)) {
-			// technically "undefined"
-			return null;
-		}
-
-		return DuelData.asProxy(map.get(key), true);
-	}
-
-	/**
-	 * Stores the value for a property of the data object
-	 * @param data
-	 * @return
-	 */
-	@SuppressWarnings({ "unchecked", "rawtypes" })
-	protected void setProperty(Object data, Object property, Object value) {
-		if (data == null || property == null) {
-			// technically error if data is null
-			return;
-		}
-
-		Class<?> dataType = data.getClass(); 
-
-		if (Map.class.isAssignableFrom(dataType)) {
-			Map map = (Map)data;
-			map.put(DuelData.coerceString(property), value);
-			return;
-		}
-
-		if (dataType.isArray()) {
-			if (DuelData.isNumber(property.getClass())) {
-				int index = ((Number)DuelData.coerceNumber(property)).intValue();
-				if ((index < 0) || (index >= Array.getLength(data))) {
-					return;
-				}
-				Array.set(data, index, value);
-				return;
-			}
-			return;
-		}
-
-		if (List.class.isAssignableFrom(dataType)) {
-			List array = (List)data;
-			if (DuelData.isNumber(property.getClass())) {
-				int index = ((Number)DuelData.coerceNumber(property)).intValue();
-				if ((index < 0) || (index >= array.size())) {
-					return;
-				}
-				array.set(index, data);
-				return;
-			}
-			return;
-		}
-	}
-
-	/**
-	 * Performs equality test
-	 * @param a
-	 * @param b
-	 * @return
-	 */
-	protected boolean equal(Object a, Object b) {
-		return (a == null) ? (b == null) : a.equals(b);
-	}
-
-	/**
-	 * Coerces objects before performing equality test
-	 * @param a
-	 * @param b
-	 * @return
-	 */
-	protected boolean coerceEqual(Object a, Object b) {
-		if (a == null) {
-			return (b == null);
-		}
-
-		// attempt to coerce b to the type of a
-		Class<?> aType = a.getClass();
-
-		if (DuelData.isNumber(aType)) {
-			b = DuelData.coerceNumber(b);
-
-		} else if (DuelData.isString(aType)) {
-			b = DuelData.coerceString(b);
-
-		} else if (DuelData.isBoolean(aType)) {
-			b = DuelData.coerceBoolean(b);
-		}
-
-		return a.equals(b);
-	}
-
-	protected Object LogicalOR(Object left, Object right) {
-		return DuelData.coerceBoolean(left) ? left : right;
-	}
-
-	protected Object LogicalAND(Object left, Object right) {
-		return DuelData.coerceBoolean(left) ? right : left;
-	}
-
-	/**
-	 * A work-around for dynamic post-inc/dec operator semantics
-	 * @param value
-	 * @param ignore
-	 * @return
-	 */
-	protected double echo(double value, double ignore) {
-		return value;
-	}
-}

duel/duel-compiler/src/main/java/org/duelengine/duel/HTMLFormatter.java

-package org.duelengine.duel;
-
-import java.io.*;
-
-/**
- * A simple abstraction for writing HTML.
- * Inherently thread-safe as contains no mutable instance data.
- */
-public class HTMLFormatter {
-
-	public HTMLFormatter writeComment(Appendable output, String value)
-		throws IOException {
-
-		final String OPEN = "<!--";
-		final String CLOSE = "-->";
-
-		output.append(OPEN);
-		if (value != null) {
-			int start = 0,
-				length = value.length();
-
-			int close = value.indexOf(CLOSE);
-			while (close >= 0) {
-				output.append(value, start, close+2).append("&gt;");
-				start = close+3;
-				close = value.indexOf(CLOSE, start);
-			}
-
-			if (start < length) {
-				output.append(value, start, length);
-			}
-		}
-		output.append(CLOSE);
-
-		return this;
-	}
-
-	public HTMLFormatter writeDocType(Appendable output, String value)
-		throws IOException {
-
-		final String OPEN = "<!DOCTYPE";
-		final char CLOSE = '>';
-
-		output.append(OPEN);
-		if (value != null && value.length() > 0) {
-			output.append(' ').append(value);
-		}
-		output.append(CLOSE);
-
-		return this;
-	}
-
-	public HTMLFormatter writeOpenElementBeginTag(Appendable output, String tagName)
-		throws IOException {
-
-		output.append('<').append(tagName);
-
-		return this;
-	}
-
-	public HTMLFormatter writeOpenAttribute(Appendable output, String name)
-		throws IOException {
-
-		output.append(' ').append(name).append("=\"");
-
-		return this;
-	}
-
-	public HTMLFormatter writeCloseAttribute(Appendable output)
-		throws IOException {
-
-		output.append('"');
-
-		return this;
-	}
-
-	public HTMLFormatter writeAttribute(Appendable output, String name, String value)
-		throws IOException {
-
-		output.append(' ').append(name);
-		if (value != null) {
-			output.append("=\"");
-			this.writeLiteral(output, value, true, true);
-			output.append('"');
-		}
-
-		return this;
-	}
-
-	public HTMLFormatter writeCloseElementBeginTag(Appendable output)
-		throws IOException {
-
-		output.append('>');
-
-		return this;
-	}
-
-	public HTMLFormatter writeCloseElementVoidTag(Appendable output)
-		throws IOException {
-
-		output.append(" />");
-
-		return this;
-	}
-
-	public HTMLFormatter writeElementEndTag(Appendable output, String tagName)
-		throws IOException {
-
-		output.append("</").append(tagName).append('>');
-
-		return this;
-	}
-