Added:
trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/
trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/Database.java
trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/DatabaseException.java
trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/DatabaseManager.java
trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/jdbm/
trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/jdbm/HTreeMap.java
trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/jdbm/JdbmDatabase.java
trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/jdbm/JdbmDatabaseManager.java
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/LexerContext.java
trunk/twxbbs/src/main/resources/org/twdata/twxbbs/proxy/script/json2.js
trunk/twxbbs/src/test/java/org/twdata/twxbbs/db/
trunk/twxbbs/src/test/java/org/twdata/twxbbs/db/DatabaseManagerTest.java
trunk/twxbbs/src/test/java/org/twdata/twxbbs/db/jdbm/
Modified:
trunk/twxbbs/pom.xml
trunk/twxbbs/scripts/game-A/stop-attacks.js
trunk/twxbbs/scripts/session/login.js
trunk/twxbbs/scripts/session/runGameScripts.js
trunk/twxbbs/src/main/java/org/twdata/twxbbs/Container.java
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/DefaultScriptManager.java
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/JavascriptScript.java
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/ScriptLexer.java
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/SessionScriptRunner.java
trunk/twxbbs/src/main/resources/org/twdata/twxbbs/proxy/script/global.js
trunk/twxbbs/src/test/java/org/twdata/twxbbs/proxy/script/ScriptLexerTest.java
Log:
Adding a database per game, and a flexible system so that any script can
have its own database. Modified the attack script to use the new game
database to store who tries to attack. The database supports strings,
integers, and even object serialization and deserialization are
automatically handled.
Also redid how the pause works, so now, you just call pause() and it works
over both streams, activating their triggers. This should make it easier
to port twxproxy scripts, if ever required.
Modified: trunk/twxbbs/pom.xml
==============================================================================
--- trunk/twxbbs/pom.xml (original)
+++ trunk/twxbbs/pom.xml Tue Sep 2 05:36:24 2008
@@ -20,6 +20,12 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>mockobjects</groupId>
+ <artifactId>mockobjects-core</artifactId>
+ <version>0.09</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>1.1.7</version>
@@ -38,6 +44,11 @@
<groupId>org.ini4j</groupId>
<artifactId>ini4j</artifactId>
<version>0.3.2</version>
+ </dependency>
+ <dependency>
+ <groupId>jdbm</groupId>
+ <artifactId>jdbm</artifactId>
+ <version>1.0</version>
</dependency>
</dependencies>
<build>
Modified: trunk/twxbbs/scripts/game-A/stop-attacks.js
==============================================================================
--- trunk/twxbbs/scripts/game-A/stop-attacks.js (original)
+++ trunk/twxbbs/scripts/game-A/stop-attacks.js Tue Sep 2 05:36:24 2008
@@ -2,7 +2,7 @@
function maybeAttack(prompt) {
game.setTextTrigger("maybeAttack", "[N]?", attack);
- game.pause();
+ pause();
game.killTextTrigger("maybeAttack");
}
function attack(prompt) {
@@ -12,13 +12,33 @@
player.send("*\u001b[31m\u001b[1mLet's just be
friends\u001b[22m*");
game.setCapturingTextLineTrigger("no", "No", function(txt){});
game.send("n");
- game.pause();
+
+ // Gets the game database and updates the attackAttempts stats
+ var db = session.get("db");
+ var stats = db.getTable("stats");
+ var attackAttemptsList = stats.get("attackAttempts");
+ attackAttemptsList = (attackAttemptsList == null ? new Array() :
attackAttemptsList);
+ attackAttemptsList.push({
+ "targetTrader" : match[1],
+ "targetShip" : match[2],
+ "targetFigs" : match[4],
+ "attackerFigs" : match[3]
+ });
+ stats.put("attackAttempts", attackAttemptsList);
+
+ // debugging
+ println("Attack attempts:");
+ for (x in attackAttemptsList) {
+ println("\tAttacked: "+attackAttemptsList[x].targetTrader);
+ }
+
+ pause();
} else {
println("Can't match :"+prompt+":");
}
- game.pause();
+ pause();
}
game.setTextTrigger("attack", "Attack", maybeAttack);
-game.pause();
\ No newline at end of file
+pause();
\ No newline at end of file
Modified: trunk/twxbbs/scripts/session/login.js
==============================================================================
--- trunk/twxbbs/scripts/session/login.js (original)
+++ trunk/twxbbs/scripts/session/login.js Tue Sep 2 05:36:24 2008
@@ -9,7 +9,7 @@
function gamePause(prompt) {
game.send(" ");
- game.pause();
+ pause();
}
function twMenu(prompt) {
game.send("T*");
@@ -25,14 +25,14 @@
game.setTextTrigger("pause", "[Pause]", gamePause);
game.setTextTrigger("login","ENTER", login);
-game.pause();
+pause();
game.setTextTrigger("menu", "menu):", menu);
-game.pause();
+pause();
game.setTextTrigger("twmenu", "Enter your choice:", twMenu);
-game.pause();
+pause();
game.setTextTrigger("todaysLog", "Show today's log?", todaysLog);
-game.pause();
+pause();
game.setTextTrigger("passwd", "Password?", passwd);
-game.pause();
+pause();
game.setTextTrigger("Command", "Command [", function(txt){});
-game.pause();
\ No newline at end of file
+pause();
\ No newline at end of file
Modified: trunk/twxbbs/scripts/session/runGameScripts.js
==============================================================================
--- trunk/twxbbs/scripts/session/runGameScripts.js (original)
+++ trunk/twxbbs/scripts/session/runGameScripts.js Tue Sep 2 05:36:24 2008
@@ -1,19 +1,70 @@
var lastGameThreads;
-function gameSelectionPrompt(prompt) {
- player.setCapturingTextTrigger("gameSelection", "", gameSelection);
- player.pause();
+function SessionDatabase(db) {
+ this.close = function() {
+ db.close();
+ }
+
+ this.getTable = function(name) {
+ return new SessionDatabaseTable(db.getMap(name));
+ }
}
-function gameSelection(input) {
+// Simple wrapper around the database table map to automatically handle
json encoding/decoding
+function SessionDatabaseTable(map) {
+ this.get = function(key) {
+ var ret = map.get(key);
+ if (ret != null) {
+ if (ret.length() > 0) {
+ var firstChar = ret.substring(0, 1);
+ if (firstChar == '[' || firstChar == '{') {
+ ret = JSON.parse(new String(ret));
+ }
+ }
+ }
+ return ret;
+ };
+ this.put = function(key, value) {
+ if (typeof(value) == 'object' || typeof(value) == 'array') {
+ value = JSON.stringify(value);
+ }
+ map.put(key, value);
+ }
+ this.remove = function(key) {
+ map.remove(new String(key));
+ }
+ this.clear = function() {
+ map.clear();
+ }
+ }
+
+function gameSelectionPrompt(prompt) {
+
+ // Cleanup any old database in the session
+ var oldDb = session.get("db");
+ if (oldDb != null) {
+ oldDb.close();
+ }
+
+ // Stop any old game scripts left running
if (lastGameThreads != null) {
sessionScriptRunner.stopScripts(lastGameThreads);
}
+ player.setCapturingTextTrigger("gameSelection", "", gameSelection);
+ pause();
+}
+
+function gameSelection(input) {
+
if (input.match(/[A-Pa-p]/) != null) {
lastGameThreads =
sessionScriptRunner.runAllInDirectory("game-"+input.toUpperCase());
}
+ session.put("game", input);
+
+ // Create a game db and put it in the session
+ session.put("db", new
SessionDatabase(dbManager.createDatabase("game-"+input)));
game.send(input);
- game.pause();
+ pause();
}
game.setTextTrigger("gameSelectionPrompt", "Selection (? for menu):",
gameSelectionPrompt);
-game.pause();
\ No newline at end of file
+pause();
\ No newline at end of file
Modified: trunk/twxbbs/src/main/java/org/twdata/twxbbs/Container.java
==============================================================================
--- trunk/twxbbs/src/main/java/org/twdata/twxbbs/Container.java (original)
+++ trunk/twxbbs/src/main/java/org/twdata/twxbbs/Container.java Tue Sep 2
05:36:24 2008
@@ -11,6 +11,8 @@
import org.twdata.twxbbs.config.Configuration;
import org.twdata.twxbbs.event.EventManager;
import org.twdata.twxbbs.event.impl.DefaultEventManager;
+import org.twdata.twxbbs.db.DatabaseManager;
+import org.twdata.twxbbs.db.jdbm.JdbmDatabaseManager;
import org.ini4j.Ini;
import javax.servlet.Servlet;
@@ -38,13 +40,17 @@
get(EventManager.class)
));
+ objects.put(DatabaseManager.class, new JdbmDatabaseManager(
+ get(EventManager.class)
+ ));
objects.put(GameAccessor.class, new StubGameAccessor());
objects.put(ProxyConnector.class, new ProxyConnector());
objects.put(TemplateGenerator.class, new TemplateGenerator(
get(EventManager.class)
));
objects.put(DefaultScriptManager.class, new DefaultScriptManager(
- get(EventManager.class)
+ get(EventManager.class),
+ get(DatabaseManager.class)
));
objects.put(ProxyManager.class, new DefaultProxyManager(
get(EventManager.class),
Added: trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/Database.java
==============================================================================
--- (empty file)
+++ trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/Database.java Tue Sep
2 05:36:24 2008
@@ -0,0 +1,18 @@
+package org.twdata.twxbbs.db;
+
+import java.util.Map;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: mrdon
+ * Date: 02/09/2008
+ * Time: 8:18:11 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public interface Database {
+ Map<String,String> getMap(String name);
+
+ void close();
+ void commit();
+ void rollback();
+}
Added:
trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/DatabaseException.java
==============================================================================
--- (empty file)
+++ trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/DatabaseException.java
Tue Sep 2 05:36:24 2008
@@ -0,0 +1,25 @@
+package org.twdata.twxbbs.db;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: mrdon
+ * Date: 02/09/2008
+ * Time: 8:45:10 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class DatabaseException extends RuntimeException {
+ public DatabaseException() {
+ }
+
+ public DatabaseException(String message) {
+ super(message);
+ }
+
+ public DatabaseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public DatabaseException(Throwable cause) {
+ super(cause);
+ }
+}
Added: trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/DatabaseManager.java
==============================================================================
--- (empty file)
+++ trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/DatabaseManager.java
Tue Sep 2 05:36:24 2008
@@ -0,0 +1,18 @@
+package org.twdata.twxbbs.db;
+
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: mrdon
+ * Date: 02/09/2008
+ * Time: 8:17:11 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public interface DatabaseManager {
+
+ Database createDatabase(String name);
+
+ Database createDatabase(String name, Properties props);
+}
Added: trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/jdbm/HTreeMap.java
==============================================================================
--- (empty file)
+++ trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/jdbm/HTreeMap.java Tue
Sep 2 05:36:24 2008
@@ -0,0 +1,155 @@
+package org.twdata.twxbbs.db.jdbm;
+
+import jdbm.htree.HTree;
+import jdbm.helper.FastIterator;
+
+import java.util.*;
+import java.io.IOException;
+
+import org.twdata.twxbbs.db.DatabaseException;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: mrdon
+ * Date: 02/09/2008
+ * Time: 8:42:00 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class HTreeMap implements Map<String,String> {
+ private final HTree htree;
+
+ public HTreeMap(HTree htree) {
+ if (htree == null) {
+ throw new IllegalArgumentException("HTree must not be null");
+ }
+ this.htree = htree;
+ }
+
+ public int size() {
+ int count = 0;
+ try {
+ FastIterator itr = htree.keys();
+ while (itr.next() != null) {
+ count++;
+ }
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ return count;
+ }
+
+ public boolean isEmpty() {
+ try {
+ return (htree.keys().next() == null);
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ }
+
+ public boolean containsKey(Object key) {
+ return get(key) != null;
+ }
+
+ public boolean containsValue(Object value) {
+ try {
+ FastIterator itr = htree.values();
+ Object obj;
+ while ((obj = itr.next()) != null) {
+ if (obj.equals(value)) {
+ return true;
+ }
+ }
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ return false;
+ }
+
+ public String get(Object key) {
+ try {
+ return (String) htree.get(key);
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ }
+
+ public String put(String key, String value) {
+ try {
+ htree.put(key, value);
+ return value;
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ }
+
+ public String remove(Object key) {
+ try {
+ htree.remove(key);
+ return (String) key;
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ }
+
+ public void putAll(Map<? extends String, ? extends String> m) {
+ for (Map.Entry entry : m.entrySet()) {
+ try {
+ htree.put(entry.getKey(), entry.getValue());
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ }
+ }
+
+ public void clear() {
+ Set<String> keys = keySet();
+ for (String key : keys) {
+ remove(key);
+ }
+ }
+
+ public Set<String> keySet() {
+ HashSet<String> keys = new HashSet<String>();
+ FastIterator itr = null;
+ try {
+ itr = htree.keys();
+ Object obj;
+ while ((obj = itr.next()) != null) {
+ keys.add((String) obj);
+ }
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ return keys;
+ }
+
+ public Collection<String> values() {
+ List<String> values = new ArrayList<String>();
+ FastIterator itr = null;
+ try {
+ itr = htree.values();
+ Object obj;
+ while ((obj = itr.next()) != null) {
+ values.add((String) obj);
+ }
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ return values;
+ }
+
+ public Set<Entry<String, String>> entrySet() {
+ HashMap<String,String> entries = new HashMap<String,String>();
+ FastIterator itr = null;
+ try {
+ itr = htree.keys();
+ Object obj;
+ while ((obj = itr.next()) != null) {
+ entries.put((String)obj, (String)htree.get(obj));
+ }
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ return entries.entrySet();
+ }
+}
Added:
trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/jdbm/JdbmDatabase.java
==============================================================================
--- (empty file)
+++ trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/jdbm/JdbmDatabase.java
Tue Sep 2 05:36:24 2008
@@ -0,0 +1,65 @@
+package org.twdata.twxbbs.db.jdbm;
+
+import org.twdata.twxbbs.db.Database;
+import org.twdata.twxbbs.db.DatabaseException;
+import jdbm.RecordManager;
+import jdbm.htree.HTree;
+
+import java.util.Map;
+import java.io.IOException;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: mrdon
+ * Date: 02/09/2008
+ * Time: 8:41:22 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class JdbmDatabase implements Database {
+ private final RecordManager recordManager;
+
+ public JdbmDatabase(RecordManager recordManager) {
+ this.recordManager = recordManager;
+ }
+
+ public Map<String, String> getMap(String name) {
+ // create or load fruit basket (hashtable of fruits)
+ HTree htree = null;
+ try {
+ long recid = recordManager.getNamedObject(name);
+ if ( recid != 0 ) {
+ htree = HTree.load(recordManager, recid );
+ } else {
+ htree = HTree.createInstance(recordManager);
+ recordManager.setNamedObject(name, htree.getRecid());
+ }
+ return new HTreeMap(htree);
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ }
+
+ public void close() {
+ try {
+ recordManager.close();
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ }
+
+ public void commit() {
+ try {
+ recordManager.commit();
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ }
+
+ public void rollback() {
+ try {
+ recordManager.rollback();
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ }
+}
Added:
trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/jdbm/JdbmDatabaseManager.java
==============================================================================
--- (empty file)
+++
trunk/twxbbs/src/main/java/org/twdata/twxbbs/db/jdbm/JdbmDatabaseManager.java
Tue Sep 2 05:36:24 2008
@@ -0,0 +1,60 @@
+package org.twdata.twxbbs.db.jdbm;
+
+import org.twdata.twxbbs.event.EventManager;
+import org.twdata.twxbbs.event.EventListener;
+import org.twdata.twxbbs.Container;
+import org.twdata.twxbbs.db.DatabaseManager;
+import org.twdata.twxbbs.db.Database;
+import org.twdata.twxbbs.db.DatabaseException;
+import org.twdata.twxbbs.config.Configuration;
+import org.twdata.twxbbs.config.ConfigurationRefreshedEvent;
+
+import java.io.IOException;
+import java.io.File;
+import java.util.Properties;
+
+import jdbm.RecordManagerFactory;
+import jdbm.RecordManager;
+import jdbm.RecordManagerOptions;
+import jdbm.htree.HTree;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: mrdon
+ * Date: 02/09/2008
+ * Time: 8:34:00 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class JdbmDatabaseManager implements DatabaseManager {
+ private File dataDir;
+ public JdbmDatabaseManager(EventManager eventManager) {
+ eventManager.register(this);
+ }
+
+ @EventListener
+ public synchronized void refresh(ConfigurationRefreshedEvent event)
throws IOException {
+ Configuration config = event.getConfiguration();
+ File baseDir = config.getBaseDir();
+ dataDir = new File(baseDir, "data");
+ if (!dataDir.exists()) {
+ dataDir.mkdirs();
+ }
+ }
+
+
+ public Database createDatabase(String name) {
+ Properties props = new Properties();
+ props.put(RecordManagerOptions.AUTO_COMMIT, "true");
+ props.put(RecordManagerOptions.THREAD_SAFE, "true");
+ return createDatabase(name, props);
+
+ }
+
+ public Database createDatabase(String name, Properties props) {
+ try {
+ return new
JdbmDatabase(RecordManagerFactory.createRecordManager( new File(dataDir,
name).getAbsolutePath(), props));
+ } catch (IOException e) {
+ throw new DatabaseException(e);
+ }
+ }
+}
Modified:
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/DefaultScriptManager.java
==============================================================================
---
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/DefaultScriptManager.java
(original)
+++
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/DefaultScriptManager.java
Tue Sep 2 05:36:24 2008
@@ -5,6 +5,7 @@
import org.twdata.twxbbs.event.EventListener;
import org.twdata.twxbbs.event.EventManager;
import org.twdata.twxbbs.Container;
+import org.twdata.twxbbs.db.DatabaseManager;
import java.io.IOException;
import java.io.File;
@@ -24,10 +25,13 @@
private File scriptsDir;
private Configuration configuration;
private final Map<String,Object> applicationContext;
+ private DatabaseManager databaseManager;
- public DefaultScriptManager(EventManager eventManager) {
+ public DefaultScriptManager(EventManager eventManager, DatabaseManager
databaseManager) {
applicationContext = Collections.synchronizedMap(new
HashMap<String,Object>());
+ this.databaseManager = databaseManager;
eventManager.register(this);
+
}
@EventListener
@@ -98,6 +102,7 @@
public Thread startScript(final Script script, final
Map<String,Object> vars) {
vars.put("application", applicationContext);
+ vars.put("dbManager", databaseManager);
Thread t = new Thread(new Runnable() {
public void run() {
script.run(vars);
Modified:
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/JavascriptScript.java
==============================================================================
---
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/JavascriptScript.java
(original)
+++
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/JavascriptScript.java
Tue Sep 2 05:36:24 2008
@@ -31,6 +31,7 @@
}
System.out.println(this+" started");
readScriptIntoEngine(jsEngine,
getClass().getClassLoader().getResource("org/twdata/twxbbs/proxy/script/global.js"));
+ readScriptIntoEngine(jsEngine,
getClass().getClassLoader().getResource("org/twdata/twxbbs/proxy/script/json2.js"));
readScriptIntoEngine(jsEngine, script);
System.out.println(this+" finished");
variables.clear();
Added:
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/LexerContext.java
==============================================================================
--- (empty file)
+++
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/LexerContext.java
Tue Sep 2 05:36:24 2008
@@ -0,0 +1,29 @@
+package org.twdata.twxbbs.proxy.script;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: mrdon
+ * Date: 02/09/2008
+ * Time: 8:01:52 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class LexerContext {
+ private boolean waiting;
+ private ScriptLexer.Match lastMatch;
+
+ public boolean isWaiting() {
+ return waiting;
+ }
+
+ public void setWaiting(boolean waiting) {
+ this.waiting = waiting;
+ }
+
+ public ScriptLexer.Match getLastMatch() {
+ return lastMatch;
+ }
+
+ public void setLastMatch(ScriptLexer.Match lastMatch) {
+ this.lastMatch = lastMatch;
+ }
+}
Modified:
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/ScriptLexer.java
==============================================================================
---
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/ScriptLexer.java
(original)
+++
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/ScriptLexer.java
Tue Sep 2 05:36:24 2008
@@ -4,8 +4,8 @@
import org.twdata.twxbbs.util.CircularFifoBuffer;
import java.io.IOException;
-import java.util.Map;
-import java.util.LinkedHashMap;
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArraySet;
/**
* Created by IntelliJ IDEA.
@@ -15,7 +15,7 @@
* To change this template use File | Settings | File Templates.
*/
public class ScriptLexer {
- private final Map<String,Trigger> activeTriggers;
+ private final Set<Trigger> activeTriggers;
// The current line
private final StringBuffer currentLine;
@@ -30,130 +30,131 @@
// Buffer for read data that may be used to create a modified write
buffer if a capturing trigger is used
private final ByteBuffer readCopyBuffer;
- // Whether we are waiting for triggers to be matched or not
- private boolean waiting;
-
- // The last match a trigger matched
- private Match lastMatch;
-
// The timeout for waiting for a match
- private long timeout = 1000 * 60;
+ private long timeout = 0;
- public ScriptLexer() {
- activeTriggers = new LinkedHashMap<String,Trigger>();
+ // Whether we are waiting for triggers to be matched or not, also used
as the synchronization object
+ private final LexerContext lexerContext;
+
+ public ScriptLexer(LexerContext lexerContext) {
+ activeTriggers = new CopyOnWriteArraySet<Trigger>();
currentLine = new StringBuffer();
backBuffer = new CircularFifoBuffer(1024);
captureBuffer = new StringBuffer();
readCopyBuffer = ByteBuffer.allocate(1024);
readCopyBuffer.setAutoExpand(true);
-
+ this.lexerContext = lexerContext;
}
- public synchronized void addTextTrigger(String id, String text) {
- if (waiting) throw new IllegalStateException("Cannot accept new
triggers while lexing text");
- activeTriggers.put(id, new DefaultTrigger(id, text, false));
+ public void addTextTrigger(String id, String text) {
+ if (lexerContext.isWaiting()) throw new
IllegalStateException("Cannot accept new triggers while lexing text");
+ activeTriggers.add(new DefaultTrigger(id, text, false));
}
- public synchronized void addTextLineTrigger(String id, String text) {
- if (waiting) throw new IllegalStateException("Cannot accept new
triggers while lexing text");
- activeTriggers.put(id, new DefaultTrigger(id, text, true));
+ public void addTextLineTrigger(String id, String text) {
+ if (lexerContext.isWaiting()) throw new
IllegalStateException("Cannot accept new triggers while lexing text");
+ activeTriggers.add(new DefaultTrigger(id, text, true));
}
- public synchronized void addCapturingTextTrigger(String id, String
text) {
- if (waiting) throw new IllegalStateException("Cannot accept new
triggers while lexing text");
+ public void addCapturingTextTrigger(String id, String text) {
+ if (lexerContext.isWaiting()) throw new
IllegalStateException("Cannot accept new triggers while lexing text");
if (text == null || text.length() == 0) {
backBuffer.clear();
}
- activeTriggers.put(id, new CapturingTrigger(id, text, false));
+ activeTriggers.add(new CapturingTrigger(id, text, false));
}
- public synchronized void addCapturingTextLineTrigger(String id, String
text) {
- if (waiting) throw new IllegalStateException("Cannot accept new
triggers while lexing text");
+ public void addCapturingTextLineTrigger(String id, String text) {
+ if (lexerContext.isWaiting()) throw new
IllegalStateException("Cannot accept new triggers while lexing text");
if (text == null || text.length() == 0) {
backBuffer.clear();
}
- activeTriggers.put(id, new CapturingTrigger(id, text, true));
+ activeTriggers.add(new CapturingTrigger(id, text, true));
}
- public synchronized void removeTextTrigger(String id) {
- if (waiting) throw new IllegalStateException("Cannot remove
triggers while lexing text");
- activeTriggers.remove(id);
+ public void removeTextTrigger(String id) {
+ if (lexerContext.isWaiting()) throw new
IllegalStateException("Cannot remove triggers while lexing text");
+ activeTriggers.remove(new DefaultTrigger(id, "", false));
}
public String getCurrentLine() {
return currentLine.toString();
}
- public synchronized Match waitForTriggers() throws IOException,
InterruptedException {
- try {
- waiting = true;
- lastMatch = null;
- if (backBuffer.hasRemaining()) {
- parse(backBuffer);
- }
- if (lastMatch == null) {
- if (timeout > 0) {
- wait(timeout);
- } else {
- wait();
+ public Match waitForTriggers() throws IOException,
InterruptedException {
+ synchronized (lexerContext) {
+ try {
+ lexerContext.setWaiting(true);
+ lexerContext.setLastMatch(null);
+ if (backBuffer.hasRemaining()) {
+ parse(backBuffer);
}
+ if (lexerContext.getLastMatch() == null) {
+ if (timeout > 0) {
+ lexerContext.wait(timeout);
+ } else {
+ lexerContext.wait();
+ }
+ }
+ } catch (InterruptedException ex) {
+ System.out.println("Stopping script");
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ } finally {
+ lexerContext.setWaiting(false);
}
- } catch (InterruptedException ex) {
- System.out.println("Stopping script");
- } catch (Exception ex) {
- ex.printStackTrace();
- } finally {
- waiting = false;
- }
- return lastMatch;
- }
-
- public synchronized ByteBuffer parse(ByteBuffer buffer) throws
IOException {
- readCopyBuffer.clear();
-
- int bytesRead = 0;
- while (buffer.hasRemaining()) {
- if (!waiting && buffer == backBuffer) {
- return buffer;
- }
- byte b = buffer.get();
- bytesRead++;
+ return lexerContext.getLastMatch();
+ }
+ }
- if (!waiting) {
- backBuffer.put(b);
- readCopyBuffer.put(b);
- captureBuffer.setLength(0);
- } else {
- char c = (char) b;
- boolean capturing = false;
- putInCurrentLine(c);
- for (Trigger trigger : activeTriggers.values()) {
+ public ByteBuffer parse(ByteBuffer buffer) throws IOException {
+ synchronized (lexerContext) {
+ readCopyBuffer.clear();
- if (trigger instanceof CapturingTrigger &&
trigger.potentialMatch(c)) {
- capturing = true;
- }
+ int bytesRead = 0;
+ while (buffer.hasRemaining()) {
+ if (!lexerContext.isWaiting() && buffer == backBuffer) {
+ return buffer;
}
+ byte b = buffer.get();
+ bytesRead++;
- if (capturing) {
- captureBuffer.append(c);
+ if (!lexerContext.isWaiting()) {
+ backBuffer.put(b);
+ readCopyBuffer.put(b);
+ captureBuffer.setLength(0);
} else {
- for (int x=0; x<captureBuffer.length(); x++) {
- readCopyBuffer.putChar(captureBuffer.charAt(x));
+ char c = (char) b;
+ boolean capturing = false;
+ putInCurrentLine(c);
+ for (Trigger trigger : activeTriggers) {
+
+ if (trigger instanceof CapturingTrigger &&
trigger.potentialMatch(c)) {
+ capturing = true;
+ }
}
- captureBuffer.setLength(0);
- readCopyBuffer.put(b);
- }
+ if (capturing) {
+ captureBuffer.append(c);
+ } else {
+ for (int x=0; x<captureBuffer.length(); x++) {
+
readCopyBuffer.putChar(captureBuffer.charAt(x));
+ }
+ captureBuffer.setLength(0);
+ readCopyBuffer.put(b);
+
+ }
- for (Trigger trigger : activeTriggers.values()) {
- if (trigger.match(c)) {
- handleMatch(trigger);
+ for (Trigger trigger : activeTriggers) {
+ if (trigger.match(c)) {
+ handleMatch(trigger);
+ }
}
}
}
- }
- return createResultingBuffer(buffer, bytesRead);
+ return createResultingBuffer(buffer, bytesRead);
+ }
}
private ByteBuffer createResultingBuffer(ByteBuffer buffer, int
bytesRead) {
@@ -199,11 +200,11 @@
}
Match match = new Match(trigger.getId(), matchedText);
if (trigger.shouldBeRemovedAfterMatch()) {
- activeTriggers.remove(trigger.getId());
+ activeTriggers.remove(trigger);
}
- waiting = false;
- lastMatch = match;
- notifyAll();
+ lexerContext.setWaiting(false);
+ lexerContext.setLastMatch(match);
+ lexerContext.notifyAll();
}
public void setTimeout(long timeout) {
@@ -288,6 +289,19 @@
public boolean shouldBeRemovedAfterMatch() {
return false;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DefaultTrigger)) return false;
+
+ DefaultTrigger that = (DefaultTrigger) o;
+
+ return id.equals(that.id);
+ }
+
+ public int hashCode() {
+ return id.hashCode();
}
}
Modified:
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/SessionScriptRunner.java
==============================================================================
---
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/SessionScriptRunner.java
(original)
+++
trunk/twxbbs/src/main/java/org/twdata/twxbbs/proxy/script/SessionScriptRunner.java
Tue Sep 2 05:36:24 2008
@@ -6,6 +6,7 @@
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created by IntelliJ IDEA.
@@ -44,13 +45,14 @@
super.clear();
}
};
- ScriptLexer gameLexer = new ScriptLexer();
+ LexerContext lexerContext = new LexerContext();
+ ScriptLexer gameLexer = new ScriptLexer(lexerContext);
ScriptApi gameApi = new ScriptApiImpl(gameLexer, new
ScriptApiImpl.TextSender() {
public void send(String text) throws Exception {
nextFilter.messageReceived(ioSession,
ByteBuffer.wrap(text.getBytes()));
}
});
- ScriptLexer playerLexer = new ScriptLexer();
+ ScriptLexer playerLexer = new ScriptLexer(lexerContext);
ScriptApi playerApi = new ScriptApiImpl(playerLexer, new
ScriptApiImpl.TextSender() {
public void send(String text) throws Exception {
nextFilter.filterWrite(ioSession, new
IoFilter.WriteRequest(ByteBuffer.wrap(text.getBytes())));
Modified:
trunk/twxbbs/src/main/resources/org/twdata/twxbbs/proxy/script/global.js
==============================================================================
---
trunk/twxbbs/src/main/resources/org/twdata/twxbbs/proxy/script/global.js
(original)
+++
trunk/twxbbs/src/main/resources/org/twdata/twxbbs/proxy/script/global.js
Tue Sep 2 05:36:24 2008
@@ -1,19 +1,12 @@
+var triggerCallbacks = {};
function Api(lexer) {
- var triggerCallbacks = {};
-
this.send = function(txt) {
lexer.send(txt);
};
this.getCurrentLine = function() {
lexer.getCurrentLine();
};
- this.pause = function() {
- var id = lexer.pause();
- if (id != null) {
- triggerCallbacks[id](lexer.stripAnsi(lexer.getMatchedLine()));
- }
- }
this.killTextTrigger = function(id) {
lexer.killTextTrigger(id);
}
@@ -37,6 +30,13 @@
var game = new Api(gameApi);
var player = new Api(playerApi);
+
+function pause() {
+ var id = gameApi.pause();
+ if (id != null) {
+ triggerCallbacks[id](stripAnsi(gameApi.getMatchedLine()));
+ }
+}
function stripAnsi(txt) {
return new String(gameApi.stripAnsi(txt));
Added:
trunk/twxbbs/src/main/resources/org/twdata/twxbbs/proxy/script/json2.js
==============================================================================
--- (empty file)
+++ trunk/twxbbs/src/main/resources/org/twdata/twxbbs/proxy/script/json2.js
Tue Sep 2 05:36:24 2008
@@ -0,0 +1,481 @@
+/*
+ http://www.JSON.org/json2.js
+ 2008-09-01
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+ This file creates a global JSON object containing two methods:
stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array.
+
+ space an optional parameter that specifies the
indentation
+ of nested structures. If it is omitted, the text
will
+ be packed without extra whitespace. If it is a
number,
+ it will specify the number of spaces to indent at
each
+ level. If it is a string (such as '\t'
or ' '),
+ it contains the characters used to indent at each
level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be
serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this
will be
+ bound to the object holding the key.
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed
the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member
will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array, then it will be used to
+ select the members to be serialized. It filters the results
such
+ that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as
undefined or
+ functions, will not be serialized. Such values in objects will
be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make
it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string
will
+ be used for indentation. If the space parameter is a number,
then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter
and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not
modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3],
+a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key,
value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS
YOU DO
+ NOT CONTROL.
+*/
+
+/*jslint evil: true */
+
+/*global JSON */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", call,
+ charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes,
+ getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length,
+ parse, propertyIsEnumerable, prototype, push, replace, slice,
stringify,
+ test, toJSON, toString, valueOf
+*/
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+ JSON = {};
+}
+(function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx =
/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapeable =
/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and
no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapeable.lastIndex = 0;
+ return escapeable.test(string) ?
+ '"' + string.replace(escapeable, function (a) {
+ var c = meta[a];
+ if (typeof c === 'string') {
+ return c;
+ }
+ return '\\u' + ('0000' +
a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array
or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object
value.
+
+ gap += indent;
+ partial = [];
+
+// If the object has a dontEnum length property, we'll treat it as an
array.
+
+ if (typeof value.length === 'number' &&
+ !value.propertyIsEnumerable('length')) {
+
+// The object is an array. Stringify every element. Use null as a
placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them
in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be
stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') +
v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') +
v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an
optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the
keys.
+// A default replacer method can be provided. Use of the space parameter
can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing
that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent
string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and
returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many
characters
+// incorrectly, either silently deleting them, or treating them as line
endings.
+
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that
look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause
mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work
around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character).
Second, we
+// replace all simple value tokens with ']' characters. Third, we delete
all
+// open brackets that follow a colon or comma or that begin the text.
Finally,
+// we look to see that the remaining characters are only whitespace or ']'
or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|
-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic
ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the
text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure,
passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+})();
Added:
trunk/twxbbs/src/test/java/org/twdata/twxbbs/db/DatabaseManagerTest.java
==============================================================================
--- (empty file)
+++
trunk/twxbbs/src/test/java/org/twdata/twxbbs/db/DatabaseManagerTest.java
Tue Sep 2 05:36:24 2008
@@ -0,0 +1,49 @@
+package org.twdata.twxbbs.db;
+
+import junit.framework.TestCase;
+import org.twdata.twxbbs.db.jdbm.JdbmDatabaseManager;
+import org.twdata.twxbbs.event.EventManager;
+import org.twdata.twxbbs.event.impl.DefaultEventManager;
+import org.twdata.twxbbs.config.ConfigurationRefreshedEvent;
+import org.twdata.twxbbs.config.Configuration;
+import com.mockobjects.dynamic.Mock;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: mrdon
+ * Date: 02/09/2008
+ * Time: 9:02:47 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class DatabaseManagerTest extends TestCase {
+
+ public void testCreateAndPopulate() throws IOException {
+ EventManager eventManager = new DefaultEventManager();
+ DatabaseManager mgr = new JdbmDatabaseManager(eventManager);
+ Mock mockConfiguration = new Mock(Configuration.class);
+ File tmpDir = new File(System.getProperty("java.io.tmpdir"));
+ mockConfiguration.expectAndReturn("getBaseDir", tmpDir);
+ eventManager.broadcast(new
ConfigurationRefreshedEvent((Configuration) mockConfiguration.proxy()));
+
+ Database db = mgr.createDatabase("test");
+ File dbFile = new File(new File(tmpDir, "data"), "test.db");
+ try {
+ Map<String,String> sectors = db.getMap("sectors");
+ sectors.put("foo", "bar");
+ assertEquals("bar", sectors.get("foo"));
+ assertTrue(sectors.containsKey("foo"));
+ assertTrue(sectors.containsValue("bar"));
+ assertEquals(1, sectors.size());
+ assertTrue(dbFile.exists());
+ } finally {
+ dbFile.delete();
+ }
+
+ mockConfiguration.verify();
+
+ }
+}
Modified:
trunk/twxbbs/src/test/java/org/twdata/twxbbs/proxy/script/ScriptLexerTest.java
==============================================================================
---
trunk/twxbbs/src/test/java/org/twdata/twxbbs/proxy/script/ScriptLexerTest.java
(original)
+++
trunk/twxbbs/src/test/java/org/twdata/twxbbs/proxy/script/ScriptLexerTest.java
Tue Sep 2 05:36:24 2008
@@ -3,6 +3,7 @@
import junit.framework.TestCase;
import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.mina.common.ByteBuffer;
import org.apache.commons.io.output.ByteArrayOutputStream;
@@ -17,7 +18,7 @@
public class ScriptLexerTest extends TestCase {
public void testSimple() throws IOException, InterruptedException {
- ScriptLexer lexer = new ScriptLexer();
+ ScriptLexer lexer = new ScriptLexer(new LexerContext());
lexer.addTextTrigger("foo", "bob");
lexer.parse(ByteBuffer.wrap("This guy bob is great".getBytes()));
ScriptLexer.Match match = lexer.waitForTriggers();
@@ -27,7 +28,7 @@
}
public void testSimpleLine() throws IOException, InterruptedException {
- ScriptLexer lexer = new ScriptLexer();
+ ScriptLexer lexer = new ScriptLexer(new LexerContext());
lexer.addTextLineTrigger("foo", "bob");
lexer.parse(ByteBuffer.wrap("This guy bob is
great\r\n".getBytes()));
ScriptLexer.Match match = lexer.waitForTriggers();
@@ -37,7 +38,7 @@
}
public void testSimpleMultiThread() throws IOException,
InterruptedException {
- final ScriptLexer lexer = new ScriptLexer();
+ final ScriptLexer lexer = new ScriptLexer(new LexerContext());
lexer.addTextTrigger("foo", "bob");
lexer.setTimeout(2000);
Thread t = new Thread(new Runnable() {
@@ -64,7 +65,7 @@
}
public void testCapturingTrigger() throws IOException,
InterruptedException {
- final ScriptLexer lexer = new ScriptLexer();
+ final ScriptLexer lexer = new ScriptLexer(new LexerContext());
lexer.addCapturingTextTrigger("foo", "bob");
lexer.setTimeout(5000);
Thread t = new Thread(new Runnable() {
@@ -88,7 +89,7 @@
}
public void testCapturingTriggerTwice() throws IOException,
InterruptedException {
- final ScriptLexer lexer = new ScriptLexer();
+ final ScriptLexer lexer = new ScriptLexer(new LexerContext());
lexer.addCapturingTextTrigger("foo", "bob");
lexer.setTimeout(5000);
sendTextOnOtherThread(lexer, "This guy bob is great\r\n", "This
guy is great\r\n");
@@ -122,7 +123,7 @@
}
public void testCapturingTriggerThatMatchesAnything() throws
IOException, InterruptedException {
- final ScriptLexer lexer = new ScriptLexer();
+ final ScriptLexer lexer = new ScriptLexer(new LexerContext());
lexer.addCapturingTextTrigger("foo", "");
lexer.setTimeout(5000);
sendTextOnOtherThread(lexer, "This guy bob is great\r\n", "his guy
bob is great\r\n");
@@ -133,7 +134,7 @@
}
public void testCapturingTriggerThatMatchesAnythingTwice() throws
IOException, InterruptedException {
- final ScriptLexer lexer = new ScriptLexer();
+ final ScriptLexer lexer = new ScriptLexer(new LexerContext());
lexer.addCapturingTextTrigger("foo", "");
lexer.setTimeout(5000);
sendTextOnOtherThread(lexer, "This guy bob is great\r\n", "his guy
bob is great\r\n");
@@ -150,7 +151,7 @@
}
public void testCapturingTriggerThatMatchesAnythingButNotBackBuffer()
throws IOException, InterruptedException {
- final ScriptLexer lexer = new ScriptLexer();
+ final ScriptLexer lexer = new ScriptLexer(new LexerContext());
lexer.parse(ByteBuffer.wrap("some precursor text".getBytes()));
lexer.addCapturingTextTrigger("foo", "");
lexer.setTimeout(5000);
@@ -175,7 +176,7 @@
}
public void testCapturingTriggerForLine() throws IOException,
InterruptedException {
- final ScriptLexer lexer = new ScriptLexer();
+ final ScriptLexer lexer = new ScriptLexer(new LexerContext());
lexer.addCapturingTextLineTrigger("foo", "bob");
lexer.setTimeout(5000);
Thread t = new Thread(new Runnable() {
@@ -199,7 +200,7 @@
}
public void testCapturingTriggerForLineThatMatchesAnything() throws
IOException, InterruptedException {
- final ScriptLexer lexer = new ScriptLexer();
+ final ScriptLexer lexer = new ScriptLexer(new LexerContext());
lexer.addCapturingTextLineTrigger("foo", "");
lexer.setTimeout(5000);
Thread t = new Thread(new Runnable() {
@@ -223,7 +224,7 @@
}
public void testCapturingTriggerSpreadOverMultipleCalls() throws
IOException, InterruptedException {
- final ScriptLexer lexer = new ScriptLexer();
+ final ScriptLexer lexer = new ScriptLexer(new LexerContext());
lexer.addCapturingTextLineTrigger("foo", "bob");
lexer.setTimeout(5000);
Thread t = new Thread(new Runnable() {