diff --git a/AppKit/Platform/DOM/CPDOMWindowBridge.j b/AppKit/Platform/DOM/CPDOMWindowBridge.j
index ad5a3e51c..2d201690f 100644
--- a/AppKit/Platform/DOM/CPDOMWindowBridge.j
+++ b/AppKit/Platform/DOM/CPDOMWindowBridge.j
@@ -861,7 +861,7 @@ var CTRL_KEY_CODE = 17;
@end
var CLICK_SPACE_DELTA = 5.0,
- CLICK_TIME_DELTA = document.addEventListener ? 350.0 : 1000.0;
+ CLICK_TIME_DELTA = (typeof document != "undefined" && document.addEventListener) ? 350.0 : 1000.0;
var CPDOMEventGetClickCount = function(aComparisonEvent, aTimestamp, aLocation)
{
diff --git a/Tools/Utilities/js.jar b/Tools/Utilities/js.jar
index 2b926505d..2f4fad90c 100644
Binary files a/Tools/Utilities/js.jar and b/Tools/Utilities/js.jar differ
diff --git a/Tools/press/env.js b/Tools/press/env.js
new file mode 100644
index 000000000..0466201b4
--- /dev/null
+++ b/Tools/press/env.js
@@ -0,0 +1,767 @@
+/*
+ * Pure JavaScript Browser Environment
+ * By John Resig
+ * Copyright 2008 John Resig, under the MIT License
+ */
+
+// The window Object
+var window = this;
+
+(function(){
+
+ // Browser Navigator
+
+ window.navigator = {
+ get userAgent(){
+ return "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3";
+ }
+ };
+
+ var curLocation = (new java.io.File("./")).toURL();
+
+ window.__defineSetter__("location", function(url){
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url);
+ xhr.onreadystatechange = function(){
+ curLocation = new java.net.URL( curLocation, url );
+ window.document = xhr.responseXML;
+
+ var event = document.createEvent();
+ event.initEvent("load");
+ window.dispatchEvent( event );
+ };
+ xhr.send();
+ });
+
+ window.__defineGetter__("location", function(url){
+ return {
+ get protocol(){
+ return curLocation.getProtocol() + ":";
+ },
+ get href(){
+ return curLocation.toString();
+ },
+ toString: function(){
+ return this.href;
+ }
+ };
+ });
+
+ // Timers
+
+ var timers = [];
+
+ window.setTimeout = function(fn, time){
+ var num;
+ return num = setInterval(function(){
+ fn();
+ clearInterval(num);
+ }, time);
+ };
+
+ window.setInterval = function(fn, time){
+ var num = timers.length;
+
+ timers[num] = new java.lang.Thread(new java.lang.Runnable({
+ run: function(){
+ while (true){
+ java.lang.Thread.currentThread().sleep(time);
+ fn();
+ }
+ }
+ }));
+
+ timers[num].start();
+
+ return num;
+ };
+
+ window.clearInterval = function(num){
+ if ( timers[num] ) {
+ timers[num].stop();
+ delete timers[num];
+ }
+ };
+
+ // Window Events
+
+ var events = [{}];
+
+ window.addEventListener = function(type, fn){
+ if ( !this.uuid || this == window ) {
+ this.uuid = events.length;
+ events[this.uuid] = {};
+ }
+
+ if ( !events[this.uuid][type] )
+ events[this.uuid][type] = [];
+
+ if ( events[this.uuid][type].indexOf( fn ) < 0 )
+ events[this.uuid][type].push( fn );
+ };
+
+ window.removeEventListener = function(type, fn){
+ if ( !this.uuid || this == window ) {
+ this.uuid = events.length;
+ events[this.uuid] = {};
+ }
+
+ if ( !events[this.uuid][type] )
+ events[this.uuid][type] = [];
+
+ events[this.uuid][type] =
+ events[this.uuid][type].filter(function(f){
+ return f != fn;
+ });
+ };
+
+ window.dispatchEvent = function(event){
+ if ( event.type ) {
+ if ( this.uuid && events[this.uuid][event.type] ) {
+ var self = this;
+
+ events[this.uuid][event.type].forEach(function(fn){
+ fn.call( self, event );
+ });
+ }
+
+ if ( this["on" + event.type] )
+ this["on" + event.type].call( self, event );
+ }
+ };
+
+ // DOM Document
+
+ window.DOMDocument = function(file){
+ this._file = file;
+ this._dom = Packages.javax.xml.parsers.
+ DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder().parse(file);
+
+ if ( !obj_nodes.containsKey( this._dom ) )
+ obj_nodes.put( this._dom, this );
+ };
+
+ DOMDocument.prototype = {
+ get nodeType(){
+ return 9;
+ },
+ createTextNode: function(text){
+ return makeNode( this._dom.createTextNode(
+ text.replace(/&/g, "&").replace(//g, ">")) );
+ },
+ createElement: function(name){
+ return makeNode( this._dom.createElement(name.toLowerCase()) );
+ },
+ getElementsByTagName: function(name){
+ return new DOMNodeList( this._dom.getElementsByTagName(
+ name.toLowerCase()) );
+ },
+ getElementsByName: function(name){
+ var elems = this._dom.getElementsByTagName("*"), ret = [];
+ ret.item = function(i){ return this[i]; };
+ ret.getLength = function(){ return this.length; };
+
+ for ( var i = 0; i < elems.length; i++ ) {
+ var elem = elems.item(i);
+ if ( elem.getAttribute("name") == name )
+ ret.push( elem );
+ }
+
+ return new DOMNodeList( ret );
+ },
+ getElementById: function(id){
+ var elems = this._dom.getElementsByTagName("*");
+
+ for ( var i = 0; i < elems.length; i++ ) {
+ var elem = elems.item(i);
+ if ( elem.getAttribute("id") == id )
+ return makeNode(elem);
+ }
+
+ return null;
+ },
+ get body(){
+ return this.getElementsByTagName("body")[0];
+ },
+ get documentElement(){
+ return makeNode( this._dom.getDocumentElement() );
+ },
+ get ownerDocument(){
+ return null;
+ },
+ addEventListener: window.addEventListener,
+ removeEventListener: window.removeEventListener,
+ dispatchEvent: window.dispatchEvent,
+ get nodeName() {
+ return "#document";
+ },
+ importNode: function(node, deep){
+ return makeNode( this._dom.importNode(node._dom, deep) );
+ },
+ toString: function(){
+ return "Document" + (typeof this._file == "string" ?
+ ": " + this._file : "");
+ },
+ get innerHTML(){
+ return this.documentElement.outerHTML;
+ },
+
+ get defaultView(){
+ return {
+ getComputedStyle: function(elem){
+ return {
+ getPropertyValue: function(prop){
+ prop = prop.replace(/\-(\w)/g,function(m,c){
+ return c.toUpperCase();
+ });
+ var val = elem.style[prop];
+
+ if ( prop == "opacity" && val == "" )
+ val = "1";
+
+ return val;
+ }
+ };
+ }
+ };
+ },
+
+ createEvent: function(){
+ return {
+ type: "",
+ initEvent: function(type){
+ this.type = type;
+ }
+ };
+ }
+ };
+
+ function getDocument(node){
+ return obj_nodes.get(node);
+ }
+
+ // DOM NodeList
+
+ window.DOMNodeList = function(list){
+ this._dom = list;
+ this.length = list.getLength();
+
+ for ( var i = 0; i < this.length; i++ ) {
+ var node = list.item(i);
+ this[i] = makeNode( node );
+ }
+ };
+
+ DOMNodeList.prototype = {
+ toString: function(){
+ return "[ " +
+ Array.prototype.join.call( this, ", " ) + " ]";
+ },
+ get outerHTML(){
+ return Array.prototype.map.call(
+ this, function(node){return node.outerHTML;}).join('');
+ }
+ };
+
+ // DOM Node
+
+ window.DOMNode = function(node){
+ this._dom = node;
+ };
+
+ DOMNode.prototype = {
+ get nodeType(){
+ return this._dom.getNodeType();
+ },
+ get nodeValue(){
+ return this._dom.getNodeValue();
+ },
+ get nodeName() {
+ return this._dom.getNodeName();
+ },
+ get childNodes(){
+ return new DOMNodeList( this._dom.getChildNodes() );
+ },
+ cloneNode: function(deep){
+ return makeNode( this._dom.cloneNode(deep) );
+ },
+ get ownerDocument(){
+ return getDocument( this._dom.ownerDocument );
+ },
+ get documentElement(){
+ return makeNode( this._dom.documentElement );
+ },
+ get parentNode() {
+ return makeNode( this._dom.getParentNode() );
+ },
+ get nextSibling() {
+ return makeNode( this._dom.getNextSibling() );
+ },
+ get previousSibling() {
+ return makeNode( this._dom.getPreviousSibling() );
+ },
+ toString: function(){
+ return '"' + this.nodeValue + '"';
+ },
+ get outerHTML(){
+ return this.nodeValue;
+ }
+ };
+
+ window.DOMComment = function(node){
+ this._dom = node;
+ };
+
+ DOMComment.prototype = extend(new DOMNode(), {
+ get nodeType(){
+ return 8;
+ },
+ get outerHTML(){
+ return "";
+ }
+ });
+
+ // DOM Element
+
+ window.DOMElement = function(elem){
+ this._dom = elem;
+ this.style = {
+ get opacity(){ return this._opacity; },
+ set opacity(val){ this._opacity = val + ""; }
+ };
+
+ // Load CSS info
+ var styles = (this.getAttribute("style") || "").split(/\s*;\s*/);
+
+ for ( var i = 0; i < styles.length; i++ ) {
+ var style = styles[i].split(/\s*:\s*/);
+ if ( style.length == 2 )
+ this.style[ style[0] ] = style[1];
+ }
+
+ if ( this.nodeName == "FORM" ) {
+ this.__defineGetter__("elements", function(){
+ return this.getElementsByTagName("*");
+ });
+
+ this.__defineGetter__("length", function(){
+ var elems = this.elements;
+ for ( var i = 0; i < elems.length; i++ ) {
+ this[i] = elems[i];
+ }
+
+ return elems.length;
+ });
+ }
+
+ if ( this.nodeName == "SELECT" ) {
+ this.__defineGetter__("options", function(){
+ return this.getElementsByTagName("option");
+ });
+ }
+
+ this.defaultValue = this.value;
+ };
+
+ DOMElement.prototype = extend( new DOMNode(), {
+ get nodeName(){
+ return this.tagName;
+ },
+ get tagName(){
+ return this._dom.getTagName().toUpperCase();
+ },
+ toString: function(){
+ return "<" + this.tagName + (this.id ? "#" + this.id : "" ) + ">";
+ },
+ get outerHTML(){
+ var ret = "<" + this.tagName, attr = this.attributes;
+
+ for ( var i in attr )
+ ret += " " + i + "='" + attr[i] + "'";
+
+ if ( this.childNodes.length || this.nodeName == "SCRIPT" )
+ ret += ">" + this.childNodes.outerHTML +
+ "" + this.tagName + ">";
+ else
+ ret += "/>";
+
+ return ret;
+ },
+
+ get attributes(){
+ var attr = {}, attrs = this._dom.getAttributes();
+
+ for ( var i = 0; i < attrs.getLength(); i++ )
+ attr[ attrs.item(i).nodeName ] = attrs.item(i).nodeValue;
+
+ return attr;
+ },
+
+ get innerHTML(){
+ return this.childNodes.outerHTML;
+ },
+ set innerHTML(html){
+ html = html.replace(/<\/?([A-Z]+)/g, function(m){
+ return m.toLowerCase();
+ }).replace(/ /g, " ");
+
+ var nodes = this.ownerDocument.importNode(
+ new DOMDocument( new java.io.ByteArrayInputStream(
+ (new java.lang.String("" + html + ""))
+ .getBytes("UTF8"))).documentElement, true).childNodes;
+
+ while (this.firstChild)
+ this.removeChild( this.firstChild );
+
+ for ( var i = 0; i < nodes.length; i++ )
+ this.appendChild( nodes[i] );
+ },
+
+ get textContent(){
+ return nav(this.childNodes);
+
+ function nav(nodes){
+ var str = "";
+ for ( var i = 0; i < nodes.length; i++ )
+ if ( nodes[i].nodeType == 3 )
+ str += nodes[i].nodeValue;
+ else if ( nodes[i].nodeType == 1 )
+ str += nav(nodes[i].childNodes);
+ return str;
+ }
+ },
+ set textContent(text){
+ while (this.firstChild)
+ this.removeChild( this.firstChild );
+ this.appendChild( this.ownerDocument.createTextNode(text));
+ },
+
+ style: {},
+ clientHeight: 0,
+ clientWidth: 0,
+ offsetHeight: 0,
+ offsetWidth: 0,
+
+ get disabled() {
+ var val = this.getAttribute("disabled");
+ return val != "false" && !!val;
+ },
+ set disabled(val) { return this.setAttribute("disabled",val); },
+
+ get checked() {
+ var val = this.getAttribute("checked");
+ return val != "false" && !!val;
+ },
+ set checked(val) { return this.setAttribute("checked",val); },
+
+ get selected() {
+ if ( !this._selectDone ) {
+ this._selectDone = true;
+
+ if ( this.nodeName == "OPTION" && !this.parentNode.getAttribute("multiple") ) {
+ var opt = this.parentNode.getElementsByTagName("option");
+
+ if ( this == opt[0] ) {
+ var select = true;
+
+ for ( var i = 1; i < opt.length; i++ )
+ if ( opt[i].selected ) {
+ select = false;
+ break;
+ }
+
+ if ( select )
+ this.selected = true;
+ }
+ }
+ }
+
+ var val = this.getAttribute("selected");
+ return val != "false" && !!val;
+ },
+ set selected(val) { return this.setAttribute("selected",val); },
+
+ get className() { return this.getAttribute("class") || ""; },
+ set className(val) {
+ return this.setAttribute("class",
+ val.replace(/(^\s*|\s*$)/g,""));
+ },
+
+ get type() { return this.getAttribute("type") || ""; },
+ set type(val) { return this.setAttribute("type",val); },
+
+ get defaultValue() { return this.getAttribute("defaultValue") || ""; },
+ set defaultValue(val) { return this.setAttribute("defaultValue",val); },
+
+ get value() { return this.getAttribute("value") || ""; },
+ set value(val) { return this.setAttribute("value",val); },
+
+ get src() { return this.getAttribute("src") || ""; },
+ set src(val) { return this.setAttribute("src",val); },
+
+ get id() { return this.getAttribute("id") || ""; },
+ set id(val) { return this.setAttribute("id",val); },
+
+ getAttribute: function(name){
+ return this._dom.hasAttribute(name) ?
+ new String( this._dom.getAttribute(name) ) :
+ null;
+ },
+ setAttribute: function(name,value){
+ this._dom.setAttribute(name,value);
+ },
+ removeAttribute: function(name){
+ this._dom.removeAttribute(name);
+ },
+
+ get childNodes(){
+ return new DOMNodeList( this._dom.getChildNodes() );
+ },
+ get firstChild(){
+ return makeNode( this._dom.getFirstChild() );
+ },
+ get lastChild(){
+ return makeNode( this._dom.getLastChild() );
+ },
+ appendChild: function(node){
+ this._dom.appendChild( node._dom );
+ },
+ insertBefore: function(node,before){
+ this._dom.insertBefore( node._dom, before ? before._dom : before );
+
+ execScripts( node );
+
+ function execScripts( node ) {
+ if ( node.nodeName == "SCRIPT" ) {
+ if ( !node.getAttribute("src") ) {
+ eval.call( window, node.textContent );
+ }
+ } else {
+ var scripts = node.getElementsByTagName("script");
+ for ( var i = 0; i < scripts.length; i++ ) {
+ execScripts( node );
+ }
+ }
+ }
+ },
+ removeChild: function(node){
+ this._dom.removeChild( node._dom );
+ },
+
+ getElementsByTagName: DOMDocument.prototype.getElementsByTagName,
+
+ addEventListener: window.addEventListener,
+ removeEventListener: window.removeEventListener,
+ dispatchEvent: window.dispatchEvent,
+
+ click: function(){
+ var event = document.createEvent();
+ event.initEvent("click");
+ this.dispatchEvent(event);
+ },
+ submit: function(){
+ var event = document.createEvent();
+ event.initEvent("submit");
+ this.dispatchEvent(event);
+ },
+ focus: function(){
+ var event = document.createEvent();
+ event.initEvent("focus");
+ this.dispatchEvent(event);
+ },
+ blur: function(){
+ var event = document.createEvent();
+ event.initEvent("blur");
+ this.dispatchEvent(event);
+ },
+ get contentWindow(){
+ return this.nodeName == "IFRAME" ? {
+ document: this.contentDocument
+ } : null;
+ },
+ get contentDocument(){
+ if ( this.nodeName == "IFRAME" ) {
+ if ( !this._doc )
+ this._doc = new DOMDocument(
+ new java.io.ByteArrayInputStream((new java.lang.String(
+ "
"))
+ .getBytes("UTF8")));
+ return this._doc;
+ } else
+ return null;
+ }
+ });
+
+ // Helper method for extending one object with another
+
+ function extend(a,b) {
+ for ( var i in b ) {
+ var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
+
+ if ( g || s ) {
+ if ( g )
+ a.__defineGetter__(i, g);
+ if ( s )
+ a.__defineSetter__(i, s);
+ } else
+ a[i] = b[i];
+ }
+ return a;
+ }
+
+ // Helper method for generating the right
+ // DOM objects based upon the type
+
+ var obj_nodes = new java.util.HashMap();
+
+ function makeNode(node){
+ if ( node ) {
+ if ( !obj_nodes.containsKey( node ) )
+ obj_nodes.put( node, node.getNodeType() == 1?
+ new DOMElement( node ) :
+ node.getNodeType() == 8 ?
+ new DOMComment( node ) :
+ new DOMNode( node ) );
+
+ return obj_nodes.get(node);
+ } else
+ return null;
+ }
+
+ // XMLHttpRequest
+ // Originally implemented by Yehuda Katz
+
+ window.XMLHttpRequest = function(){
+ this.headers = {};
+ this.responseHeaders = {};
+ };
+
+ XMLHttpRequest.prototype = {
+ open: function(method, url, async, user, password){
+ this.readyState = 1;
+ if (async)
+ this.async = true;
+ this.method = method || "GET";
+ this.url = url;
+ this.onreadystatechange();
+ },
+ setRequestHeader: function(header, value){
+ this.headers[header] = value;
+ },
+ getResponseHeader: function(header){ },
+ send: function(data){
+ var self = this;
+
+ function makeRequest(){
+ var url = new java.net.URL(curLocation, self.url);
+
+ if ( url.getProtocol() == "file" ) {
+ if ( self.method == "PUT" ) {
+ var out = new java.io.FileWriter(
+ new java.io.File( new java.net.URI( url.toString() ) ) ),
+ text = new java.lang.String( data || "" );
+
+ out.write( text, 0, text.length() );
+ out.flush();
+ out.close();
+ } else if ( self.method == "DELETE" ) {
+ var file = new java.io.File( new java.net.URI( url.toString() ) );
+ file["delete"]();
+ } else {
+ var connection = url.openConnection();
+ connection.connect();
+ handleResponse();
+ }
+ } else {
+ var connection = url.openConnection();
+
+ connection.setRequestMethod( self.method );
+
+ // Add headers to Java connection
+ for (var header in self.headers)
+ connection.addRequestProperty(header, self.headers[header]);
+
+ connection.connect();
+
+ // Stick the response headers into responseHeaders
+ for (var i = 0; ; i++) {
+ var headerName = connection.getHeaderFieldKey(i);
+ var headerValue = connection.getHeaderField(i);
+ if (!headerName && !headerValue) break;
+ if (headerName)
+ self.responseHeaders[headerName] = headerValue;
+ }
+
+ handleResponse();
+ }
+
+ function handleResponse(){
+ self.readyState = 4;
+ self.status = parseInt(connection.responseCode) || undefined;
+ self.statusText = connection.responseMessage || "";
+
+ var stream = new java.io.InputStreamReader(connection.getInputStream()),
+ buffer = new java.io.BufferedReader(stream), line;
+
+ while ((line = buffer.readLine()) != null)
+ self.responseText += line;
+
+ self.responseXML = null;
+
+ if ( self.responseText.match(/^\s*) ) {
+ try {
+ self.responseXML = new DOMDocument(
+ new java.io.ByteArrayInputStream(
+ (new java.lang.String(
+ self.responseText)).getBytes("UTF8")));
+ } catch(e) {}
+ }
+ }
+
+ self.onreadystatechange();
+ }
+
+ if (this.async)
+ (new java.lang.Thread(new java.lang.Runnable({
+ run: makeRequest
+ }))).start();
+ else
+ makeRequest();
+ },
+ abort: function(){},
+ onreadystatechange: function(){},
+ getResponseHeader: function(header){
+ if (this.readyState < 3)
+ throw new Error("INVALID_STATE_ERR");
+ else {
+ var returnedHeaders = [];
+ for (var rHeader in this.responseHeaders) {
+ if (rHeader.match(new Regexp(header, "i")))
+ returnedHeaders.push(this.responseHeaders[rHeader]);
+ }
+
+ if (returnedHeaders.length)
+ return returnedHeaders.join(", ");
+ }
+
+ return null;
+ },
+ getAllResponseHeaders: function(header){
+ if (this.readyState < 3)
+ throw new Error("INVALID_STATE_ERR");
+ else {
+ var returnedHeaders = [];
+
+ for (var header in this.responseHeaders)
+ returnedHeaders.push( header + ": " + this.responseHeaders[header] );
+
+ return returnedHeaders.join("\r\n");
+ }
+ },
+ async: true,
+ readyState: 0,
+ responseText: "",
+ status: 0
+ };
+})();
diff --git a/Tools/press/objj-analysis-tools.j b/Tools/press/objj-analysis-tools.j
new file mode 100644
index 000000000..e073e4363
--- /dev/null
+++ b/Tools/press/objj-analysis-tools.j
@@ -0,0 +1,340 @@
+var objjPath = OBJJ_LIB+'/Frameworks/Objective-J/Objective-J.js',
+ bridgePath = OBJJ_LIB+'/bridge.js',
+ envPath = "/Users/tlrobinson/280North/git/cappuccino/Tools/press/env.js";
+
+/*
+ param context includes
+ scope: a global variable containing objj_files hash
+ processedFiles: hash containing file paths which have already been analyzed
+ dependencies: hash mapping from paths to an array of global variables defined by that file
+ [importCallback]: callback function that is called for each imported file (takes importing file path, and imported file path parameters)
+ [referencedCallback]: callback function that is called for each referenced file (takes referencing file path, referenced file path parameters, and list of tokens)
+ [importedFiles]: hash that will contain a mapping of file names to a hash of imported files
+ [referencedFiles]: hash that will contain a mapping of file names to a hash of referenced files (which contains a hash of tokens referenced)
+
+ param file is an objj_file object containing path, fragments, content, bundle, etc
+*/
+function traverseDependencies(context, file)
+{
+ if (context.processedFiles[file.path])
+ return;
+ context.processedFiles[file.path] = true;
+
+ var ignoreImports = false;
+ if (context.ignoreAllImports)
+ {
+ CPLog.warn("Ignoring all import fragments. ("+file.path+")");
+ ignoreImports = true;
+ }
+ else if (context.ignoreFrameworkImports)
+ {
+ var matches = file.path.match(/([^\/]+)\/([^\/]+)\.j$/); // Matches "ZZZ/ZZZ.j" (e.x. AppKit/AppKit.j and Foundation/Foundation.j)
+ if (matches && matches[1] === matches[2])
+ {
+ CPLog.warn("Framework import file! Ignoring all import fragments. ("+file.path+")");
+ ignoreImports = true;
+ }
+ }
+
+ if (!file.fragments)
+ {
+ if (file.included)
+ CPLog.warn(file.path + " is included but missing fragments");
+ else
+ CPLog.info("Preprocessing " + file.path);
+
+ file.fragments = objj_preprocess(file.contents, file.bundle, file);
+ }
+
+ var referencedFiles = {},
+ importedFiles = {};
+
+ CPLog.trace("Processing " + file.path + " fragments ("+file.fragments.length+")");
+ for (var i = 0; i < file.fragments.length; i++)
+ {
+ var fragment = file.fragments[i];
+
+ if (fragment.type & FRAGMENT_CODE)
+ {
+ var lexer = new objj_lexer(fragment.info, NULL);
+
+ var token;
+ while (token = lexer.skip_whitespace())
+ {
+ if (context.dependencies[token])
+ {
+ var files = context.dependencies[token]
+ for (var j = 0; j < files.length; j++)
+ {
+ // don't record references to self
+ if (files[j] != file.path)
+ {
+ if (!referencedFiles[files[j]])
+ referencedFiles[files[j]] = {};
+
+ referencedFiles[files[j]][token] = true;
+ }
+ }
+ }
+ }
+ }
+ else if (fragment.type & FRAGMENT_IMPORT)
+ {
+ if (ignoreImports)
+ {
+ fragment.conditionallyIgnore = true;
+ }
+ else
+ {
+ var importedFile = findImportInObjjFiles(context.scope, fragment);
+ if (importedFile)
+ {
+ // should never import self, but just in case?
+ if (importedFile != file.path)
+ importedFiles[importedFile] = true;
+ else
+ CPLog.error("Ignoring self import (why are you importing yourself!?): " + file.path);
+ }
+ else
+ CPLog.error("Couldn't find file for import " + fragment.info + "("+fragment.type+")");
+ }
+ }
+ }
+
+ // check each imported file
+ for (var importedFile in importedFiles)
+ {
+ if (importedFile != file.path)
+ {
+ if (context.importCallback)
+ context.importCallback(file.path, importedFile);
+
+ if (context.scope.objj_files[importedFile])
+ traverseDependencies(context, context.scope.objj_files[importedFile]);
+ else
+ CPLog.error("Missing imported file: " + importedFile);
+ }
+ }
+
+ if (context.importedFiles)
+ context.importedFiles[file.path] = importedFiles;
+
+ // check each referenced file
+ for (var referencedFile in referencedFiles)
+ {
+ if (referencedFile != file.path)
+ {
+ if (context.referenceCallback)
+ context.referenceCallback(file.path, referencedFile, referencedFiles[referencedFile]);
+
+ if (context.scope.objj_files[referencedFile])
+ traverseDependencies(context, context.scope.objj_files[referencedFile]);
+ else
+ CPLog.error("Missing referenced file: " + referencedFile);
+ }
+ }
+
+ if (context.referencedFiles)
+ context.referencedFiles[file.path] = referencedFiles;
+}
+
+function findImportInObjjFiles(scope, fragment)
+{
+ var importPath = null;
+
+ if (fragment.type & FRAGMENT_LOCAL)
+ {
+ //CPLog.debug("Importing local file: " + fragment.info);
+
+ var searchPath = fragment.info;
+ if (scope.objj_files[searchPath])
+ {
+ importPath = searchPath;
+ }
+ }
+ else if (fragment.type & FRAGMENT_FILE)
+ {
+ //CPLog.debug("Importing search-path file: " + fragment.info);
+
+ var count = scope.OBJJ_INCLUDE_PATHS.length;
+ while (count--)
+ {
+ var searchPath = scope.OBJJ_INCLUDE_PATHS[count] + fragment.info;
+ if (scope.objj_files[searchPath])
+ {
+ importPath = searchPath;
+ break;
+ }
+ }
+ }
+ else
+ CPLog.warn("Import fragment not local or file");
+
+ return importPath;
+}
+
+// given a fresh scope and the path to a root source file, determine which files define each global variable
+function findGlobalDefines(context, scope, rootPath)
+{
+ addMockBrowserEnvironment(scope);
+
+ var ignore = cloneProperties(scope, true);
+ ignore['bundle'] = true;
+
+ var dependencies = {};
+
+ //scope.fragment_evaluate_file_original = scope.fragment_evaluate_file;
+ //scope.fragment_evaluate_file = function(aFragment)
+ //{
+ // //CPLog.trace("Loading "+aFragment.info);
+ //
+ // var result = scope.fragment_evaluate_file_original(aFragment);
+ //
+ // return result;
+ //}
+
+ scope.fragment_evaluate_code_original = scope.fragment_evaluate_code;
+ scope.fragment_evaluate_code = function(aFragment)
+ {
+ CPLog.debug("Evaling "+aFragment.file.path + " / " + aFragment.bundle.path);
+
+ var before = cloneProperties(scope);
+
+ var result = scope.fragment_evaluate_code_original(aFragment);
+
+ var definedGlobals = {};
+ diff(before, scope, ignore, definedGlobals, definedGlobals, null);
+ dependencies[aFragment.file.path] = definedGlobals;
+
+ return result;
+ }
+
+ runWithScope(context, scope, function(importName) {
+
+ print('Loading from '+OBJJ_INCLUDE_PATHS);
+
+ objj_import(importName, true, function() {
+ print('Callback complete');
+ });
+
+ print('Done');
+
+ }, [rootPath]);
+
+ //for (var i in scope.objj_included_files)
+ // CPLog.debug(i + " ==> " + scope.objj_included_files[i]);
+
+ return dependencies;
+}
+
+function coalesceGlobalDefines(globals)
+{
+ var dependencies = {};
+ for (var fileName in globals)
+ {
+ var fileGlobals = globals[fileName];
+
+ for (var globalName in fileGlobals)
+ {
+ if (!dependencies[globalName])
+ dependencies[globalName] = [];
+ dependencies[globalName].push(fileName);
+ }
+ }
+ return dependencies;
+}
+
+// create a new scope loaded with Objective-J
+function makeObjjScope(context, debug)
+{
+ // init standard js scope objects
+ var scope = context.initStandardObjects();
+
+ if (debug)
+ {
+ scope.objj_alert = print;
+ scope.debug = true;
+ }
+
+ // give the scope "print"
+ scope.print = function(value) { Packages.java.lang.System.out.println(value); };
+
+ // load and eval fake browser environment
+ var envSource = readFile(envPath);
+ if (envSource)
+ context.evaluateString(scope, envSource, "env.js", 1, null);
+ else
+ CPLog.warn("Missing env.js");
+
+ // load and eval the bridge
+ var bridgeSource = readFile(bridgePath);
+ if (bridgeSource)
+ context.evaluateString(scope, bridgeSource, "bridge.js", 1, null);
+ else
+ CPLog.warn("Missing bridge.js");
+
+ // load and eval obj-j
+ var objjSource = readFile(objjPath);
+ if (objjSource)
+ context.evaluateString(scope, objjSource, "Objective-J.js", 1, null);
+ else
+ CPLog.warn("Missing Objective-J.js");
+
+ return scope;
+}
+
+// run a function within the given scope (func can be a function object if the source of the function is returned by toString() as it is by default)
+function runWithScope(context, scope, func, arguments)
+{
+ scope.__runWithScopeArgs = arguments || [];
+
+ var code = "("+func+").apply(this, this.__runWithScopeArgs); serviceTimeouts();";
+
+ return context.evaluateString(scope, code, "", 1, null);
+}
+
+// add a mock browser environment to the provided scope
+function addMockBrowserEnvironment(scope)
+{
+ // TODO: complete this. or use env.js?
+
+ scope.Element = function() {
+ this.style = {}
+ }
+
+ scope.document = {
+ createElement : function() {
+ return new scope.Element();
+ }
+ }
+}
+
+// does a shallow copy of an object. if onlyList is true, it sets each property to "true" instead of the actual value
+function cloneProperties(object, onlyList)
+{
+ var results = {}
+ for (var memeber in object)
+ results[memeber] = onlyList ? true : object[memeber];
+ return results;
+}
+
+function diff(objectA, objectB, ignore, added, changed, deleted)
+{
+ for (var i in objectB)
+ if (added && !ignore[i] && typeof objectA[i] == "undefined")
+ added[i] = true;
+ for (var i in objectB)
+ if (changed && !ignore[i] && typeof objectA[i] != "undefined" && typeof objectB[i] != "undefined" && objectA[i] !== objectB[i])
+ changed[i] = true;
+ for (var i in objectA)
+ if (deleted && !ignore[i] && typeof objectB[i] == "undefined")
+ deleted[i] = true;
+}
+
+function allKeys(object)
+{
+ var result = [];
+ for (var i in object)
+ result.push(i)
+ return result.sort();
+}
\ No newline at end of file
diff --git a/Tools/press/press.j b/Tools/press/press.j
new file mode 100644
index 000000000..c4fad198d
--- /dev/null
+++ b/Tools/press/press.j
@@ -0,0 +1,264 @@
+import
+
+import "objj-analysis-tools.j"
+
+CPLogRegister(CPLogPrint);
+
+
+function main()
+{
+ if (args.length < 2)
+ {
+ print("Usage: press input_base_file.j output_directory");
+ return;
+ }
+
+ var rootPath = args[0],
+ sourceDirectory = dirname(rootPath) || ".",
+ outputDirectory = args[1];
+
+ var cx = Packages.org.mozilla.javascript.Context.enter(),
+ scope = makeObjjScope(cx);
+
+ var frameworks = rootPath.substring(0, rootPath.lastIndexOf("/")+1) + "Frameworks/";
+ scope.OBJJ_INCLUDE_PATHS = [frameworks];
+
+ CPLog.info("OBJJ_INCLUDE_PATHS="+scope.OBJJ_INCLUDE_PATHS);
+
+ // phase 1: get global defines
+
+ var globals = findGlobalDefines(cx, scope, rootPath);
+
+ // coalesce the results
+ var dependencies = coalesceGlobalDefines(globals);
+
+ // phase 2: walk the import tree to determine exactly which files need to be included
+
+ var requiredFiles = {};
+
+ if (scope.objj_files[rootPath])
+ {
+ var context = {
+ scope : scope,
+ dependencies : dependencies,
+ processedFiles : {},
+ ignoreFrameworkImports : true,
+ importCallback : function(importing, imported) {
+ requiredFiles[imported] = true;
+ },
+ referenceCallback : function(referencing, referenced) {
+ requiredFiles[referenced] = true;
+ }
+ }
+
+ traverseDependencies(context, scope.objj_files[rootPath]);
+
+ var count = 0,
+ total = 0;
+ for (var path in scope.objj_files)
+ {
+ if (requiredFiles[path])
+ {
+ CPLog.info("Included: " + path);
+ count++;
+ }
+ else
+ {
+ CPLog.warn("Excluded: " + path);
+ }
+ total++;
+ }
+ CPLog.error("Total required files: " + count + " out of " + total);
+ }
+ else
+ {
+ CPLog.error("Root file not loaded!");
+ return;
+ }
+
+ // phase 3: rebuild .sj files with correct imports, copy .j files
+
+ var outputFiles = {};
+ for (var path in requiredFiles)
+ {
+ var file = scope.objj_files[path],
+ filename = basename(path),
+ directory = dirname(path);
+
+ if (file.path != path)
+ CPLog.warn("Sanity check (file path): " + file.path + " vs. " + path);
+
+ if (file.bundle)
+ {
+ var bundleDirectory = dirname(file.bundle.path);
+
+ if (bundleDirectory != directory)
+ CPLog.warn("Sanity check (directory path): " + directory + " vs. " + bundleDirectory);
+
+ // if it's in a .sj
+ var dict = file.bundle.info,
+ replacedFiles = [dict objectForKey:"CPBundleReplacedFiles"];
+ if (replacedFiles && [replacedFiles containsObject:filename])
+ {
+ var staticPath = bundleDirectory + "/" + [dict objectForKey:"CPBundleExecutable"];
+ if (!outputFiles[staticPath])
+ {
+ outputFiles[staticPath] = [];
+ outputFiles[staticPath].push("@STATIC;1.0;");
+ }
+ outputFiles[staticPath].push("p;");
+ outputFiles[staticPath].push(filename.length+";");
+ outputFiles[staticPath].push(filename);
+
+ for (var i = 0; i < file.fragments.length; i++)
+ {
+ if (file.fragments[i].type & FRAGMENT_CODE)
+ {
+ outputFiles[staticPath].push("c;");
+ outputFiles[staticPath].push(file.fragments[i].info.length+";");
+ outputFiles[staticPath].push(file.fragments[i].info);
+ }
+ else if (file.fragments[i].type & FRAGMENT_IMPORT)
+ {
+ var ignoreFragment = false;
+ if (file.fragments[i].conditionallyIgnore)
+ {
+ var importPath = findImportInObjjFiles(scope, file.fragments[i]);
+ if (!importPath || !requiredFiles[importPath])
+ {
+ ignoreFragment = true;
+ }
+ }
+
+ if (!ignoreFragment)
+ {
+ if (file.fragments[i].type & FRAGMENT_LOCAL)
+ {
+ var relativePath = pathRelativeTo(file.fragments[i].info, directory)
+
+ outputFiles[staticPath].push("i;");
+ outputFiles[staticPath].push(relativePath.length+";");
+ outputFiles[staticPath].push(relativePath);
+ }
+ else if (file.fragments[i].type & FRAGMENT_FILE)
+ {
+ outputFiles[staticPath].push("I;");
+ outputFiles[staticPath].push(file.fragments[i].info.length+";");
+ outputFiles[staticPath].push(file.fragments[i].info);
+ }
+ }
+ else
+ CPLog.warn("Ignoring import fragment " + file.fragments[i].info + " in " + path);
+ }
+ else
+ CPLog.error("Unknown fragment type");
+ }
+ }
+ // always output individual .j files
+ else
+ {
+ outputFiles[path] = file.contents;
+ }
+ }
+ else
+ CPLog.warn("No bundle for " + path)
+ }
+
+ // phase 4: copy everything and write out the new files
+
+ var sourceDirectoryFile = new Packages.java.io.File(sourceDirectory),
+ outputDirectoryFile = new Packages.java.io.File(outputDirectory);
+
+ copyDirectory(sourceDirectoryFile, outputDirectoryFile);
+
+ for (var path in outputFiles)
+ {
+ var file = new java.io.File(outputDirectoryFile, path);
+
+ var parent = file.getParentFile();
+ if (!parent.exists())
+ {
+ CPLog.warn(parent + " doesn't exist, creating directories.");
+ parent.mkdirs();
+ }
+
+ CPLog.info("Writing out " + file);
+
+ var writer = new java.io.BufferedWriter(new java.io.FileWriter(file));
+
+ if (typeof outputFiles[path] == "string")
+ writer.write(outputFiles[path]);
+ else
+ writer.write(outputFiles[path].join(""));
+
+ writer.close();
+ }
+}
+
+// Helper Utilities
+
+// TODO: moved elsewhere?
+
+function copyDirectory(src, dst)
+{
+ CPLog.trace("Copying directory " + src);
+
+ dst.mkdirs();
+
+ var files = src.listFiles();
+ for (var i = 0; i < files.length; i++)
+ {
+ if (files[i].isFile())
+ copyFile(files[i], new Packages.java.io.File(dst, files[i].getName()));
+ else if (files[i].isDirectory())
+ copyDirectory(files[i], new Packages.java.io.File(dst, files[i].getName()));
+ }
+}
+
+function copyFile(src, dst)
+{
+ CPLog.trace("Copying file " + src);
+
+ var input = (new Packages.java.io.FileInputStream(src)).getChannel(),
+ output = (new Packages.java.io.FileOutputStream(dst)).getChannel();
+
+ input.transferTo(0, input.size(), output);
+
+ input.close();
+ output.close();
+}
+
+function dirname(path)
+{
+ return path.substring(0, path.lastIndexOf("/"));
+}
+
+function basename(path)
+{
+ return path.substring(path.lastIndexOf("/") + 1);
+}
+
+function pathRelativeTo(target, relativeTo)
+{
+ var components = [],
+ targetParts = target.split("/"),
+ relativeParts = relativeTo.split("/");
+
+ var i = 0;
+ while (i < targetParts.length)
+ {
+ if (targetParts[i] != relativeParts[i])
+ break;
+ i++;
+ }
+
+ for (var j = i; j < relativeParts.length; j++)
+ components.push("..");
+
+ for (var j = i; j < targetParts.length; j++)
+ components.push(targetParts[j]);
+
+ return components.join("/");
+}
+
+main();