/* * Executable.js * Objective-J * * Created by Francisco Tolmasky. * Copyright 2010, 280 North, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ var ExecutableUnloadedFileDependencies = 0, ExecutableLoadingFileDependencies = 1, ExecutableLoadedFileDependencies = 2; function Executable(/*String*/ aCode, /*Array*/ fileDependencies, /*String*/ aScope, /*Function*/ aFunction) { if (arguments.length === 0) return this; this._code = aCode; this._function = aFunction || NULL; this._scope = aScope || "(Anonymous)"; this._fileDependencies = fileDependencies; this._fileDependencyLoadStatus = ExecutableUnloadedFileDependencies; this._eventDispatcher = new EventDispatcher(this); if (this._function) return; this.setCode(aCode); } exports.Executable = Executable; Executable.prototype.path = function() { return FILE.join(FILE.cwd(), "(Anonymous)"); } Executable.prototype.functionParameters = function() { var functionParameters = ["global", "objj_executeFile", "objj_importFile"]; //exportedNames().concat("objj_executeFile", "objj_importFile"); #ifdef COMMONJS functionParameters = functionParameters.concat("require", "exports", "module", "system", "print", "window"); #endif return functionParameters; } Executable.prototype.functionArguments = function() { var functionArguments = [global, this.fileExecuter(), this.fileImporter()]; #ifdef COMMONJS functionArguments = functionArguments.concat(Executable.commonJSArguments()); #endif return functionArguments; } #ifdef COMMONJS Executable.setCommonJSParameters = function() { this._commonJSParameters = Array.prototype.slice.call(arguments); } Executable.commonJSParameters = function() { return this._commonJSParameters || []; } Executable.setCommonJSArguments = function() { this._commonJSArguments = Array.prototype.slice.call(arguments); } Executable.commonJSArguments = function() { return this._commonJSArguments || []; } Executable.prototype.toMarkedString = function() { var markedString = "@STATIC;1.0;", dependencies = this.fileDependencies(), index = 0, count = dependencies.length; for (; index < count; ++index) markedString += dependencies[index].toMarkedString(); var code = this.code(); return markedString + MARKER_TEXT + ";" + code.length + ";" + code; } #endif Executable.prototype.execute = function() { #if EXECUTION_LOGGING CPLog("EXECUTION: " + this.path()); #endif var oldContextBundle = CONTEXT_BUNDLE; CONTEXT_BUNDLE = CFBundle.bundleContainingPath(this.path()); var result = this._function.apply(global, this.functionArguments()); CONTEXT_BUNDLE = oldContextBundle; return result; } Executable.prototype.code = function() { return this._code; } Executable.prototype.setCode = function(code) { this._code = code; var parameters = this.functionParameters().join(","); #if COMMONJS if (typeof system !== "undefined" && system.engine === "rhino") { code = "function(" + parameters + "){" + code + "/**/\n}"; this._function = Packages.org.mozilla.javascript.Context.getCurrentContext().compileFunction(window, code, this._scope, 0, NULL); } else { #endif // "//@ sourceURL=" at the end lets us name our eval'd files for debuggers, etc. // * WebKit: http://pmuellr.blogspot.com/2009/06/debugger-friendly.html // * Firebug: http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/ //if (YES) { code += "/**/\n//@ sourceURL=" + this._scope; this._function = new Function(parameters, code); //} else { // // Firebug only does it for "eval()", not "new Function()". Ugh. Slower. // var functionText = "(function(){"+GET_CODE(aFragment)+"/**/\n})\n//@ sourceURL="+GET_FILE(aFragment).path; // compiled = eval(functionText); //} this._function.displayName = this._scope; #if COMMONJS } #endif } Executable.prototype.fileDependencies = function() { return this._fileDependencies; } Executable.prototype.scope = function() { return this._scope; } Executable.prototype.hasLoadedFileDependencies = function() { return this._fileDependencyLoadStatus === ExecutableLoadedFileDependencies; } var globalIteration = 0; Executable.prototype.loadFileDependencies = function() { #if DEPENDENCY_LOGGING CPLog("DEPENDENCY: initiated by " + this.scope()); #endif if (this._fileDependencyLoadStatus !== ExecutableUnloadedFileDependencies) return; this._fileDependencyLoadStatus = ExecutableLoadingFileDependencies; var searchedPaths = [{ }, { }], fileExecutableSearches = new CFMutableDictionary(), incompleteFileExecutableSearches = new CFMutableDictionary(), loadingExecutables = { }; function searchForFileDependencies(/*Executable*/ anExecutable) { var executables = [anExecutable], executableIndex = 0, executableCount = executables.length; for (; executableIndex < executableCount; ++executableIndex) { var executable = executables[executableIndex]; if (executable.hasLoadedFileDependencies()) continue; var executablePath = executable.path(); loadingExecutables[executablePath] = executable; var cwd = FILE.dirname(executablePath), fileDependencies = executable.fileDependencies(), fileDependencyIndex = 0, fileDependencyCount = fileDependencies.length; for (; fileDependencyIndex < fileDependencyCount; ++fileDependencyIndex) { var fileDependency = fileDependencies[fileDependencyIndex], isLocal = fileDependency.isLocal(), path = importablePath(fileDependency.path(), isLocal, cwd); if (searchedPaths[isLocal ? 1 : 0][path]) continue; searchedPaths[isLocal ? 1 : 0][path] = YES; var fileExecutableSearch = new FileExecutableSearch(path, isLocal), fileExecutableSearchUID = fileExecutableSearch.UID(); if (fileExecutableSearches.containsKey(fileExecutableSearchUID)) continue; fileExecutableSearches.setValueForKey(fileExecutableSearchUID, fileExecutableSearch); if (fileExecutableSearch.isComplete()) { executables.push(fileExecutableSearch.result()); ++executableCount; } else { incompleteFileExecutableSearches.setValueForKey(fileExecutableSearchUID, fileExecutableSearch); fileExecutableSearch.addEventListener("complete", function( anEvent) { var fileExecutableSearch = anEvent.fileExecutableSearch; incompleteFileExecutableSearches.removeValueForKey(fileExecutableSearch.UID()); searchForFileDependencies(fileExecutableSearch.result()); }); } } } if (incompleteFileExecutableSearches.count() > 0) #if !DEPENDENCY_LOGGING return; #else { CPLog("DEPENDENCY: more dependencies: "); CPLog(incompleteFileExecutableSearches.toString()); return; } CPLog("DEPENDENCY: Ended"); #endif for (var path in loadingExecutables) if (hasOwnProperty.call(loadingExecutables, path)) loadingExecutables[path]._fileDependencyLoadStatus = ExecutableLoadedFileDependencies; for (var path in loadingExecutables) if (hasOwnProperty.call(loadingExecutables, path)) { var executable = loadingExecutables[path]; executable._eventDispatcher.dispatchEvent( { type:"dependenciesload", executable:executable }); } } searchForFileDependencies(this); } Executable.prototype.addEventListener = function(/*String*/ anEventName, /*Function*/ aListener) { this._eventDispatcher.addEventListener(anEventName, aListener); } Executable.prototype.removeEventListener = function(/*String*/ anEventName, /*Function*/ aListener) { this._eventDispatcher.removeEventListener(anEventName, aListener); } function importablePath(/*String*/ aPath, /*BOOL*/ isLocal, /*String*/ aCWD) { aPath = FILE.normal(aPath); if (FILE.isAbsolute(aPath)) return aPath; if (isLocal) aPath = FILE.normal(FILE.join(aCWD, aPath)); return aPath; } Executable.prototype.fileImporter = function() { return Executable.fileImporterForPath(FILE.dirname(this.path())); } Executable.prototype.fileExecuter = function() { return Executable.fileExecuterForPath(FILE.dirname(this.path())); } var cachedFileExecutersForPaths = { }; Executable.fileExecuterForPath = function(/*String*/ referencePath) { referencePath = FILE.normal(referencePath); var fileExecuter = cachedFileExecutersForPaths[referencePath]; if (!fileExecuter) { fileExecuter = function(/*String*/ aPath, /*BOOL*/ isLocal, /*BOOL*/ shouldForce) { aPath = importablePath(aPath, isLocal, referencePath); var fileExecutableSearch = new FileExecutableSearch(aPath, isLocal), fileExecutable = fileExecutableSearch.result(); if (0 && !fileExecutable.hasLoadedFileDependencies()) throw "No executable loaded for file at path " + aPath; fileExecutable.execute(shouldForce); } cachedFileExecutersForPaths[referencePath] = fileExecuter; } return fileExecuter; } var cachedImportersForPaths = { }; Executable.fileImporterForPath = function(/*String*/ referencePath) { referencePath = FILE.normal(referencePath); var cachedImporter = cachedImportersForPaths[referencePath]; if (!cachedImporter) { cachedImporter = function(/*String*/ aPath, /*BOOL*/ isLocal, /*Function*/ aCallback) { aPath = importablePath(aPath, isLocal, referencePath); var fileExecutableSearch = new FileExecutableSearch(aPath, isLocal); function searchComplete(/*FileExecutableSearch*/ aFileExecutableSearch) { var fileExecutable = aFileExecutableSearch.result(), fileExecuter = Executable.fileExecuterForPath(referencePath), executeAndCallback = function () { fileExecuter(aPath, isLocal); if (aCallback) aCallback(); } if (!fileExecutable.hasLoadedFileDependencies()) { fileExecutable.addEventListener("dependenciesload", executeAndCallback); fileExecutable.loadFileDependencies(); } else executeAndCallback(); } if (fileExecutableSearch.isComplete()) searchComplete(fileExecutableSearch); else fileExecutableSearch.addEventListener("complete", function(/*Event*/ anEvent) { searchComplete(anEvent.fileExecutableSearch); }); } cachedImportersForPaths[referencePath] = cachedImporter; } return cachedImporter; }