2010-01-27

Simplificando

Ayer me di cuenta que no necesito rehacer los classloaders de java para cargar scripts de JavaScript, ya que puedo usarlos y son todo lo flexibles que necesito (y más). Inclusive, si por una de esas casualidades llegara a hace falta, creo que puedo hacer uno en rhino y todo. Por lo tanto decidí que mi lógica para cargar módulos tenía una indirección de más (la lista de loaders) y lo cambié para que use el classloader y listo. Fíjense en la nueva implementación de loadMod y como se simplificó el uso de los loaders.
En la versión del código que estoy usando ahora, también hice que los módulos se carguen un toque distintos para que haya mejores stack-traces (ese cambio está en la función require). Bueno, abajo pongo el código:


/*
* Does not support relative paths (yet?).
* Can be invoked by several threads.
*
* Uses the classloader to fetch js modules.
*/
var require = function (id) {

try {

require.lock.lock() // Avoid threading issues while loading modules.

if (!require.modules[id]) {

var modCode = require.loadMod(id)

var exports = {}
require.modules[id] = exports
var module = {
id : id
}

var context = {
exports: exports,
module: module,
}

if(!modCode) {
throw new require.RequireError(id, "module not found")
}

org.mozilla.javascript.Context.getCurrentContext().evaluateString(
context,
modCode,
id,
1,
null
)
/*
Old style invocation, should work in the browser but has worst error messages.

var f = new Function("require", "exports", "module", modCode)
f.call(context, require, exports, module) */
}

return require.modules[id]
} catch (x) {
if (x instanceof require.RequireError) {
throw x
} else {
throw new require.RequireError(id, x)
}
} finally {
require.lock.unlock()
}
}

require.loaders = []
require.modules = {}
require.lock = new java.util.concurrent.locks.ReentrantLock()

var RequireErrorProto = {}

require.RequireError = function(moduleId, cause) {
this.moduleId = moduleId
this.cause = cause

}
require.RequireError.prototype = RequireErrorProto
require.RequireError.prototype.toString = function() {
return "Error loading " + this.moduleId + ( this.cause ? ". Cause: " + this.cause : "" )
}
require.RequireError.prototype.name = "RequireError"

require.load = function(path) {
return java.lang.Class.forName("java.lang.String").getResourceAsStream(path)
}

require.loadMod = function(id) {
function path(id) {
return "/" + id + ".js"
}

function readFromStream(stream) {
var io = java.io
var reader = new io.BufferedReader( new io.InputStreamReader(stream) )
try {
var stringBuffer = new java.lang.StringBuffer()

var line = ""
while( line = reader.readLine()) {
stringBuffer.append(line)
stringBuffer.append("\n")
}

return stringBuffer.toString()
} finally {
reader.close()
}
}

var stream = require.load(path(id))
if (!stream) { return null }

return readFromStream(stream)
}

/**
* Resets the module cache.
*
* It will reload all the modules. Old modules referenced will still be working.
*/
require.reset = function() {
try {
require.lock.lock()
require.modules = {}
} finally {
require.lock.unlock()
}

}

Este cambio también simplifico el RhinoServlet, y ahora toma sus scripts del classpath también.

Happy hacking,
Aureliano

2010-01-25

Parseando CSVs con una expresión de JavaScript

Para parsear un CSV con datos numéricos hay que hacer esto:


text.split("\n").map( function(r){
return r.split(",").map( function(d) {
return parseFloat(d)
} )
} )

Esto solo anda si tu intérprete de JavaScript tiene Array.prototype.map definido. Sino antes de eso tenés que definirlo así:

Array.prototype.map = Array.prototype.map || function( f ) {
var r = []
for (var i=0; i<this.length; i++) {
r[i] = f(this[i])
}
return r
}

2010-01-18

2010-01-17

Generando xhtml con e4x

Primero hay que setear el namespace por default:

default xml namespace = "http://www.w3.org/1999/xhtml"


Para generar elementos, uso esta función:
function elem(name) { return <{name} /> }


Para setear un attributo cualquiera esta función:
function attr(elem, key, value) { elem["@"+key] = value }


Y puedo usar elem.appendChild para agregar como hijo de un elemento otro o un texto como string que es escapado.

Happy hacking,
Aureliano.

2010-01-12

Entrada de BibTeX en Google Scholar

Si estás logueado en Google, podés setear Google Scholar para que te muestre las entradas de bibtex.
Para eso tenés que ir a Scholar Preferences -> Bibliography Manager y elegir Show links to import citations into BibTeX.

Happy hacking,
Aureliano

(Sacado de acá)

2010-01-10

Implementación de require en Rhino

Hoy estuve hackeando un toque y modifiqué el RhinoServlet que les mostré en este post para poder tener módulos en Javascript. El API que hice es un subset de lo definido en CommonJS. En particular, soporta require pero no los paths relativos de módulos (o sea, los que empiezan con . o ..). También le puse a todo un lock para que pueda usarse en un entorno concurrente, pero si un módulo tarda mucho en cargarse puede llegar a ser un problema.
Lo que me parece más interesante de mi prototipo es que se pueden agregar fácilmente nuevos loaders de módulos de JavasScript. Ya vienen por defecto loaders para cargar desde el classpath y desde la webapp, pero si quieren poder poner sus módulos en /WEB-INF/example, alcanza con agregar una línea en init.js:

require.loaders.push( require.stdLoader(servlet.servletContext, "/WEB-INF/example/"))

Bueno, basta de cháchara, acá está la versión modificada del RhinoServlet:
package aure.jslib;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

public class RhinoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private Scriptable initialScope = null;

public void runJs(Context cx, String scriptLocation, Scriptable scope) throws IOException {

InputStream is = this.getClass().getResourceAsStream(scriptLocation);
if( is == null) {
is = this.getServletContext().getResourceAsStream("/WEB-INF/js/" + scriptLocation);
}
Reader jsReader = new InputStreamReader(is);

cx.evaluateReader(scope, jsReader, scriptLocation, 1, null);
}

public static void addJavaObjectToScope(Scriptable scope, String name, Object obj) {
ScriptableObject.putProperty(scope, name, Context.javaToJS(obj, scope) );
}

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
Context cx = Context.enter();
try {
Scriptable scope = cx.initStandardObjects();
addJavaObjectToScope(scope, "servlet", this);
addJavaObjectToScope(scope, "config", config);

this.runJs(cx, "initRequire.js", scope);
this.runJs(cx, "init.js", scope);

this.initialScope = scope;
} catch (IOException e) {
throw new ServletException(e);
} finally {
Context.exit();
}
}

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Context cx = Context.enter();
try {
Scriptable scope = cx.initStandardObjects();
scope.setParentScope(this.initialScope);

addJavaObjectToScope(scope, "request", request);
addJavaObjectToScope(scope, "response", response);

this.runJs(cx, "start.js", scope);
} finally {
Context.exit();
}
}
}

Noten que toqué el init para que agregue el require y cambié la forma de levantar javascript. Por otro lado este es el javascript en initRequire.js, que tiene la implementación del require:

/* Based on http://www.davidflanagan.com/demos/require.js
* but heavily modified.
*
* Does not support relative paths (yet?).
* Can be invoked by several threads.
*
* Uses the servletContext and the classloader to fetch js modules.
*/
var logger = java.util.logging.Logger.getLogger("sarasa")

var require = function (id) {

try {

require.lock.lock() // Avoid threading issues while loading modules.

if (!require.modules[id]) {

var modText = require.loadMod(id)

var context = {}
var exports = {}
require.modules[id] = exports
var module = {
id : id
}

var f = new Function("require", "exports", "module", modText)
f.call(context, require, exports, module)
}

return require.modules[id]
} catch (x) {
throw new Error("Can't load module: " + id + ": " + x)
} finally {
require.lock.unlock()
}
}

require.loaders = []
require.modules = {}
require.lock = new java.util.concurrent.locks.ReentrantLock()
require.loadMod = function(id) {
var modText = null
var i = 0
while (modText == null && i < require.loaders.length) {
modText = require.loaders[i](id)
i++
}
return modText
}

// Setup loaders for files in the classpath, files in WEB-INF and files in the
// web-app (usually served to the client)
require.stdLoader = function(source, prepend) {
// Support functions
function readFromStream(stream) {
var io = java.io
var reader = new io.BufferedReader( new io.InputStreamReader(stream) )
var stringBuffer = new java.lang.StringBuffer()

var line = ""
while( line = reader.readLine()) {
stringBuffer.append(line)
stringBuffer.append("\n")
}

return stringBuffer.toString()
}

function path(id) {
return prepend + id + ".js"
}

var f = function(id) {
var stream = source.getResourceAsStream(path(id))
return stream ? readFromStream(stream) : null
}

return f

}

require.initStdLoaders = function() {
// classpath loader
require.loaders.push( this.stdLoader(servlet["class"], "/") )

// web-app context loader
require.loaders.push( this.stdLoader(servlet.servletContext, "/") )
}

require.initStdLoaders()

Empecé a hacer esto mirando el código en http://www.davidflanagan.com/demos/require.js pero al final quedó bastante distinto, sobre todo porque tiene la opción de tener muchos loaders.
Las cosas que me quedan para hacer son hacer que acepte paths relativos (que para que sea thread-safe hace falta que haga algunos truquitos) y hacer que en vez de usar
   var f = new Function("require", "exports", "module", modText)
f.call(context, require, exports, module)

para invocar el módulo haga algo usando el API de rhino para poder tener errores donde aparezca el nombre del módulo y el número de línea si hubo algún problema.

Escucho comentarios y sugerencias.

Happy hacking,
Aureliano.

Eclipse plug-in para app-engine

¿Por qué te negás a usar dependencias de otros proyectos de eclipse?
¿Por qué no recompilás bien cuando está corriendo el web-server en modo debug y uso resources de adentro del classpath?

Bueno, por ahora son 2 problemas que creo poder "workarroundear".

Happy hacking,
Aureliano.

2010-01-08

2 placas y default gateway

Hoy estuve configurando una VM con ubuntu que tiene una placa en modo bridged por DHCP y otra host-only estática. El problema que tuve fue que no encontraba la forma que la ruta por default sea la ruta por default que informa el dhcp. La solución que encontré fue hacer que la métrica de la ruta por default de la ip estática sea más grande.

Este es el archivo /etc/network/interfaces que terminé usando:

auto lo eth1 eth0
iface lo inet loopback

iface eth0 inet dhcp

iface eth1 inet static
address 172.17.17.16
netmask 255.255.255.0
network 172.17.17.0
broadcast 172.17.17.255
gateway 172.17.17.1
metric 200

Y la posta está en la última línea.

Probé sacar la entrada de gateway y agregar una entrada borrando la ruta que me sobraba (up route del default gw 172.17.17.1) y no funcionó.

Se les ocurre alguna otra forma de arreglar esto?

Happy hacking,
Aureliano.

2010-01-05

2 servlets para correr javascript

Estuve con algo de tiempo y me puse a probar como hacer para ejecutar JavaScript en el server. E hice 2 implementaciones (o sea, 2 servlets programados en Java). La primera que hice usa javax.script (que viene por defecto en Java 6) y me parece que el API es más cómoda pero tiene 2 limitaciones que no me gustaron, no se puede heredar de clases Java en JavaScript y no encontré cómo hacer para bindear un método de Java a JavaScript. Este es el código:

package aure;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class JsServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

private ScriptEngine jsEng = new ScriptEngineManager()
.getEngineByName("js");
private Bindings initBindings = this.jsEng
.getBindings(ScriptContext.ENGINE_SCOPE);

public JsServlet() {
super();
}

@Override
public void init(ServletConfig config) throws ServletException {
try {
super.init(config);

this.initBindings.put("config", config);
this.initBindings.put("servlet", this);

this.runJs("init.js", this.initBindings);
} catch (ScriptException e) {
throw new ServletException("Error running init.js", e);
}
}

public void runJs(String scriptLocation, Bindings bindings)
throws ScriptException {
Reader jsReader = new InputStreamReader(this.getServletContext()
.getResourceAsStream(scriptLocation));

this.jsEng.eval(jsReader, bindings);
}

protected void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {

try {
Bindings bindings = jsEng.createBindings();
bindings.putAll(this.initBindings);
bindings.put("request", request);
bindings.put("response", response);

this.runJs("start.js", bindings);
} catch (ScriptException e) {
throw new ServletException("Error running javascript", e);
}
}

}

La segunda usa rhino bajado desde mozilla y por lo tanto no tiene esas limitaciones, pero el API para embeber JavaScript en Java es más fea. Acá está el código de la segunda versión:
package aure;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

public class RhinoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private Scriptable initialScope = null;

public void runJs(Context cx, String scriptLocation, Scriptable scope) throws IOException {
Reader jsReader = new InputStreamReader(this.getServletContext()
.getResourceAsStream("/WEB-INF/js" + scriptLocation));

cx.evaluateReader(scope, jsReader, scriptLocation, 1, null);
}

public static void addJavaObjectToScope(Scriptable scope, String name, Object obj) {
ScriptableObject.putProperty(scope, name, Context.javaToJS(obj, scope) );
}

@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
Context cx = Context.enter();
try {
Scriptable scope = cx.initStandardObjects();
addJavaObjectToScope(scope, "servlet", this);
addJavaObjectToScope(scope, "config", config);

this.runJs(cx, "/init.js", scope);

this.initialScope = scope;
} catch (IOException e) {
throw new ServletException(e);
} finally {
Context.exit();
}
}

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Context cx = Context.enter();
try {
Scriptable scope = cx.initStandardObjects();
scope.setParentScope(this.initialScope);

addJavaObjectToScope(scope, "request", request);
addJavaObjectToScope(scope, "response", response);

this.runJs(cx, "/start.js", scope);
} finally {
Context.exit();
}
}
}

Ambas versiones ejecutan WEB-INF/js/init.js cuando se carga el servlet (tiene bindeados el servlet y la configuración del servlet) y WEB-INF/js/start.js cuando se atiende un pedido (agrega a los bindings lo que se haya puesto en init.js, el servlet, la configuración, el request y el response.

La idea de todo esto es ponerme las pilas e implementar las cosas que dije en este post hace casi 3 años, ya que ahora parece que se está poniendo de moda usar JavaScript.

Espero que les parezca interesante, me voy a dormir.

Happy hacking,
Aureliano.