[yui-compressor] 04/13: New upstream version 2.4.8
tony mancill
tmancill at debian.org
Sun Feb 11 04:56:09 UTC 2018
This is an automated email from the git hooks/post-receive script.
tmancill pushed a commit to branch master
in repository yui-compressor.
commit c77c8b868a3b12db69a5a5a971f5fb373d084ca8
Author: tony mancill <tmancill at debian.org>
Date: Sat Feb 10 19:28:02 2018 -0800
New upstream version 2.4.8
---
.gitignore | 13 +
.travis.yml | 9 +
LICENSE.TXT | 54 +
README.md | 188 +
ant.properties | 8 +
build.xml | 80 +
doc/CHANGELOG | 277 +
docs/README.md | 32 +
docs/css.mustache | 460 +
docs/index.mustache | 174 +
docs/project.json | 3 +
nodejs/cli.js | 23 +
nodejs/index.js | 114 +
nodejs_tests/files/yui.js | 10970 +++++++++++++++++++
nodejs_tests/files/yui.js.min | 1 +
nodejs_tests/tests.js | 102 +
package.json | 34 +
ports/js/cssmin.js | 391 +
.../yahoo/platform/yui/compressor/Bootstrap.java | 23 +
.../platform/yui/compressor/CssCompressor.java | 468 +
.../platform/yui/compressor/JarClassLoader.java | 158 +
.../yui/compressor/JavaScriptCompressor.java | 1337 +++
.../yui/compressor/JavaScriptIdentifier.java | 55 +
.../platform/yui/compressor/JavaScriptToken.java | 28 +
.../platform/yui/compressor/ScriptOrFnScope.java | 160 +
.../platform/yui/compressor/YUICompressor.java | 275 +
src/org/mozilla/classfile/ByteCode.java | 274 +
src/org/mozilla/classfile/ClassFileWriter.java | 3043 +++++
src/org/mozilla/javascript/Arguments.java | 322 +
src/org/mozilla/javascript/BaseFunction.java | 564 +
src/org/mozilla/javascript/Callable.java | 59 +
src/org/mozilla/javascript/ClassCache.java | 223 +
src/org/mozilla/javascript/ClassShutter.java | 89 +
src/org/mozilla/javascript/CompilerEnvirons.java | 233 +
src/org/mozilla/javascript/ConstProperties.java | 109 +
src/org/mozilla/javascript/Context.java | 2639 +++++
src/org/mozilla/javascript/ContextAction.java | 58 +
src/org/mozilla/javascript/ContextFactory.java | 590 +
src/org/mozilla/javascript/ContextListener.java | 60 +
.../mozilla/javascript/ContinuationPending.java | 99 +
src/org/mozilla/javascript/DToA.java | 1271 +++
src/org/mozilla/javascript/Decompiler.java | 918 ++
.../mozilla/javascript/DefaultErrorReporter.java | 113 +
.../mozilla/javascript/DefiningClassLoader.java | 89 +
src/org/mozilla/javascript/Delegator.java | 266 +
src/org/mozilla/javascript/EcmaError.java | 161 +
src/org/mozilla/javascript/ErrorReporter.java | 106 +
src/org/mozilla/javascript/Evaluator.java | 118 +
src/org/mozilla/javascript/EvaluatorException.java | 123 +
src/org/mozilla/javascript/Function.java | 84 +
src/org/mozilla/javascript/FunctionNode.java | 117 +
src/org/mozilla/javascript/FunctionObject.java | 572 +
.../mozilla/javascript/GeneratedClassLoader.java | 66 +
src/org/mozilla/javascript/IRFactory.java | 1607 +++
src/org/mozilla/javascript/IdFunctionCall.java | 55 +
src/org/mozilla/javascript/IdFunctionObject.java | 196 +
src/org/mozilla/javascript/IdScriptableObject.java | 741 ++
src/org/mozilla/javascript/ImporterTopLevel.java | 324 +
src/org/mozilla/javascript/InterfaceAdapter.java | 156 +
.../mozilla/javascript/InterpretedFunction.java | 235 +
src/org/mozilla/javascript/Interpreter.java | 4740 ++++++++
src/org/mozilla/javascript/InterpreterData.java | 192 +
src/org/mozilla/javascript/JavaAdapter.java | 1140 ++
src/org/mozilla/javascript/JavaMembers.java | 933 ++
.../mozilla/javascript/JavaScriptException.java | 118 +
src/org/mozilla/javascript/Kit.java | 455 +
src/org/mozilla/javascript/LazilyLoadedCtor.java | 141 +
src/org/mozilla/javascript/MemberBox.java | 372 +
src/org/mozilla/javascript/NativeArray.java | 1745 +++
src/org/mozilla/javascript/NativeBoolean.java | 175 +
src/org/mozilla/javascript/NativeCall.java | 158 +
src/org/mozilla/javascript/NativeContinuation.java | 139 +
src/org/mozilla/javascript/NativeDate.java | 1610 +++
src/org/mozilla/javascript/NativeError.java | 232 +
src/org/mozilla/javascript/NativeFunction.java | 172 +
src/org/mozilla/javascript/NativeGenerator.java | 288 +
src/org/mozilla/javascript/NativeGlobal.java | 795 ++
src/org/mozilla/javascript/NativeIterator.java | 269 +
src/org/mozilla/javascript/NativeJavaArray.java | 180 +
src/org/mozilla/javascript/NativeJavaClass.java | 329 +
.../mozilla/javascript/NativeJavaConstructor.java | 88 +
src/org/mozilla/javascript/NativeJavaMethod.java | 580 +
src/org/mozilla/javascript/NativeJavaObject.java | 1002 ++
src/org/mozilla/javascript/NativeJavaPackage.java | 218 +
.../mozilla/javascript/NativeJavaTopPackage.java | 186 +
src/org/mozilla/javascript/NativeMath.java | 403 +
src/org/mozilla/javascript/NativeNumber.java | 250 +
src/org/mozilla/javascript/NativeObject.java | 321 +
src/org/mozilla/javascript/NativeScript.java | 230 +
src/org/mozilla/javascript/NativeString.java | 995 ++
src/org/mozilla/javascript/NativeWith.java | 207 +
src/org/mozilla/javascript/Node.java | 1399 +++
src/org/mozilla/javascript/NodeTransformer.java | 565 +
src/org/mozilla/javascript/ObjArray.java | 388 +
src/org/mozilla/javascript/ObjToIntMap.java | 697 ++
src/org/mozilla/javascript/Parser.java | 2507 +++++
.../javascript/PolicySecurityController.java | 223 +
src/org/mozilla/javascript/Ref.java | 64 +
src/org/mozilla/javascript/RefCallable.java | 59 +
src/org/mozilla/javascript/RegExpProxy.java | 71 +
src/org/mozilla/javascript/RhinoException.java | 308 +
src/org/mozilla/javascript/Script.java | 73 +
src/org/mozilla/javascript/ScriptOrFnNode.java | 230 +
src/org/mozilla/javascript/ScriptRuntime.java | 3925 +++++++
src/org/mozilla/javascript/Scriptable.java | 342 +
src/org/mozilla/javascript/ScriptableObject.java | 2522 +++++
src/org/mozilla/javascript/SecureCaller.java | 196 +
src/org/mozilla/javascript/SecurityController.java | 211 +
src/org/mozilla/javascript/SecurityUtilities.java | 80 +
src/org/mozilla/javascript/SpecialRef.java | 155 +
src/org/mozilla/javascript/Synchronizer.java | 82 +
src/org/mozilla/javascript/Token.java | 434 +
src/org/mozilla/javascript/TokenStream.java | 1463 +++
src/org/mozilla/javascript/UintMap.java | 659 ++
src/org/mozilla/javascript/Undefined.java | 60 +
src/org/mozilla/javascript/UniqueTag.java | 121 +
src/org/mozilla/javascript/VMBridge.java | 183 +
src/org/mozilla/javascript/WrapFactory.java | 183 +
src/org/mozilla/javascript/WrappedException.java | 93 +
src/org/mozilla/javascript/Wrapper.java | 58 +
src/org/mozilla/javascript/debug/DebugFrame.java | 91 +
.../mozilla/javascript/debug/DebuggableObject.java | 61 +
.../mozilla/javascript/debug/DebuggableScript.java | 119 +
src/org/mozilla/javascript/debug/Debugger.java | 69 +
.../mozilla/javascript/jdk13/VMBridge_jdk13.java | 165 +
.../mozilla/javascript/jdk15/VMBridge_jdk15.java | 89 +
src/org/mozilla/javascript/optimizer/Block.java | 619 ++
.../javascript/optimizer/ClassCompiler.java | 211 +
src/org/mozilla/javascript/optimizer/Codegen.java | 5048 +++++++++
.../javascript/optimizer/DataFlowBitSet.java | 135 +
.../javascript/optimizer/OptFunctionNode.java | 149 +
.../mozilla/javascript/optimizer/OptRuntime.java | 311 +
.../javascript/optimizer/OptTransformer.java | 135 +
.../mozilla/javascript/optimizer/Optimizer.java | 509 +
.../mozilla/javascript/regexp/NativeRegExp.java | 2792 +++++
.../javascript/regexp/NativeRegExpCtor.java | 297 +
src/org/mozilla/javascript/regexp/RegExpImpl.java | 541 +
src/org/mozilla/javascript/regexp/SubString.java | 76 +
.../javascript/resources/Messages.properties | 781 ++
.../javascript/resources/Messages_fr.properties | 329 +
.../serialize/ScriptableInputStream.java | 114 +
.../serialize/ScriptableOutputStream.java | 220 +
src/org/mozilla/javascript/xml/XMLLib.java | 173 +
src/org/mozilla/javascript/xml/XMLObject.java | 128 +
tests/README | 6 +
tests/_munge.js | 8 +
tests/_munge.js.min | 1 +
tests/_string_combo.js | 5 +
tests/_string_combo.js.min | 1 +
tests/_syntax_error.js | 73 +
tests/_syntax_error.js.min | 1 +
tests/background-position.css | 2 +
tests/background-position.css.min | 1 +
tests/border-none.css | 10 +
tests/border-none.css.min | 1 +
tests/box-model-hack.css | 9 +
tests/box-model-hack.css.min | 1 +
tests/bug2527974.css | 10 +
tests/bug2527974.css.min | 1 +
tests/bug2527991.css | 19 +
tests/bug2527991.css.min | 1 +
tests/bug2527998.css | 4 +
tests/bug2527998.css.min | 1 +
tests/bug2528034.css | 5 +
tests/bug2528034.css.min | 1 +
tests/charset-media.css | 9 +
tests/charset-media.css.min | 1 +
tests/color-keyword.css | 1 +
tests/color-keyword.css.min | 1 +
tests/color-simple.css | 8 +
tests/color-simple.css.min | 1 +
tests/color.css | 47 +
tests/color.css.min | 1 +
tests/comment.css | 3 +
tests/comment.css.min | 1 +
tests/concat-charset.css | 15 +
tests/concat-charset.css.min | 1 +
tests/dataurl-base64-doublequotes.css | 23 +
tests/dataurl-base64-doublequotes.css.min | 1 +
tests/dataurl-base64-eof.css | 10 +
tests/dataurl-base64-eof.css.min | 1 +
tests/dataurl-base64-linebreakindata.css | 34 +
tests/dataurl-base64-linebreakindata.css.min | 1 +
tests/dataurl-base64-noquotes.css | 26 +
tests/dataurl-base64-noquotes.css.min | 1 +
tests/dataurl-base64-singlequotes.css | 23 +
tests/dataurl-base64-singlequotes.css.min | 1 +
tests/dataurl-base64-twourls.css | 27 +
tests/dataurl-base64-twourls.css.min | 1 +
tests/dataurl-dbquote-font.css | 30 +
tests/dataurl-dbquote-font.css.min | 5 +
tests/dataurl-nonbase64-doublequotes.css | 13 +
tests/dataurl-nonbase64-doublequotes.css.min | 1 +
tests/dataurl-nonbase64-noquotes.css | 11 +
tests/dataurl-nonbase64-noquotes.css.min | 1 +
tests/dataurl-nonbase64-singlequotes.css | 15 +
tests/dataurl-nonbase64-singlequotes.css.min | 2 +
tests/dataurl-noquote-multiline-font.css | 31 +
tests/dataurl-noquote-multiline-font.css.min | 3 +
tests/dataurl-realdata-doublequotes.css | 90 +
tests/dataurl-realdata-doublequotes.css.min | 1 +
tests/dataurl-realdata-noquotes.css | 90 +
tests/dataurl-realdata-noquotes.css.min | 1 +
tests/dataurl-realdata-singlequotes.css | 90 +
tests/dataurl-realdata-singlequotes.css.min | 1 +
tests/dataurl-realdata-yuiapp.css | 106 +
tests/dataurl-realdata-yuiapp.css.min | 1 +
tests/dataurl-singlequote-font.css | 30 +
tests/dataurl-singlequote-font.css.min | 3 +
tests/dataurl-validity.html | 29 +
tests/decimals.css | 3 +
tests/decimals.css.min | 1 +
tests/dollar-header.css | 7 +
tests/dollar-header.css.min | 3 +
tests/float.js | 2 +
tests/float.js.min | 1 +
tests/font-face.css | 6 +
tests/font-face.css.min | 1 +
tests/ie5mac.css | 5 +
tests/ie5mac.css.min | 1 +
tests/jquery-1.6.4.js | 9046 +++++++++++++++
tests/jquery-1.6.4.js.min | 23 +
tests/lowercasing.css | 63 +
tests/lowercasing.css.min | 1 +
tests/media-empty-class.css | 16 +
tests/media-empty-class.css.min | 1 +
tests/media-multi.css | 3 +
tests/media-multi.css.min | 1 +
tests/media-test.css | 3 +
tests/media-test.css.min | 1 +
tests/opacity-filter.css | 14 +
tests/opacity-filter.css.min | 1 +
tests/opera-pixel-ratio.css | 14 +
tests/opera-pixel-ratio.css.min | 1 +
tests/preserve-case.css | 15 +
tests/preserve-case.css.min | 1 +
tests/preserve-important.css | 1 +
tests/preserve-important.css.min | 1 +
tests/preserve-new-line.css | 6 +
tests/preserve-new-line.css.min | 3 +
tests/preserve-strings.css | 7 +
tests/preserve-strings.css.min | 1 +
tests/pseudo-first.css | 16 +
tests/pseudo-first.css.min | 1 +
tests/pseudo.css | 4 +
tests/pseudo.css.min | 1 +
tests/special-comments.css | 13 +
tests/special-comments.css.min | 9 +
tests/star-underscore-hacks.css | 5 +
tests/star-underscore-hacks.css.min | 1 +
tests/string-in-comment.css | 8 +
tests/string-in-comment.css.min | 1 +
tests/suite.rhino | 3 +
tests/suite.sh | 62 +
tests/webkit-transform.css | 2 +
tests/webkit-transform.css.min | 1 +
tests/zeros.css | 12 +
tests/zeros.css.min | 1 +
tools/cssmin-debugger.html | 91 +
yinst/yuicompressor | 3 +
yinst/yuicompressor.yicf | 22 +
261 files changed, 90755 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..515f69e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+build_tmp/
+node_modules
+.DS_Store
+.*.swp
+.svn
+CVS/
+# Ignore all backup files
+*~
+# Ignore Emacs auto saved files
+*#*#
+build/classes
+build/jar
+build/yuicompressor*.jar
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..d0992c0
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,9 @@
+language: java
+jdk:
+ - openjdk6
+install:
+ - ant
+ - npm install
+script:
+ - ./tests/suite.sh
+ - npm test
\ No newline at end of file
diff --git a/LICENSE.TXT b/LICENSE.TXT
new file mode 100644
index 0000000..8a1c7a1
--- /dev/null
+++ b/LICENSE.TXT
@@ -0,0 +1,54 @@
+YUI Compressor Copyright License Agreement (BSD License)
+
+Copyright (c) 2013, Yahoo! Inc.
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+* Neither the name of Yahoo! Inc. nor the names of its
+ contributors may be used to endorse or promote products
+ derived from this software without specific prior
+ written permission of Yahoo! Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+This software also requires access to software from the following sources:
+
+The Jarg Library v 1.0 ( http://jargs.sourceforge.net/ ) is available
+under a BSD License � Copyright (c) 2001-2003 Steve Purcell,
+Copyright (c) 2002 Vidar Holen, Copyright (c) 2002 Michal Ceresna and
+Copyright (c) 2005 Ewan Mellor.
+
+The Rhino Library ( http://www.mozilla.org/rhino/ ) is dually available
+under an MPL 1.1/GPL 2.0 license, with portions subject to a BSD license.
+
+Additionally, this software contains modified versions of the following
+component files from the Rhino Library:
+
+[org/mozilla/javascript/Decompiler.java]
+[org/mozilla/javascript/Parser.java]
+[org/mozilla/javascript/Token.java]
+[org/mozilla/javascript/TokenStream.java]
+
+The modified versions of these files are distributed under the MPL v 1.1
+( http://www.mozilla.org/MPL/MPL-1.1.html )
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..159190f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,188 @@
+YUI Compressor - The Yahoo! JavaScript and CSS Compressor
+=========================================================
+
+The YUI Compressor is a JavaScript compressor which, in addition to removing
+comments and white-spaces, obfuscates local variables using the smallest
+possible variable name. This obfuscation is safe, even when using constructs
+such as 'eval' or 'with' (although the compression is not optimal is those
+cases) Compared to jsmin, the average savings is around 20%.
+
+The YUI Compressor is also able to safely compress CSS files. The decision
+on which compressor is being used is made on the file extension (js or css)
+
+Building
+--------
+
+ ant
+
+Testing
+-------
+
+ ./tests/suite.sh
+
+
+Node.js Package
+---------------
+
+You can require compressor in a Node.js package and compress files and strings in async.
+_It still uses Java under the hood_
+
+ npm i yuicompressor
+
+```javascript
+
+var compressor = require('yuicompressor');
+
+compressor.compress('/path/to/file or String of JS', {
+ //Compressor Options:
+ charset: 'utf8',
+ type: 'js',
+ nomunge: true,
+ 'line-break': 80
+}, function(err, data, extra) {
+ //err If compressor encounters an error, it's stderr will be here
+ //data The compressed string, you write it out where you want it
+ //extra The stderr (warnings are printed here in case you want to echo them
+});
+
+```
+
+Options:
+* `charset` // defaults to 'utf8'
+* `type` // defaults to 'js'
+* `line-break`
+* `nomunge`
+* `preserve-semi`
+* `disable-optimizations`
+
+
+TODO
+----
+
+* Better Docs
+* Help Pages
+
+Build Status
+------------
+
+[![Build Status](https://secure.travis-ci.org/yui/yuicompressor.png?branch=master)](http://travis-ci.org/yui/yuicompressor)
+
+
+Global Options
+--------------
+
+ -h, --help
+ Prints help on how to use the YUI Compressor
+
+ --line-break
+ Some source control tools don't like files containing lines longer than,
+ say 8000 characters. The linebreak option is used in that case to split
+ long lines after a specific column. It can also be used to make the code
+ more readable, easier to debug (especially with the MS Script Debugger)
+ Specify 0 to get a line break after each semi-colon in JavaScript, and
+ after each rule in CSS.
+
+ --type js|css
+ The type of compressor (JavaScript or CSS) is chosen based on the
+ extension of the input file name (.js or .css) This option is required
+ if no input file has been specified. Otherwise, this option is only
+ required if the input file extension is neither 'js' nor 'css'.
+
+ --charset character-set
+ If a supported character set is specified, the YUI Compressor will use it
+ to read the input file. Otherwise, it will assume that the platform's
+ default character set is being used. The output file is encoded using
+ the same character set.
+
+ -o outfile
+
+ Place output in file outfile. If not specified, the YUI Compressor will
+ default to the standard output, which you can redirect to a file.
+ Supports a filter syntax for expressing the output pattern when there are
+ multiple input files. ex:
+ java -jar yuicompressor.jar -o '.css$:-min.css' *.css
+ ... will minify all .css files and save them as -min.css
+
+ -v, --verbose
+ Display informational messages and warnings.
+
+JavaScript Only Options
+-----------------------
+
+ --nomunge
+ Minify only. Do not obfuscate local symbols.
+
+ --preserve-semi
+ Preserve unnecessary semicolons (such as right before a '}') This option
+ is useful when compressed code has to be run through JSLint (which is the
+ case of YUI for example)
+
+ --disable-optimizations
+ Disable all the built-in micro optimizations.
+
+Notes
+-----
+
+* If no input file is specified, it defaults to stdin.
+
+* Supports wildcards for specifying multiple input files.
+
+* The YUI Compressor requires Java version >= 1.5.
+
+* It is possible to prevent a local variable, nested function or function
+argument from being obfuscated by using "hints". A hint is a string that
+is located at the very beginning of a function body like so:
+
+```
+function fn (arg1, arg2, arg3) {
+ "arg2:nomunge, localVar:nomunge, nestedFn:nomunge";
+
+ ...
+ var localVar;
+ ...
+
+ function nestedFn () {
+ ....
+ }
+
+ ...
+}
+```
+The hint itself disappears from the compressed file.
+
+* C-style comments starting with `/*!` are preserved. This is useful with
+ comments containing copyright/license information. For example:
+
+```
+/*!
+ * TERMS OF USE - EASING EQUATIONS
+ * Open source under the BSD License.
+ * Copyright 2001 Robert Penner All rights reserved.
+ */
+```
+
+becomes:
+
+```
+/*
+ * TERMS OF USE - EASING EQUATIONS
+ * Open source under the BSD License.
+ * Copyright 2001 Robert Penner All rights reserved.
+ */
+```
+
+Modified Rhino Files
+--------------------
+
+YUI Compressor uses a modified version of the Rhino library
+(http://www.mozilla.org/rhino/) The changes were made to support
+JScript conditional comments, preserved comments, unescaped slash
+characters in regular expressions, and to allow for the optimization
+of escaped quotes in string literals.
+
+Copyright And License
+---------------------
+
+Copyright (c) 2013 Yahoo! Inc. All rights reserved.
+The copyrights embodied in the content of this file are licensed
+by Yahoo! Inc. under the BSD (revised) open source license.
diff --git a/ant.properties b/ant.properties
new file mode 100644
index 0000000..4ab2250
--- /dev/null
+++ b/ant.properties
@@ -0,0 +1,8 @@
+src.dir = src
+lib.dir = lib
+doc.dir = doc
+build.dir = build
+product.name = yuicompressor
+version.number = 2.4.8
+jar.name = ${product.name}-${version.number}.jar
+dist.package.name = ${product.name}-${version.number}
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..a2663b3
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<project name="YUI Compressor" default="build.jar" basedir=".">
+
+ <target name="clean" depends="-load.properties">
+ <delete dir="${build.dir}" quiet="true"/>
+ </target>
+
+ <target name="-load.properties">
+ <property file="ant.properties"/>
+ </target>
+
+ <target name="-init" depends="-load.properties">
+ <delete dir="${build.dir}"/>
+ <mkdir dir="${build.dir}"/>
+ </target>
+
+ <target name="-updateversion" depends="-init">
+ <copy todir="${build.dir}/build_tmp">
+ <fileset dir="${src.dir}"/>
+ <filterchain>
+ <replacetokens>
+ <token key="VERSION" value="${version.number}"/>
+ </replacetokens>
+ </filterchain>
+ </copy>
+ </target>
+
+ <target name="build.classes" depends="-init, -updateversion">
+ <mkdir dir="${build.dir}/classes"/>
+ <javac srcdir="${build.dir}/build_tmp"
+ destdir="${build.dir}/classes"
+ includes="**/*.java"
+ deprecation="off"
+ debug="on"
+ includeantruntime="false"
+ target="1.5"
+ source="1.5">
+ <classpath>
+ <pathelement location="${lib.dir}/jargs-1.0.jar"/>
+ <pathelement location="${lib.dir}/rhino-1.7R2.jar"/>
+ </classpath>
+ </javac>
+ </target>
+
+ <target name="build.jar" depends="build.classes">
+ <mkdir dir="${build.dir}/jar"/>
+ <!-- The order is important here. Rhino MUST be unjarred first!
+ (some of our own classes will override the Rhino classes) -->
+ <unjar src="${lib.dir}/jargs-1.0.jar" dest="${build.dir}/jar"/>
+ <unjar src="${lib.dir}/rhino-1.7R2.jar" dest="${build.dir}/jar"/>
+ <copy todir="${build.dir}/jar">
+ <fileset dir="${build.dir}/classes" includes="**/*.class"/>
+ </copy>
+ <jar destfile="${build.dir}/${jar.name}" basedir="${build.dir}/jar">
+ <manifest>
+ <attribute name="Main-Class" value="com.yahoo.platform.yui.compressor.Bootstrap"/>
+ </manifest>
+ </jar>
+ </target>
+
+ <target name="build.dist.package" depends="build.jar">
+ <mkdir dir="${build.dir}/${dist.package.name}"/>
+ <mkdir dir="${build.dir}/${dist.package.name}/build"/>
+ <copy file="${build.dir}/${jar.name}" todir="${build.dir}/${dist.package.name}/build"/>
+ <copy todir="${build.dir}/${dist.package.name}">
+ <fileset dir=".">
+ <include name="ant.properties"/>
+ <include name="build.xml"/>
+ <include name="doc/**/*"/>
+ <include name="lib/**/*"/>
+ <include name="src/**/*"/>
+ <exclude name="**/CVS"/>
+ </fileset>
+ </copy>
+ <zip destfile="${build.dir}/${dist.package.name}.zip"
+ basedir="${build.dir}"
+ includes="${dist.package.name}/**/*"/>
+ </target>
+
+</project>
diff --git a/doc/CHANGELOG b/doc/CHANGELOG
new file mode 100644
index 0000000..da32634
--- /dev/null
+++ b/doc/CHANGELOG
@@ -0,0 +1,277 @@
+YUI Compressor 2.4.8
+--------------------
++ Fix "important" and conditional comment processing
++ Fix a bug in the support for JS 1.7 style getters/setters
++ Better compliance and results in CSS compression (@danbeam,
+ @faisalman, @killsaw, @ademey)
++ Now minifies "border-left" in CSS (@sbertrang)
++ Include filename in warning and error output (@danielbeardsley)
++ Many improvements to parameter parsing and batch modes (@bmouw,
+ @bandesz, @ryansully)
++ Include jQuery as part of our test suite (@apm)
++ Trim trailing commas where possible (@nlalevee)
+
+YUI Compressor 2.4.7
+--------------------
++ Handle data urls without blowing up Java memory (regex)
++ Updated docs to reflect Java >= 1.5 required for CssCompressor
++ Fixed issue where we were breaking #AABBCC id selectors, with the
+ #AABBCC -> #ABC color compression.
+
+YUI Compressor 2.4.6
+--------------------
++ Show usage information when started without arguments.
+
+YUI Compressor 2.4.5
+--------------------
++ Default file encoding changed from system default to UTF-8.
++ Errors/messages/usage info all are sent to stderr.
++ Removed unnecessary warning about short undeclared global symbols.
++ Added support for processing multiple files with a single invokation
++ $ in CSS files doesn't throw exceptions
++ white space in ! important comments preserved in CSS
++ fix in greedy empty CSS declaration blocks removal
++ safe handling of strings and comments in CSS files
++ shorter alpha opacity CSS filers
++ shorter Mac/IE5 hack -> /*\*/ hack {mac: 1} /**/
++ JS port of the CSS minifier
++ safe @media queries handling
++ stripping the trailing ; in CSS declaration blocks
++ shorter border:none->0 where applicable
++ fixed transform-origin: 0 0 [bug 2528060]
++ tests++
+
+YUI Compressor 2.4.4
+--------------------
++ Interim 2.4.5 release
+
+YUI Compressor 2.4.3
+--------------------
++ Changed custodian to ci-tools@
+
+YUI Compressor 2.4.2
+--------------------------------
++ Preserved comments shouldn't prevent obfuscation (Thanks to Matjaz Lipus)
+
+
+YUI Compressor 2.4.1
+--------------------
+
++ Use preferentially lower case letters for obfuscated variable names.
+ Since JavaScript keywords use lower case letters most often, this
+ improves the efficiency of any compression algorithm (gzipping)
+ used after minification.
++ Don't append a semi-colon at the end of a JavaScript file when the
+ last token is a special comment.
+
+YUI Compressor 2.4
+------------------
+
++ Allowed the YUI Compressor (which uses a modified version of Rhino)
+ to work alongside the original (unmodified) rhino library by using
+ a custom class loader.
++ Added all that's necessary to build the YUI Compressor to the
+ downloable package.
++ Fixed unnecessary white space after return / typeof when possible.
+
+YUI Compressor 2.3.6
+--------------------
+
++ Fixed a few minor bugs with the CSS compressor
++ Changed packaging. The original Rhino library, which is used to build the
+ YUI Compressor, is not part of the downloadable archive. Too many people
+ put it in their classpath, generating a lot of invalid bugs.
+
+YUI Compressor 2.3.5
+--------------------
+
++ Added a warning when more than one 'var' statement is used in a single scope.
+ Automatic coalescence is extremely complicated, and would be unsafe if not
+ done properly.
+
+YUI Compressor 2.3.4
+--------------------
+
++ Expanded the list of reserved words used by isValidIdentifier()
+
+YUI Compressor 2.3.3
+--------------------
+
++ C-style comments starting with /*! are preserved. This is especially
+ useful with comments containing copyright/license information.
+
+YUI Compressor 2.3.2
+--------------------
+
++ Compressing an empty JS file throws an error [SourceForge bug #1884207]
++ When a string is the first token in a function body, it was removed from
+ the compressed file [SourceForge bug #1884314]
+
+YUI Compressor 2.3.1
+--------------------
+
++ Added test against list of reserved words in method isValidIdentifier.
+
+YUI Compressor 2.3
+------------------
+
++ Always output a ';' at the end of a minified JavaScript file. This allows
+ the concatenating of several minified files without the fear of introducing
+ a syntax error.
++ Removed all System.exit() statements. Throw exceptions instead. This is
+ especially useful when running the compressor from within a J2EE container.
+ [SourceForge bug #1834750]
++ Transform obj["foo"] into obj.foo whenever possible, saving 3 bytes.
++ Transform 'foo': ... into foo: ... whenever possible, saving 2 bytes.
++ Added support for multi-line string literals [SourceForge bug #1871453]
++ Added support for unescaped slashes inside character classes in regexp.
++ Minor performance improvements.
++ Preserve the escaping for an octal representation of a character in string
+ literals [SourceForge bug #1844894]
+
+ var a = '\001';
+
++ CSS: Preserve comments that hide CSS rules from IE Mac:
+
+ /* Hides from IE-mac \*/
+ ...
+ /* End hide from IE-mac */
+
++ CSS: Added support for box model hack [SourceForge bug #1862107]
+
+ div.content {
+ width:400px;
+ voice-family: "\"}\"";
+ voice-family:inherit;
+ width:300px;
+ }
+
+YUI Compressor 2.2.5
+--------------------
+
++ Remove line terminator after escape in string literals.
+
+YUI Compressor 2.2.4
+--------------------
+
++ Fixed the way quote characters are counted in string literals
+ [SourceForge bug #1804576]
++ Do not use a regular expression using non-greedy matching to remove CSS
+ comments (if the comment is more than 800 characters long or so, a stack
+ overflow exception gets thrown) Instead, use good old parsing...
++ Fix unnecessary quote escaping in string literals.
+
+YUI Compressor 2.2.3
+--------------------
+
++ Transform </script into <\/script instead of replacing all </ into <\/.
++ Fixed bug related to the shortening of hexadecimal color codes (the string
+ "1px solid #aabbcc" became "1px solid#abc", missing a required white space)
++ Added --preserve-strings option to specify that concatenated string literals
+ should never be merged.
++ Do not convert \uXXXX and \xXX escape sequences to their unicode equivalent.
+
+YUI Compressor 2.2.2
+--------------------
+
++ Fixed regression related to the optimization of the amount of escaping
+ in string literals and the concatenation of string literals.
++ Modified the Rhino tokenizer to handle JScript conditional comments
+ natively (instead of hacking around the fact that Rhino is not keeping
+ track of comments)
++ Transform </ into <\/ in string literals. This is especially useful if the
+ code is written to a script block in an HTML document. This renders the old
+ hack '<scr'+'ipt ...><'+'/script>' completely useless.
++ When converting decimal rgb color values to hexadecimal color values,
+ prepend a '0' if the value is less than 16. Otherwise, rgb(0,124,114)
+ for instance becomes #07c72, which is incorrect.
++ In CSS files, do not change color names into their corresponding color
+ codes (and vice-versa) due to the high potential of introducing bugs
+ (rolled back from 2.2.1)
+
+YUI Compressor 2.2.1
+--------------------
+
++ Optimize quote escaping in JavaScript string literals by using the best quote
+ character (' or " depending on the occurrence of this character in the string)
++ Fixed minor bug in the CSS compressor. Colors should not be shortened in
+ filter: chroma(color="#FFFFFF");
+ Otherwise, it makes the filter break in Internet Explorer.
++ In CSS files, change color names into their corresponding color codes
+ (and vice-versa) if that change yields any savings.
+
+YUI Compressor 2.2
+------------------
+
++ Don't obfuscate function argument named $super if it is the first function
+ argument listed. This is to support Prototype 1.6's heretic implementation.
++ Added support for stdin/stdout (see README for more info)
++ Shorten colors from rgb(51,102,153) to #336699 in CSS files.
++ Shorten values from 0.8em to .8em in CSS files.
++ Added support for Internet Explorer's conditional comments in JavaScript
+ files. Note that the presence of a conditional comment inside a function
+ (i.e. not in the global scope) will reduce the level of compression for the
+ same reason the use of 'eval' or 'with' reduces the level of compression
+ (conditional comments, which do not get parsed, may refer to local variables,
+ which get obfuscated) In any case, the use of Internet Explorer's conditional
+ comment is to be avoided.
+
+YUI Compressor 2.1.2
+--------------------
+
++ Added --preserve-semi option
++ Modified --line-break option
+
+YUI Compressor 2.1.1
+--------------------
+
++ Fixed missing space in CSS background:url('foo.png')no-repeat
+ causing a background not to appear on Internet Explorer.
+
+YUI Compressor 2.1
+------------------
+
++ Pass the --line-break option to the CSS compressor.
++ Allow the output file to overwrite the input file (with version 2.0,
+ in this case, the output file was always empty)
++ Remove spaces before and after '(' and ')' as in background:url('xxx');
++ Merge (if possible) string literals that are appended in JavaScript files.
+ This not only makes the code smaller, it makes the code faster,
+ but allows you to maintain some readability in your source code.
++ Handle constructs such as a + ++ b or a + + "1" (in which case the
+ space between the operators must be kept!) and other similar cases...
++ Pass ErrorReporter instance to the constructor of class JavaScriptCompressor
+ (as suggested by David Bernard for his integration of the YUI Compressor
+ as a maven plugin)
+
+YUI Compressor 2.0
+------------------
+
++ Switched from Rhino 1.6R6 to Rhino 1.6R7
++ Integrated Isaac Schlueter's CSS compressor.
++ Refactored code to make it easier to use the compressor from a servlet
+ environment or another Java app (no need to pass in file names anymore)
++ Output a white-space character after 'throw' only when necessary.
++ Output a white-space character after 'break' and 'continue' when followed
+ by a label.
+
+YUI Compressor 1.1
+------------------
+
++ Java source now in package com.yahoo.platform.yui.compressor
++ Added --line-break option that adds a line feed character after each
+ semi-colon character (may help debugging with the MS Script debugger)
++ Added support for missing JavaScript features (get, set, const)
++ Do not show the entire stack trace when the input file cannot be found.
++ Removed the randomization of obfuscated symbols. When compressed code is
+ checked in CVS, unchanged files would otherwise end up being versioned.
++ Added web-based front-end to the YUI Compressor as part of the dist package.
++ Added a public entry point that makes the YUI Compressor easy to integrate
+ with an already existing Java application.
++ Simplified code by using the same parsing routines used to build the symbol
+ tree while looking for undeclared symbols.
++ Count how many times each identifier is used, and display a warning when an
+ identifier seems to be unused (code cannot safely be removed automatically)
++ Remove ';' when followed by a '}'. This yields an additional ~1.5% savings
+ on yahoo-dom-event.js compared to the JSMin version.
++ Output a white-space character after 'return' and 'case' only when necessary.
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..c0e8d01
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,32 @@
+YUI Compressor Docs
+===================
+
+
+These docs are what powers http://yui.github.com/yuicompressor
+
+They are powered by [Selleck](http://yui.github.com/selleck).
+
+```terminal
+$ npm -g install selleck
+```
+
+How to build them
+-----------------
+
+```terminal
+$ git clone git://github.com/yui/yuicompressor.git
+$ git clone git://github.com/yui/yuicompressor.git yuicompressor-pages
+
+cd yuicompressor-pages
+git fetch
+git checkout -t gh-pages
+```
+
+This should pull the `gh-pages` branch locally.
+
+```terminal
+cd ../yuicompressor/docs/
+selleck -o ../../yuicompressor-pages/
+```
+
+Done!
diff --git a/docs/css.mustache b/docs/css.mustache
new file mode 100644
index 0000000..5a97903
--- /dev/null
+++ b/docs/css.mustache
@@ -0,0 +1,460 @@
+<p>
+ This document describes how the CSS minification part of the YUICompressor works.
+</p>
+<h2 class="first" id="min">CSS minifications</h2>
+
+<p>
+This section describes the various ways that YUICompressor makes your CSS smaller.
+</p>
+
+<h3>Stripping comments and white space</h3>
+<p>
+YUICompressor strips all the comments and white space that are not required
+for the CSS to work.
+</p>
+
+<p>Before the minification:</p>
+
+```
+/*****
+ Multi-line comment
+ before a new class name
+*****/
+.classname {
+ /* comment in declaration block */
+ font-weight: normal;
+}
+```
+<p>After the minification:</p>
+
+```
+.classname{font-weight:normal}
+```
+<h3>Special comments</h3>
+
+<p>
+Stripping comments is not always acceptable.
+Sometimes you need to retain copyright information.
+You can use ! at the beginning of the comment to mark the comment as special
+and it will be preserved.
+</p>
+
+<p>Before:</p>
+
+```
+/*!
+ (c) Very Important Comment
+*/
+.classname {
+ /* comment in declaration block */
+ font-weight: normal;
+}
+```
+<p>After:</p>
+
+```
+/*!
+ (c) Very Important Comment
+*/.classname{font-weight:normal}
+```
+
+ <p>
+ The ! character itself is also preserved. This way you can safely double-minify the same file (probably by mistake).
+ That also allows when reviewing the code (manually or with tools) to see !
+ and conclude that this comment is there intentionally, not because you forgot to minify the CSS.
+ </p>
+
+ <h3>Striping last semi-colon</h3>
+
+ <p>
+ The last semi-colon in a declaration block is not needed.
+ You can keep it in your source for maintenance purposes and
+ let the minifier take care of stripping it out before the site goes live.
+ </p>
+
+ <p>Before:</p>
+
+```
+.classname {
+ border-top: 1px;
+ border-bottom: 2px;
+}
+```
+
+<p>After:</p>
+
+```
+.classname{border-top:1px;border-bottom:2px}
+```
+
+
+<h3>Extra semi-colons</h3>
+
+<p>Only one semi-colon is required to separate rules, so the minifier will strip an accidentally added one.</p>
+
+<p>Before:</p>
+
+```
+.classname {
+ border-top: 1px; ;
+ border-bottom: 2px;;;
+}
+```
+
+<p>After:</p>
+
+```
+.classname{border-top:1px;border-bottom:2px}
+```
+
+<h3>No empty declarations</h3>
+
+<p>
+ Empty declaration blocks don't contribute to the way the stylesheets work,
+ so they can safely be removed.
+</p>
+
+<p>Before:</p>
+
+```
+.empty { ;}
+.nonempty {border: 0;}
+```
+
+ <p>After:</p>
+
+```
+.nonempty{border:0}
+```
+
+<h3>Zero values</h3>
+
+<p>
+ When using zero values, the units of measure are not required, so the YUICompressor will strip them.
+ Additionally, when you have two, three of four zeros in margins and paddings, they can be reduced to one.
+</p>
+
+<p>Before:</p>
+```
+a {
+ margin: 0px 0pt 0em 0%;
+ background-position: 0 0ex;
+ padding: 0in 0cm 0mm 0pc
+}
+```
+
+<p>After:</p>
+
+
+```
+a{margin:0;background-position:0 0;padding:0}
+```
+
+<h3>Floats</h3>
+
+<p>
+ When a value is using a floating point number smaller than 1, the leading 0 is not required.
+</p>
+
+<p>Before:</p>
+
+```
+.classname {
+ margin: 0.6px 0.333pt 1.2em 8.8cm;
+}
+```
+
+<p>After:</p>
+
+```
+.classname{margin:.6px .333pt 1.2em 8.8cm}
+```
+
+<h3>Colors values</h3>
+
+<p>
+ RGB color values are easier to read, but the hex values are shorter, so YUICompressor will use the
+ more concise form.
+
+ Addiotionally, colors that follow the pattern #AABBCC can be reduced to #ABC, except when used
+ in IE filter values.
+
+<p>Before:</p>
+
+```
+.color-me {
+ color: rgb(123, 123, 123);
+ border-color: #ffeedd;
+ background: none repeat scroll 0 0 rgb(255, 0,0);
+}
+```
+
+<p>After:</p>
+
+```
+.color-me{color:#7b7b7b;border-color:#fed;background:none repeat scroll 0 0 #f00}
+```
+
+<p>Before:</p>
+
+```
+.cantouch {
+ color: rgba(1, 2, 3, 4);
+ filter: chroma(color="#FFFFFF");
+}
+```
+
+<p>After (no color minification):</p>
+
+```
+.cantouch{color:rgba(1,2,3,4);filter:chroma(color="#FFFFFF")}
+```
+
+<h3>Single charsets</h3>
+
+<p>
+ Only one charset declaration is allowed per stylesheet, but sometimes when automatically merging
+ stylesheets into one to <a href="http://developer.yahoo.com/performance/">reduce HTTP requests</a>,
+ you may end up with more than one charset.
+ YUICompressor will keep only the first.
+</p>
+
+<p>Before:</p>
+
+```
+ at charset "utf-8";
+#foo {
+ border-width: 1px;
+}
+
+/* second css, merged */
+ at charset "another one";
+#bar {
+ border-width: 10px;
+}
+```
+
+<p>After:</p>
+
+```
+ at charset "utf-8";#foo{border-width:1px}#bar{border-width:10px}
+```
+
+
+<h3>Alpha opacity</h3>
+
+<p>
+ The opacity filter in IE has a verbose syntax,
+ which can be shortened using the IE4 syntax
+</p>
+
+<p>Before:</p>
+
+```
+.classname {
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; /* IE 8 */
+ filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); /* IE < 8 */
+}
+```
+<p>After:</p>
+
+```
+.classname{-ms-filter:"alpha(opacity=80)";filter:alpha(opacity=80)}
+```
+
+<h3>Shorter `none` values</h3>
+
+<p>
+ For some properties `none` and `0`
+ have the same meaning. Instances of "none" are converted to 0 where
+ applicable and safe to do so.
+</p>
+
+<p>Before:</p>
+
+```
+.classname {
+ border: none;
+ background: none;
+ outline: none;
+}
+```
+
+<p>After:</p>
+
+```
+.classname{border:0;background:0;outline:0}
+```
+
+<h2 class="first" id="hacks">CSS hacks tolerance</h2>
+
+<p>
+ When stripping comments and performing other minification tasks,
+ the YUICompressor keeps an eye on preserving common CSS hacks.
+ CSS hacks often use parsing glitches in browsers to provide some extra declarations
+ (or hide some) from specific browser versions.
+</p>
+
+<p>
+ This section describes the hacks that are tolerated by the YUICompressor.
+</p>
+
+
+<h3>Underscore/star hack</h3>
+
+<p>
+ Using _ and * to prefix CSS properties is a simple way to target IE6 and IE7.
+</p>
+
+<p>
+ Since YUICompressor uses a regular expression based CSS minifier and doesn't validate CSS properties,
+ it can accept pretty much any property. Therefore this hack is preserved.
+</p>
+
+<p>Before:</p>
+
+```
+#element {
+ width: 1px;
+ *width: 2px;
+ _width: 3px;
+}
+```
+
+<p>After:</p>
+
+```
+#element{width:1px;*width:3pt;_width:2em}
+```
+
+<h3>Child selector hack</h3>
+
+<p>The child selector hack allows developers to hide declarations from IE7 and below using an empty comment.</p>
+
+<p>
+ YUICompressor strips comments but will make an exception and retain empty comments that
+ immediately follow the `>` character.
+</p>
+
+<p>Before:</p>
+
+```
+html >/**/ body p {
+ color: blue;
+}
+```
+
+<p>After:</p>
+
+```
+html>/**/body p{color:blue}
+```
+
+<h3>IE5/Mac hack</h3>
+
+<p>
+ There is a hack that targets IE5/Mac using a bug when parsing comments.
+</p>
+
+<p>
+ YUICompressor retains comments that look like they are using this hack, but it minifies the comments needed by the hack.
+</p>
+
+<p>Before:</p>
+
+```
+/* Ignore the next rule in IE mac \*/
+.selector {
+ color: khaki;
+}
+/* Stop ignoring in IE mac */
+```
+
+<p>After:</p>
+
+```
+/*\*/.selector{color:khaki}/**/
+```
+
+<h3>Box model hack</h3>
+
+<p>This hack uses valid CSS and there's no special use of comments so it's retained.</p>
+
+<p>Before:</p>
+
+```
+#elem {
+ width: 100px; /* IE */
+ voice-family: "\"}\"";
+ voice-family:inherit;
+ width: 200px; /* others */
+}
+html>body #elem {
+ width: 200px; /* others */
+}
+```
+
+<p>After:</p>
+
+```
+#elem{width:100px;voice-family:"\"}\"";voice-family:inherit;width:200px}html>body #elem{width:200px}
+```
+
+
+<h2 class="first" id="tests">Running and contributing tests</h2>
+
+<p>
+ When reporting bugs, it's greatly appreciated if you can provide a test case.
+ In order to create a test you can use your version of the compressor or build it from source.
+</p>
+
+<ol>
+ <li>
+ Checkout or download the code from <a href="http://github.com/yui/yuicompressor/">http://github.com/yui/yuicompressor/</a>.
+ You need the code even if you don't intend to build the tool yourself, because that's where the tests
+ and the test runner are.
+ </li>
+ <li>Navigate to the root `yuicompressor/` directory</li>
+ <li>Type `ant` and hit enter</li>
+</ol>
+<p>
+ In order for this to work you need a recent Java SDK installed (at least 1.4) and
+ also <a href="http://ant.apache.org/">Ant</a> running.
+ (On the Mac, you can simply do `port install apache-ant` to get Ant)</p>
+
+<p>
+ There are a number of tests in the `tests/` directory,
+ you can run them with the suite script:</p>
+
+```terminal
+cd tests/
+./suite.sh
+```
+
+<p>
+ This command will also run the tests using the JavaScript port of the minifier.
+ Since the compressor is using Mozilla's <a href="http://www.mozilla.org/rhino/">Rhino</a> (slightly modified),
+ Rhino is <a href="http://github.com/yui/yuicompressor/blob/master/lib/rhino-1.6R7.jar">part of the code</a>.
+ So it can be used as a JavaScript interpreter to run the JavaScript port.</p>
+
+<p>The procedure to write new tests is as follows:</p>
+<ol>
+ <li>Create source CSS file in the `tests/` directory, e.g. `new-test.css`</li>
+ <li>Create a new file with the expected result and name it with a `.min` extension, e.g. `new-test.css.min`</li>
+</ol>
+
+<h2 class="first" id="ports">JavaScript port</h2>
+
+<p>
+ The YUICompressor is written in Java, however there's a JavaScript port of the CSS minification part,
+ which is available separately.
+</p>
+
+<p>
+ There is a <a href="https://github.com/jbleuzen/node-cssmin">Node.js module</a> for cssmin using the JavaScript port of our minifier.
+ It can be installed with:
+</p>
+
+```terminal
+$ npm install cssmin
+```
diff --git a/docs/index.mustache b/docs/index.mustache
new file mode 100644
index 0000000..cf1fae9
--- /dev/null
+++ b/docs/index.mustache
@@ -0,0 +1,174 @@
+<p>
+ According to <a href="http://developer.yahoo.com/performance/">Yahoo!'s Exceptional Performance Team</a>,
+ 40% to 60% of Yahoo!'s users have an empty cache experience and about 20% of all page views are done with an empty cache
+ (see <a href="http://yuiblog.com/blog/2007/01/04/performance-research-part-2/">this article by Tenni Theurer on the YUIBlog for more
+ information on browser cache usage</a>). This fact outlines the importance of keeping web pages as lightweight as possible.
+ Improving the engineering design of a page or a web application usually yields the biggest savings and that should always be a primary strategy. With the right design in place, there are many secondary strategies for improving performance such as minification of the code, <a href="http://en.wikipedia.org/wiki/Http_compression">HTTP compression</a>, using CSS sprites, etc.
+</p>
+<p>
+ In terms of code minification, the most widely used tools to minify JavaScript code are Douglas Crockford's
+ <a href="http://crockford.com/javascript/jsmin">JSMIN</a>,
+ <a href="http://dojotoolkit.org/docs/shrinksafe">the Dojo compressor</a> and Dean Edwards'
+ <a href="http://dean.edwards.name/packer/">Packer</a>. Each of these tools, however, has drawbacks.
+ JSMIN, for example, does not yield optimal savings (due to its simple algorithm, it must leave many line feed characters in
+ the code in order not to introduce any new bugs).</p>
+<p>
+ The goal of JavaScript and CSS minification is always to preserve the operational qualities of the code while reducing its overall byte footprint (both in raw terms and after gzipping, as most JavaScript and CSS served from production web servers is gzipped as part of the HTTP protocol). The YUI Compressor is JavaScript minifier designed to be 100% safe and yield a higher compression ratio than most other tools. Tests on the <a href="http://yuilibrary.com/">YUI Library</a> have shown [...]
+ The YUI Compressor is also able to compress CSS files by using a port of <a href="http://foohack.com/">Isaac Schlueter</a>'s regular-expression-based CSS minifier.
+</p>
+
+<h2>Quick Links</h2>
+<ul>
+ <li><a href="https://github.com/yui/yuicompressor/blob/master/README.md">Documentation</a>: Detailed description of the YUI Compressor and how to use it.</li>
+ <li><a href="https://github.com/yui/yuicompressor/blob/master/doc/CHANGELOG">Release Notes</a>: Detailed change log for the YUI Compressor.</li>
+ <li><a href="css.html">CSS minification</a>: Description of the CSS minification performed by the compressor.</li>
+ <li><strong>License:</strong> All code specific to YUI Compressor is issued under a <a href="/yui/license.html">BSD license</a>. YUI Compressor extends and implements code from <a href="http://www.mozilla.org/rhino/">Mozilla's Rhino project</a>. Rhino is issued under the <a href="http://www.mozilla.org/MPL/">Mozilla Public License (MPL)</a>, and MPL applies to the Rhino source and binaries that are distributed with YUI Compressor, including Rhino modifications made by YUI Compressor. [...]
+ <li><a href="http://yuilibrary.com/downloads/#yuicompressor">Download</a>: Download the YUI Compressor.</li>
+</ul>
+
+<h2 class="first" id="video">Video: Yahoo engineer Julien Lecomte introduces the YUI Compressor</h2>
+
+<p><embed src='http://l.yimg.com/m/player/media/swf/FLVVideoSolo.swf' flashvars='id=4199776&emailUrl=http%3A%2F%2Fvideo.yahoo.com%2Futil%2Fmail%3Fei%3DUTF-8%26vid%3D1174737&imUrl=http%253A%252F%252Fvideo.yahoo.com%252Fvideo%252Fplay%253Fei%253DUTF-8%2526vid%253D1174737&imTitle=Julien%2BLecomte%253A%2B%2526quot%253BIntroducing%2Bthe%2BYUI%2BCompressor%2526quot%253B&searchUrl=http://video.yahoo.com/search/video?p=&profileUrl=http://video.yahoo.com/video/profile?yid=&creatorValue=ZXJpY21pcm [...]
+
+<h2 class="first" id="work">How does the YUI Compressor work?</h2>
+
+<p>
+ The YUI Compressor is written in <a href="http://java.sun.com/">Java</a> (requires Java >= 1.4)
+ and relies on <a href="http://www.mozilla.org/rhino/">Rhino</a> to tokenize the source JavaScript file.
+ It starts by analyzing the source JavaScript file to understand how it is structured. It then prints out the token stream,
+ omitting as many white space characters as possible, and replacing all local symbols by a 1 (or 2, or 3) letter symbol
+ wherever such a substitution is appropriate (in the face of
+ <a href="http://www.jslint.com/lint.html">evil features</a> such as <code>eval</code> or
+ <code>with</code>, the YUI Compressor takes a defensive approach by not obfuscating any of the scopes containing the evil
+ statement) The CSS compression algorithm uses a set of finely tuned regular expressions to compress the source CSS file.
+ The YUI Compressor is open-source, so don't hesitate to look at the code to understand exactly how it works.
+</p>
+
+<h2 id="using">Using the YUI Compressor from the command line</h2>
+
+```terminal
+$ java -jar yuicompressor-x.y.z.jar
+Usage: java -jar yuicompressor-x.y.z.jar [options] [input file]
+
+ Global Options
+ -h, --help Displays this information
+ --type <js|css> Specifies the type of the input file
+ --charset <charset> Read the input file using <charset>
+ --line-break <column> Insert a line break after the specified column number
+ -v, --verbose Display informational messages and warnings
+ -o <file> Place the output into <file> or a file pattern.
+ Defaults to stdout.
+
+ JavaScript Options
+ --nomunge Minify only, do not obfuscate
+ --preserve-semi Preserve all semicolons
+ --disable-optimizations Disable all micro optimizations
+
+GLOBAL OPTIONS
+
+ -h, --help
+ Prints help on how to use the YUI Compressor
+
+ --line-break
+ Some source control tools don't like files containing lines longer than,
+ say 8000 characters. The linebreak option is used in that case to split
+ long lines after a specific column. It can also be used to make the code
+ more readable, easier to debug (especially with the MS Script Debugger)
+ Specify 0 to get a line break after each semi-colon in JavaScript, and
+ after each rule in CSS.
+
+ --type js|css
+ The type of compressor (JavaScript or CSS) is chosen based on the
+ extension of the input file name (.js or .css) This option is required
+ if no input file has been specified. Otherwise, this option is only
+ required if the input file extension is neither 'js' nor 'css'.
+
+ --charset character-set
+ If a supported character set is specified, the YUI Compressor will use it
+ to read the input file. Otherwise, it will assume that the platform's
+ default character set is being used. The output file is encoded using
+ the same character set.
+
+ -o outfile
+
+ Place output in file outfile. If not specified, the YUI Compressor will
+ default to the standard output, which you can redirect to a file.
+ Supports a filter syntax for expressing the output pattern when there are
+ multiple input files. ex:
+ java -jar yuicompressor.jar -o '.css$:-min.css' *.css
+ ... will minify all .css files and save them as -min.css
+
+ -v, --verbose
+ Display informational messages and warnings.
+
+JAVASCRIPT ONLY OPTIONS
+
+ --nomunge
+ Minify only. Do not obfuscate local symbols.
+
+ --preserve-semi
+ Preserve unnecessary semicolons (such as right before a '}') This option
+ is useful when compressed code has to be run through JSLint (which is the
+ case of YUI for example)
+
+ --disable-optimizations
+ Disable all the built-in micro optimizations.
+```
+
+<p><strong>Note:</strong> If no input file is specified, it defaults to stdin.</p>
+
+<p>The following command line (x.y.z represents the version number):</p>
+
+```terminal
+$ java -jar yuicompressor-x.y.z.jar myfile.js -o myfile-min.js
+```
+
+<p>will minify the file `myfile.js` and output the file `myfile-min.js`.
+For more information on how to use the YUI Compressor, please refer to the documentation
+included in the archive.</p>
+
+<p>The charset parameter isn't always required, but the compressor may throw an error
+if the file's encoding is incompatible with the system's default encoding. In particular,
+if your file is encoded in utf-8, you should supply the parameter.</p>
+
+```terminal
+$ java -jar yuicompressor-x.y.z.jar myfile.js -o myfile-min.js --charset utf-8
+```
+
+<h3 id="notes">Additional notes</h3>
+
+<ul>
+ <li>
+ Don't hesitate to use the <code>-v</code> option. Although not a replacement for
+ <a href="http://www.jslint.com/lint.html">JSLint</a>, it will output some helpful hints
+ when it senses that something might be wrong with your code.
+ </li>
+ <li>
+ If you wish to minify your files on the backend (also known as on-the-fly minification) instead of at build time,
+ you will want to cache the minified files in memory for optimal performance (instead of minifying the same files
+ over and over & minification is a time consuming process) Note that the YUI Compressor can easily be instantiated
+ and used from a Java-based environment (Servlet).
+ </li>
+</ul>
+
+<h2 id="support">Support & Community</h2>
+<p>The YUI Library and related topics are discussed on the on the <a href="http://yuilib.com/forum/">YUILibrary.com forums</a>.</p>
+
+<p>Also be sure to check out <a href="http://yuiblog.com">YUIBlog</a> for updates and articles about the YUI Library written by the library's developers.</p>
+
+<h2 id="filingbugs">Filing Bugs & Feature Requests</h2>
+
+<p>YUICompressor uses <a href="https://github.com/yui/yuicompressor">Github Issues</a> to track issues.</p>
+
+<h3>More Reading about JavaScript/CSS minification and the YUI Compressor</h3>
+<ul>
+ <li><a href="http://yuiblog.com/blog/2008/02/11/helping-the-yui-compressor">Helping the YUI Compressor</a>, by Nicholas Zakas</li>
+ <li><a href="http://www.coderjournal.com/2008/05/how-to-create-a-yui-compressor-msbuild-task/">How to create a YUI Compressor MSBuild Task</a>, by Nick Berardi
+ <li><a href="http://developer.yahoo.com/performance/rules.html#minify">Minify JavaScript</a>, by Yahoo!'s Exceptional Performance Team</li>
+ <li><a href="http://yuiblog.com/blog/2006/03/06/minification-v-obfuscation/">Minification v Obfuscation</a>, by Douglas Crockford</li>
+ <li><a href="http://www.julienlecomte.net/blog/2007/08/13/introducing-the-yui-compressor/">Introducing the YUI Compressor</a>, by YUI Compressor author Julien Lecomte</li>
+ <li><a href="http://www.crockford.com/javascript/jsmin.html">JSMIN</a>, by Douglas Crockford</li>
+ <li><a href="http://dojotoolkit.org/docs/shrinksafe">ShrinkSafe</a>, aka The Dojo Compressor</li>
+ <li><a href="http://dean.edwards.name/packer/">Packer</a>, by Dean Edwards</li>
+ <li><a href="http://compressorrater.thruhere.net/">CompressorRater</a>, by Arthur Blake</li>
+</ul>
diff --git a/docs/project.json b/docs/project.json
new file mode 100644
index 0000000..73008cb
--- /dev/null
+++ b/docs/project.json
@@ -0,0 +1,3 @@
+{
+ "projectName": "YUI Compressor"
+}
diff --git a/nodejs/cli.js b/nodejs/cli.js
new file mode 100755
index 0000000..47ccd86
--- /dev/null
+++ b/nodejs/cli.js
@@ -0,0 +1,23 @@
+#!/usr/bin/env node
+
+/*
+Just a simple nodejs wrapper around the .jar file
+for easy CLI use
+*/
+
+var spawn = require('child_process').spawn,
+ fs = require('fs'),
+ compressor = require('./index'),
+ args = process.argv.slice(2);
+
+args.unshift(compressor.jar);
+args.unshift('-jar');
+
+var cmd = spawn('java', args);
+
+cmd.stdout.on('data',function(data) {
+ process.stdout.write(data.toString());
+});
+cmd.stderr.on('data',function(data) {
+ process.stderr.write(data.toString());
+});
diff --git a/nodejs/index.js b/nodejs/index.js
new file mode 100644
index 0000000..3375fd3
--- /dev/null
+++ b/nodejs/index.js
@@ -0,0 +1,114 @@
+
+var spawn = require('child_process').spawn,
+ fs = require('fs'),
+ path = require('path'),
+ jar,
+ exists = fs.exists || path.exists,
+ lists = fs.readdirSync(path.join(__dirname, '../build'));
+
+lists.some(function(item) {
+ if (path.extname(item) === '.jar') {
+ jar = path.join(__dirname, '../build/', item);
+ return true;
+ }
+});
+
+exports.jar = jar;
+
+var defaultOptions = {
+ charset: 'utf8',
+ type: 'js'
+};
+
+var validOptions = {
+ charset: 1,
+ type: 1,
+ 'line-break': 1,
+ nomunge: 1,
+ 'preserve-semi': 1,
+ 'disable-optimizations': 1
+};
+
+var getString = function(str, callback, options) {
+ exists(str, function(y) {
+ if (y) {
+ var ext = (path.extname(str)).replace('.', '');
+ fs.readFile(str, 'utf8', function(err, data) {
+ //Set the type from the file name
+ options.type = ext;
+ callback(err, data, options);
+ });
+ } else {
+ callback(null, str, options);
+ }
+ });
+};
+
+
+var filterOptions = function(options) {
+ Object.keys(options).forEach(function(key) {
+ if (!validOptions[key]) {
+ delete options[key];
+ }
+ });
+
+ options.type = options.type || 'js';
+ options.charset = options.charset || 'utf8';
+ return options;
+};
+
+var compressString = function(str, options, callback) {
+ //Now we have a string, spawn and pipe it in.
+
+ options = filterOptions(options);
+
+ var args = [
+ '-jar',
+ exports.jar
+ ], buffer = '', errBuffer = '', child;
+
+ Object.keys(options).forEach(function(key) {
+ args.push('--' + key);
+ if (options[key] && options[key] !== true) {
+ args.push(options[key]);
+ }
+ });
+
+ child = spawn('java', args, {
+ stdio: ['pipe', 'pipe', 'pipe']
+ });
+
+ child.stdin.write(str);
+ child.stdin.end();
+
+ child.stdout.on('data', function(chunk) {
+ buffer += chunk;
+ });
+ child.stderr.on('data', function(chunk) {
+ errBuffer += chunk;
+ });
+
+ child.on('exit', function() {
+ var err = null;
+ if (errBuffer.indexOf('[ERROR]') > -1) {
+ err = errBuffer;
+ }
+ callback(err, buffer, errBuffer);
+ });
+};
+
+var compress = function(str, options, callback) {
+ if (typeof options === 'function') {
+ callback = options;
+ options = defaultOptions;
+ }
+
+ getString(str, function(err, str, options) {
+
+ compressString(str, options, callback);
+
+ }, options);
+};
+
+exports.compress = compress;
+exports.compressString = compressString;
diff --git a/nodejs_tests/files/yui.js b/nodejs_tests/files/yui.js
new file mode 100644
index 0000000..829c9f9
--- /dev/null
+++ b/nodejs_tests/files/yui.js
@@ -0,0 +1,10970 @@
+/**
+ * The YUI module contains the components required for building the YUI seed
+ * file. This includes the script loading mechanism, a simple queue, and
+ * the core utilities for the library.
+ * @module yui
+ * @main yui
+ * @submodule yui-base
+ */
+
+if (typeof YUI != 'undefined') {
+ YUI._YUI = YUI;
+}
+
+/**
+The YUI global namespace object. If YUI is already defined, the
+existing YUI object will not be overwritten so that defined
+namespaces are preserved. It is the constructor for the object
+the end user interacts with. As indicated below, each instance
+has full custom event support, but only if the event system
+is available. This is a self-instantiable factory function. You
+can invoke it directly like this:
+
+ YUI().use('*', function(Y) {
+ // ready
+ });
+
+But it also works like this:
+
+ var Y = YUI();
+
+Configuring the YUI object:
+
+ YUI({
+ debug: true,
+ combine: false
+ }).use('node', function(Y) {
+ //Node is ready to use
+ });
+
+See the API docs for the <a href="config.html">Config</a> class
+for the complete list of supported configuration properties accepted
+by the YUI constuctor.
+
+ at class YUI
+ at constructor
+ at global
+ at uses EventTarget
+ at param [o]* {Object} 0..n optional configuration objects. these values
+are store in Y.config. See <a href="config.html">Config</a> for the list of supported
+properties.
+*/
+ /*global YUI*/
+ /*global YUI_config*/
+ var YUI = function() {
+ var i = 0,
+ Y = this,
+ args = arguments,
+ l = args.length,
+ instanceOf = function(o, type) {
+ return (o && o.hasOwnProperty && (o instanceof type));
+ },
+ gconf = (typeof YUI_config !== 'undefined') && YUI_config;
+
+ if (!(instanceOf(Y, YUI))) {
+ Y = new YUI();
+ } else {
+ // set up the core environment
+ Y._init();
+
+ /**
+ YUI.GlobalConfig is a master configuration that might span
+ multiple contexts in a non-browser environment. It is applied
+ first to all instances in all contexts.
+ @property GlobalConfig
+ @type {Object}
+ @global
+ @static
+ @example
+
+
+ YUI.GlobalConfig = {
+ filter: 'debug'
+ };
+
+ YUI().use('node', function(Y) {
+ //debug files used here
+ });
+
+ YUI({
+ filter: 'min'
+ }).use('node', function(Y) {
+ //min files used here
+ });
+
+ */
+ if (YUI.GlobalConfig) {
+ Y.applyConfig(YUI.GlobalConfig);
+ }
+
+ /**
+ YUI_config is a page-level config. It is applied to all
+ instances created on the page. This is applied after
+ YUI.GlobalConfig, and before the instance level configuration
+ objects.
+ @global
+ @property YUI_config
+ @type {Object}
+ @example
+
+
+ //Single global var to include before YUI seed file
+ YUI_config = {
+ filter: 'debug'
+ };
+
+ YUI().use('node', function(Y) {
+ //debug files used here
+ });
+
+ YUI({
+ filter: 'min'
+ }).use('node', function(Y) {
+ //min files used here
+ });
+ */
+ if (gconf) {
+ Y.applyConfig(gconf);
+ }
+
+ // bind the specified additional modules for this instance
+ if (!l) {
+ Y._setup();
+ }
+ }
+
+ if (l) {
+ // Each instance can accept one or more configuration objects.
+ // These are applied after YUI.GlobalConfig and YUI_Config,
+ // overriding values set in those config files if there is a '
+ // matching property.
+ for (; i < l; i++) {
+ Y.applyConfig(args[i]);
+ }
+
+ Y._setup();
+ }
+
+ Y.instanceOf = instanceOf;
+
+ return Y;
+ };
+
+(function() {
+
+ var proto, prop,
+ VERSION = '@VERSION@',
+ PERIOD = '.',
+ BASE = 'http://yui.yahooapis.com/',
+ /*
+ These CSS class names can't be generated by
+ getClassName since it is not available at the
+ time they are being used.
+ */
+ DOC_LABEL = 'yui3-js-enabled',
+ CSS_STAMP_EL = 'yui3-css-stamp',
+ NOOP = function() {},
+ SLICE = Array.prototype.slice,
+ APPLY_TO_AUTH = { 'io.xdrReady': 1, // the functions applyTo
+ 'io.xdrResponse': 1, // can call. this should
+ 'SWF.eventHandler': 1 }, // be done at build time
+ hasWin = (typeof window != 'undefined'),
+ win = (hasWin) ? window : null,
+ doc = (hasWin) ? win.document : null,
+ docEl = doc && doc.documentElement,
+ docClass = docEl && docEl.className,
+ instances = {},
+ time = new Date().getTime(),
+ add = function(el, type, fn, capture) {
+ if (el && el.addEventListener) {
+ el.addEventListener(type, fn, capture);
+ } else if (el && el.attachEvent) {
+ el.attachEvent('on' + type, fn);
+ }
+ },
+ remove = function(el, type, fn, capture) {
+ if (el && el.removeEventListener) {
+ // this can throw an uncaught exception in FF
+ try {
+ el.removeEventListener(type, fn, capture);
+ } catch (ex) {}
+ } else if (el && el.detachEvent) {
+ el.detachEvent('on' + type, fn);
+ }
+ },
+ handleLoad = function() {
+ YUI.Env.windowLoaded = true;
+ YUI.Env.DOMReady = true;
+ if (hasWin) {
+ remove(window, 'load', handleLoad);
+ }
+ },
+ getLoader = function(Y, o) {
+ var loader = Y.Env._loader,
+ lCore = [ 'loader-base' ],
+ G_ENV = YUI.Env,
+ mods = G_ENV.mods;
+
+ if (loader) {
+ //loader._config(Y.config);
+ loader.ignoreRegistered = false;
+ loader.onEnd = null;
+ loader.data = null;
+ loader.required = [];
+ loader.loadType = null;
+ } else {
+ loader = new Y.Loader(Y.config);
+ Y.Env._loader = loader;
+ }
+ if (mods && mods.loader) {
+ lCore = [].concat(lCore, YUI.Env.loaderExtras);
+ }
+ YUI.Env.core = Y.Array.dedupe([].concat(YUI.Env.core, lCore));
+
+ return loader;
+ },
+
+ clobber = function(r, s) {
+ for (var i in s) {
+ if (s.hasOwnProperty(i)) {
+ r[i] = s[i];
+ }
+ }
+ },
+
+ ALREADY_DONE = { success: true };
+
+// Stamp the documentElement (HTML) with a class of "yui-loaded" to
+// enable styles that need to key off of JS being enabled.
+if (docEl && docClass.indexOf(DOC_LABEL) == -1) {
+ if (docClass) {
+ docClass += ' ';
+ }
+ docClass += DOC_LABEL;
+ docEl.className = docClass;
+}
+
+if (VERSION.indexOf('@') > -1) {
+ VERSION = '3.5.0'; // dev time hack for cdn test
+}
+
+proto = {
+ /**
+ * Applies a new configuration object to the YUI instance config.
+ * This will merge new group/module definitions, and will also
+ * update the loader cache if necessary. Updating Y.config directly
+ * will not update the cache.
+ * @method applyConfig
+ * @param {Object} o the configuration object.
+ * @since 3.2.0
+ */
+ applyConfig: function(o) {
+
+ o = o || NOOP;
+
+ var attr,
+ name,
+ // detail,
+ config = this.config,
+ mods = config.modules,
+ groups = config.groups,
+ aliases = config.aliases,
+ loader = this.Env._loader;
+
+ for (name in o) {
+ if (o.hasOwnProperty(name)) {
+ attr = o[name];
+ if (mods && name == 'modules') {
+ clobber(mods, attr);
+ } else if (aliases && name == 'aliases') {
+ clobber(aliases, attr);
+ } else if (groups && name == 'groups') {
+ clobber(groups, attr);
+ } else if (name == 'win') {
+ config[name] = (attr && attr.contentWindow) || attr;
+ config.doc = config[name] ? config[name].document : null;
+ } else if (name == '_yuid') {
+ // preserve the guid
+ } else {
+ config[name] = attr;
+ }
+ }
+ }
+
+ if (loader) {
+ loader._config(o);
+ }
+
+ },
+ /**
+ * Old way to apply a config to the instance (calls `applyConfig` under the hood)
+ * @private
+ * @method _config
+ * @param {Object} o The config to apply
+ */
+ _config: function(o) {
+ this.applyConfig(o);
+ },
+
+ /**
+ * Initialize this YUI instance
+ * @private
+ * @method _init
+ */
+ _init: function() {
+ var filter, el,
+ Y = this,
+ G_ENV = YUI.Env,
+ Env = Y.Env,
+ prop;
+
+ /**
+ * The version number of the YUI instance.
+ * @property version
+ * @type string
+ */
+ Y.version = VERSION;
+
+ if (!Env) {
+ Y.Env = {
+ core: ['get','features','intl-base','yui-log','yui-later','loader-base', 'loader-rollup', 'loader-yui3'],
+ loaderExtras: ['loader-rollup', 'loader-yui3'],
+ mods: {}, // flat module map
+ versions: {}, // version module map
+ base: BASE,
+ cdn: BASE + VERSION + '/build/',
+ // bootstrapped: false,
+ _idx: 0,
+ _used: {},
+ _attached: {},
+ _missed: [],
+ _yidx: 0,
+ _uidx: 0,
+ _guidp: 'y',
+ _loaded: {},
+ // serviced: {},
+ // Regex in English:
+ // I'll start at the \b(simpleyui).
+ // 1. Look in the test string for "simpleyui" or "yui" or
+ // "yui-base" or "yui-davglass" or "yui-foobar" that comes after a word break. That is, it
+ // can't match "foyui" or "i_heart_simpleyui". This can be anywhere in the string.
+ // 2. After #1 must come a forward slash followed by the string matched in #1, so
+ // "yui-base/yui-base" or "simpleyui/simpleyui" or "yui-pants/yui-pants".
+ // 3. The second occurence of the #1 token can optionally be followed by "-debug" or "-min",
+ // so "yui/yui-min", "yui/yui-debug", "yui-base/yui-base-debug". NOT "yui/yui-tshirt".
+ // 4. This is followed by ".js", so "yui/yui.js", "simpleyui/simpleyui-min.js"
+ // 0. Going back to the beginning, now. If all that stuff in 1-4 comes after a "?" in the string,
+ // then capture the junk between the LAST "&" and the string in 1-4. So
+ // "blah?foo/yui/yui.js" will capture "foo/" and "blah?some/thing.js&3.3.0/build/yui-davglass/yui-davglass.js"
+ // will capture "3.3.0/build/"
+ //
+ // Regex Exploded:
+ // (?:\? Find a ?
+ // (?:[^&]*&) followed by 0..n characters followed by an &
+ // * in fact, find as many sets of characters followed by a & as you can
+ // ([^&]*) capture the stuff after the last & in \1
+ // )? but it's ok if all this ?junk&more_junk stuff isn't even there
+ // \b(simpleyui| after a word break find either the string "simpleyui" or
+ // yui(?:-\w+)? the string "yui" optionally followed by a -, then more characters
+ // ) and store the simpleyui or yui-* string in \2
+ // \/\2 then comes a / followed by the simpleyui or yui-* string in \2
+ // (?:-(min|debug))? optionally followed by "-min" or "-debug"
+ // .js and ending in ".js"
+ _BASE_RE: /(?:\?(?:[^&]*&)*([^&]*))?\b(simpleyui|yui(?:-\w+)?)\/\2(?:-(min|debug))?\.js/,
+ parseBasePath: function(src, pattern) {
+ var match = src.match(pattern),
+ path, filter;
+
+ if (match) {
+ path = RegExp.leftContext || src.slice(0, src.indexOf(match[0]));
+
+ // this is to set up the path to the loader. The file
+ // filter for loader should match the yui include.
+ filter = match[3];
+
+ // extract correct path for mixed combo urls
+ // http://yuilibrary.com/projects/yui3/ticket/2528423
+ if (match[1]) {
+ path += '?' + match[1];
+ }
+ path = {
+ filter: filter,
+ path: path
+ }
+ }
+ return path;
+ },
+ getBase: G_ENV && G_ENV.getBase ||
+ function(pattern) {
+ var nodes = (doc && doc.getElementsByTagName('script')) || [],
+ path = Env.cdn, parsed,
+ i, len, src;
+
+ for (i = 0, len = nodes.length; i < len; ++i) {
+ src = nodes[i].src;
+ if (src) {
+ parsed = Y.Env.parseBasePath(src, pattern);
+ if (parsed) {
+ filter = parsed.filter;
+ path = parsed.path;
+ break;
+ }
+ }
+ }
+
+ // use CDN default
+ return path;
+ }
+
+ };
+
+ Env = Y.Env;
+
+ Env._loaded[VERSION] = {};
+
+ if (G_ENV && Y !== YUI) {
+ Env._yidx = ++G_ENV._yidx;
+ Env._guidp = ('yui_' + VERSION + '_' +
+ Env._yidx + '_' + time).replace(/\./g, '_').replace(/-/g, '_');
+ } else if (YUI._YUI) {
+
+ G_ENV = YUI._YUI.Env;
+ Env._yidx += G_ENV._yidx;
+ Env._uidx += G_ENV._uidx;
+
+ for (prop in G_ENV) {
+ if (!(prop in Env)) {
+ Env[prop] = G_ENV[prop];
+ }
+ }
+
+ delete YUI._YUI;
+ }
+
+ Y.id = Y.stamp(Y);
+ instances[Y.id] = Y;
+
+ }
+
+ Y.constructor = YUI;
+
+ // configuration defaults
+ Y.config = Y.config || {
+ bootstrap: true,
+ cacheUse: true,
+ debug: true,
+ doc: doc,
+ fetchCSS: true,
+ throwFail: true,
+ useBrowserConsole: true,
+ useNativeES5: true,
+ win: win
+ };
+
+ //Register the CSS stamp element
+ if (doc && !doc.getElementById(CSS_STAMP_EL)) {
+ el = doc.createElement('div');
+ el.innerHTML = '<div id="' + CSS_STAMP_EL + '" style="position: absolute !important; visibility: hidden !important"></div>';
+ YUI.Env.cssStampEl = el.firstChild;
+ if (doc.body) {
+ doc.body.appendChild(YUI.Env.cssStampEl);
+ } else {
+ docEl.insertBefore(YUI.Env.cssStampEl, docEl.firstChild);
+ }
+ }
+
+ Y.config.lang = Y.config.lang || 'en-US';
+
+ Y.config.base = YUI.config.base || Y.Env.getBase(Y.Env._BASE_RE);
+
+ if (!filter || (!('mindebug').indexOf(filter))) {
+ filter = 'min';
+ }
+ filter = (filter) ? '-' + filter : filter;
+ Y.config.loaderPath = YUI.config.loaderPath || 'loader/loader' + filter + '.js';
+
+ },
+
+ /**
+ * Finishes the instance setup. Attaches whatever modules were defined
+ * when the yui modules was registered.
+ * @method _setup
+ * @private
+ */
+ _setup: function(o) {
+ var i, Y = this,
+ core = [],
+ mods = YUI.Env.mods,
+ extras = Y.config.core || [].concat(YUI.Env.core); //Clone it..
+
+ for (i = 0; i < extras.length; i++) {
+ if (mods[extras[i]]) {
+ core.push(extras[i]);
+ }
+ }
+
+ Y._attach(['yui-base']);
+ Y._attach(core);
+
+ if (Y.Loader) {
+ getLoader(Y);
+ }
+
+ },
+
+ /**
+ * Executes a method on a YUI instance with
+ * the specified id if the specified method is whitelisted.
+ * @method applyTo
+ * @param id {String} the YUI instance id.
+ * @param method {String} the name of the method to exectute.
+ * Ex: 'Object.keys'.
+ * @param args {Array} the arguments to apply to the method.
+ * @return {Object} the return value from the applied method or null.
+ */
+ applyTo: function(id, method, args) {
+ if (!(method in APPLY_TO_AUTH)) {
+ this.log(method + ': applyTo not allowed', 'warn', 'yui');
+ return null;
+ }
+
+ var instance = instances[id], nest, m, i;
+ if (instance) {
+ nest = method.split('.');
+ m = instance;
+ for (i = 0; i < nest.length; i = i + 1) {
+ m = m[nest[i]];
+ if (!m) {
+ this.log('applyTo not found: ' + method, 'warn', 'yui');
+ }
+ }
+ return m && m.apply(instance, args);
+ }
+
+ return null;
+ },
+
+/**
+Registers a module with the YUI global. The easiest way to create a
+first-class YUI module is to use the YUI component build tool.
+
+http://yuilibrary.com/projects/builder
+
+The build system will produce the `YUI.add` wrapper for you module, along
+with any configuration info required for the module.
+ at method add
+ at param name {String} module name.
+ at param fn {Function} entry point into the module that is used to bind module to the YUI instance.
+ at param {YUI} fn.Y The YUI instance this module is executed in.
+ at param {String} fn.name The name of the module
+ at param version {String} version string.
+ at param details {Object} optional config data:
+ at param details.requires {Array} features that must be present before this module can be attached.
+ at param details.optional {Array} optional features that should be present if loadOptional
+ is defined. Note: modules are not often loaded this way in YUI 3,
+ but this field is still useful to inform the user that certain
+ features in the component will require additional dependencies.
+ at param details.use {Array} features that are included within this module which need to
+ be attached automatically when this module is attached. This
+ supports the YUI 3 rollup system -- a module with submodules
+ defined will need to have the submodules listed in the 'use'
+ config. The YUI component build tool does this for you.
+ at return {YUI} the YUI instance.
+ at example
+
+ YUI.add('davglass', function(Y, name) {
+ Y.davglass = function() {
+ alert('Dav was here!');
+ };
+ }, '3.4.0', { requires: ['yui-base', 'harley-davidson', 'mt-dew'] });
+
+*/
+ add: function(name, fn, version, details) {
+ details = details || {};
+ var env = YUI.Env,
+ mod = {
+ name: name,
+ fn: fn,
+ version: version,
+ details: details
+ },
+ //Instance hash so we don't apply it to the same instance twice
+ applied = {},
+ loader, inst,
+ i, versions = env.versions;
+
+ env.mods[name] = mod;
+ versions[version] = versions[version] || {};
+ versions[version][name] = mod;
+
+ for (i in instances) {
+ if (instances.hasOwnProperty(i)) {
+ inst = instances[i];
+ if (!applied[inst.id]) {
+ applied[inst.id] = true;
+ loader = inst.Env._loader;
+ if (loader) {
+ if (!loader.moduleInfo[name] || loader.moduleInfo[name].temp) {
+ loader.addModule(details, name);
+ }
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ /**
+ * Executes the function associated with each required
+ * module, binding the module to the YUI instance.
+ * @param {Array} r The array of modules to attach
+ * @param {Boolean} [moot=false] Don't throw a warning if the module is not attached
+ * @method _attach
+ * @private
+ */
+ _attach: function(r, moot) {
+ var i, name, mod, details, req, use, after,
+ mods = YUI.Env.mods,
+ aliases = YUI.Env.aliases,
+ Y = this, j,
+ cache = YUI.Env._renderedMods,
+ loader = Y.Env._loader,
+ done = Y.Env._attached,
+ len = r.length, loader, def, go,
+ c = [];
+
+ //Check for conditional modules (in a second+ instance) and add their requirements
+ //TODO I hate this entire method, it needs to be fixed ASAP (3.5.0) ^davglass
+ for (i = 0; i < len; i++) {
+ name = r[i];
+ mod = mods[name];
+ c.push(name);
+ if (loader && loader.conditions[name]) {
+ for (j in loader.conditions[name]) {
+ if (loader.conditions[name].hasOwnProperty(j)) {
+ def = loader.conditions[name][j];
+ go = def && ((def.ua && Y.UA[def.ua]) || (def.test && def.test(Y)));
+ if (go) {
+ c.push(def.name);
+ }
+ }
+ }
+ }
+ }
+ r = c;
+ len = r.length;
+
+ for (i = 0; i < len; i++) {
+ if (!done[r[i]]) {
+ name = r[i];
+ mod = mods[name];
+
+ if (aliases && aliases[name] && !mod) {
+ Y._attach(aliases[name]);
+ continue;
+ }
+ if (!mod) {
+ if (loader && loader.moduleInfo[name]) {
+ mod = loader.moduleInfo[name];
+ moot = true;
+ }
+
+
+ //if (!loader || !loader.moduleInfo[name]) {
+ //if ((!loader || !loader.moduleInfo[name]) && !moot) {
+ if (!moot && name) {
+ if ((name.indexOf('skin-') === -1) && (name.indexOf('css') === -1)) {
+ Y.Env._missed.push(name);
+ Y.Env._missed = Y.Array.dedupe(Y.Env._missed);
+ Y.message('NOT loaded: ' + name, 'warn', 'yui');
+ }
+ }
+ } else {
+ done[name] = true;
+ //Don't like this, but in case a mod was asked for once, then we fetch it
+ //We need to remove it from the missed list ^davglass
+ for (j = 0; j < Y.Env._missed.length; j++) {
+ if (Y.Env._missed[j] === name) {
+ Y.message('Found: ' + name + ' (was reported as missing earlier)', 'warn', 'yui');
+ Y.Env._missed.splice(j, 1);
+ }
+ }
+ /*
+ If it's a temp module, we need to redo it's requirements if it's already loaded
+ since it may have been loaded by another instance and it's dependencies might
+ have been redefined inside the fetched file.
+ */
+ if (loader && cache && cache[name] && cache[name].temp) {
+ loader.getRequires(cache[name]);
+ req = [];
+ for (j in loader.moduleInfo[name].expanded_map) {
+ if (loader.moduleInfo[name].expanded_map.hasOwnProperty(j)) {
+ req.push(j);
+ }
+ }
+ Y._attach(req);
+ }
+
+ details = mod.details;
+ req = details.requires;
+ use = details.use;
+ after = details.after;
+ //Force Intl load if there is a language (Loader logic) @todo fix this shit
+ if (details.lang) {
+ req = req || [];
+ req.unshift('intl');
+ }
+
+ if (req) {
+ for (j = 0; j < req.length; j++) {
+ if (!done[req[j]]) {
+ if (!Y._attach(req)) {
+ return false;
+ }
+ break;
+ }
+ }
+ }
+
+ if (after) {
+ for (j = 0; j < after.length; j++) {
+ if (!done[after[j]]) {
+ if (!Y._attach(after, true)) {
+ return false;
+ }
+ break;
+ }
+ }
+ }
+
+ if (mod.fn) {
+ if (Y.config.throwFail) {
+ mod.fn(Y, name);
+ } else {
+ try {
+ mod.fn(Y, name);
+ } catch (e) {
+ Y.error('Attach error: ' + name, e, name);
+ return false;
+ }
+ }
+ }
+
+ if (use) {
+ for (j = 0; j < use.length; j++) {
+ if (!done[use[j]]) {
+ if (!Y._attach(use)) {
+ return false;
+ }
+ break;
+ }
+ }
+ }
+
+
+
+ }
+ }
+ }
+
+ return true;
+ },
+ /**
+ * Delays the `use` callback until another event has taken place. Like: window.onload, domready, contentready, available.
+ * @private
+ * @method _delayCallback
+ * @param {Callback} cb The original `use` callback
+ * @param {String|Object} until Either an event (load, domready) or an Object containing event/args keys for contentready/available
+ */
+ _delayCallback: function(cb, until) {
+
+ var Y = this,
+ mod = ['event-base'];
+
+ until = (Y.Lang.isObject(until) ? until : { event: until });
+
+ if (until.event === 'load') {
+ mod.push('event-synthetic');
+ }
+
+ return function() {
+ var args = arguments;
+ Y._use(mod, function() {
+ Y.on(until.event, function() {
+ args[1].delayUntil = until.event;
+ cb.apply(Y, args);
+ }, until.args);
+ });
+ };
+ },
+
+ /**
+ * Attaches one or more modules to the YUI instance. When this
+ * is executed, the requirements are analyzed, and one of
+ * several things can happen:
+ *
+ * * All requirements are available on the page -- The modules
+ * are attached to the instance. If supplied, the use callback
+ * is executed synchronously.
+ *
+ * * Modules are missing, the Get utility is not available OR
+ * the 'bootstrap' config is false -- A warning is issued about
+ * the missing modules and all available modules are attached.
+ *
+ * * Modules are missing, the Loader is not available but the Get
+ * utility is and boostrap is not false -- The loader is bootstrapped
+ * before doing the following....
+ *
+ * * Modules are missing and the Loader is available -- The loader
+ * expands the dependency tree and fetches missing modules. When
+ * the loader is finshed the callback supplied to use is executed
+ * asynchronously.
+ *
+ * @method use
+ * @param modules* {String|Array} 1-n modules to bind (uses arguments array).
+ * @param [callback] {Function} callback function executed when
+ * the instance has the required functionality. If included, it
+ * must be the last parameter.
+ * @param callback.Y {YUI} The `YUI` instance created for this sandbox
+ * @param callback.status {Object} Object containing `success`, `msg` and `data` properties
+ *
+ * @example
+ * // loads and attaches dd and its dependencies
+ * YUI().use('dd', function(Y) {});
+ *
+ * // loads and attaches dd and node as well as all of their dependencies (since 3.4.0)
+ * YUI().use(['dd', 'node'], function(Y) {});
+ *
+ * // attaches all modules that are available on the page
+ * YUI().use('*', function(Y) {});
+ *
+ * // intrinsic YUI gallery support (since 3.1.0)
+ * YUI().use('gallery-yql', function(Y) {});
+ *
+ * // intrinsic YUI 2in3 support (since 3.1.0)
+ * YUI().use('yui2-datatable', function(Y) {});
+ *
+ * @return {YUI} the YUI instance.
+ */
+ use: function() {
+ var args = SLICE.call(arguments, 0),
+ callback = args[args.length - 1],
+ Y = this,
+ i = 0,
+ a = [],
+ name,
+ Env = Y.Env,
+ provisioned = true;
+
+ // The last argument supplied to use can be a load complete callback
+ if (Y.Lang.isFunction(callback)) {
+ args.pop();
+ if (Y.config.delayUntil) {
+ callback = Y._delayCallback(callback, Y.config.delayUntil);
+ }
+ } else {
+ callback = null;
+ }
+ if (Y.Lang.isArray(args[0])) {
+ args = args[0];
+ }
+
+ if (Y.config.cacheUse) {
+ while ((name = args[i++])) {
+ if (!Env._attached[name]) {
+ provisioned = false;
+ break;
+ }
+ }
+
+ if (provisioned) {
+ if (args.length) {
+ }
+ Y._notify(callback, ALREADY_DONE, args);
+ return Y;
+ }
+ }
+
+ if (Y._loading) {
+ Y._useQueue = Y._useQueue || new Y.Queue();
+ Y._useQueue.add([args, callback]);
+ } else {
+ Y._use(args, function(Y, response) {
+ Y._notify(callback, response, args);
+ });
+ }
+
+ return Y;
+ },
+ /**
+ * Notify handler from Loader for attachment/load errors
+ * @method _notify
+ * @param callback {Function} The callback to pass to the `Y.config.loadErrorFn`
+ * @param response {Object} The response returned from Loader
+ * @param args {Array} The aruments passed from Loader
+ * @private
+ */
+ _notify: function(callback, response, args) {
+ if (!response.success && this.config.loadErrorFn) {
+ this.config.loadErrorFn.call(this, this, callback, response, args);
+ } else if (callback) {
+ if (this.Env._missed && this.Env._missed.length) {
+ response.msg = 'Missing modules: ' + this.Env._missed.join();
+ response.success = false;
+ }
+ if (this.config.throwFail) {
+ callback(this, response);
+ } else {
+ try {
+ callback(this, response);
+ } catch (e) {
+ this.error('use callback error', e, args);
+ }
+ }
+ }
+ },
+
+ /**
+ * This private method is called from the `use` method queue. To ensure that only one set of loading
+ * logic is performed at a time.
+ * @method _use
+ * @private
+ * @param args* {String} 1-n modules to bind (uses arguments array).
+ * @param *callback {Function} callback function executed when
+ * the instance has the required functionality. If included, it
+ * must be the last parameter.
+ */
+ _use: function(args, callback) {
+
+ if (!this.Array) {
+ this._attach(['yui-base']);
+ }
+
+ var len, loader, handleBoot, handleRLS,
+ Y = this,
+ G_ENV = YUI.Env,
+ mods = G_ENV.mods,
+ Env = Y.Env,
+ used = Env._used,
+ aliases = G_ENV.aliases,
+ queue = G_ENV._loaderQueue,
+ firstArg = args[0],
+ YArray = Y.Array,
+ config = Y.config,
+ boot = config.bootstrap,
+ missing = [],
+ i,
+ r = [],
+ ret = true,
+ fetchCSS = config.fetchCSS,
+ process = function(names, skip) {
+
+ var i = 0, a = [], name, len, m, req, use;
+
+ if (!names.length) {
+ return;
+ }
+
+ if (aliases) {
+ len = names.length;
+ for (i = 0; i < len; i++) {
+ if (aliases[names[i]] && !mods[names[i]]) {
+ a = [].concat(a, aliases[names[i]]);
+ } else {
+ a.push(names[i]);
+ }
+ }
+ names = a;
+ }
+
+ len = names.length;
+
+ for (i = 0; i < len; i++) {
+ name = names[i];
+ if (!skip) {
+ r.push(name);
+ }
+
+ // only attach a module once
+ if (used[name]) {
+ continue;
+ }
+
+ m = mods[name];
+ req = null;
+ use = null;
+
+ if (m) {
+ used[name] = true;
+ req = m.details.requires;
+ use = m.details.use;
+ } else {
+ // CSS files don't register themselves, see if it has
+ // been loaded
+ if (!G_ENV._loaded[VERSION][name]) {
+ missing.push(name);
+ } else {
+ used[name] = true; // probably css
+ }
+ }
+
+ // make sure requirements are attached
+ if (req && req.length) {
+ process(req);
+ }
+
+ // make sure we grab the submodule dependencies too
+ if (use && use.length) {
+ process(use, 1);
+ }
+ }
+
+ },
+
+ handleLoader = function(fromLoader) {
+ var response = fromLoader || {
+ success: true,
+ msg: 'not dynamic'
+ },
+ redo, origMissing,
+ ret = true,
+ data = response.data;
+
+ Y._loading = false;
+
+ if (data) {
+ origMissing = missing;
+ missing = [];
+ r = [];
+ process(data);
+ redo = missing.length;
+ if (redo) {
+ if ([].concat(missing).sort().join() ==
+ origMissing.sort().join()) {
+ redo = false;
+ }
+ }
+ }
+
+ if (redo && data) {
+ Y._loading = true;
+ Y._use(missing, function() {
+ if (Y._attach(data)) {
+ Y._notify(callback, response, data);
+ }
+ });
+ } else {
+ if (data) {
+ ret = Y._attach(data);
+ }
+ if (ret) {
+ Y._notify(callback, response, args);
+ }
+ }
+
+ if (Y._useQueue && Y._useQueue.size() && !Y._loading) {
+ Y._use.apply(Y, Y._useQueue.next());
+ }
+
+ };
+
+
+ // YUI().use('*'); // bind everything available
+ if (firstArg === '*') {
+ args = [];
+ for (i in mods) {
+ if (mods.hasOwnProperty(i)) {
+ args.push(i);
+ }
+ }
+ ret = Y._attach(args);
+ if (ret) {
+ handleLoader();
+ }
+ return Y;
+ }
+
+ if ((mods.loader || mods['loader-base']) && !Y.Loader) {
+ Y._attach(['loader' + ((!mods.loader) ? '-base' : '')]);
+ }
+
+
+ // use loader to expand dependencies and sort the
+ // requirements if it is available.
+ if (boot && Y.Loader && args.length) {
+ loader = getLoader(Y);
+ loader.require(args);
+ loader.ignoreRegistered = true;
+ loader._boot = true;
+ loader.calculate(null, (fetchCSS) ? null : 'js');
+ args = loader.sorted;
+ loader._boot = false;
+ }
+
+ process(args);
+
+ len = missing.length;
+
+
+ if (len) {
+ missing = YArray.dedupe(missing);
+ len = missing.length;
+ }
+
+
+ // dynamic load
+ if (boot && len && Y.Loader) {
+ Y._loading = true;
+ loader = getLoader(Y);
+ loader.onEnd = handleLoader;
+ loader.context = Y;
+ loader.data = args;
+ loader.ignoreRegistered = false;
+ loader.require(args);
+ loader.insert(null, (fetchCSS) ? null : 'js');
+
+ } else if (boot && len && Y.Get && !Env.bootstrapped) {
+
+ Y._loading = true;
+
+ handleBoot = function() {
+ Y._loading = false;
+ queue.running = false;
+ Env.bootstrapped = true;
+ G_ENV._bootstrapping = false;
+ if (Y._attach(['loader'])) {
+ Y._use(args, callback);
+ }
+ };
+
+ if (G_ENV._bootstrapping) {
+ queue.add(handleBoot);
+ } else {
+ G_ENV._bootstrapping = true;
+ Y.Get.script(config.base + config.loaderPath, {
+ onEnd: handleBoot
+ });
+ }
+
+ } else {
+ ret = Y._attach(args);
+ if (ret) {
+ handleLoader();
+ }
+ }
+
+ return Y;
+ },
+
+
+ /**
+ Adds a namespace object onto the YUI global if called statically.
+
+ // creates YUI.your.namespace.here as nested objects
+ YUI.namespace("your.namespace.here");
+
+ If called as a method on a YUI <em>instance</em>, it creates the
+ namespace on the instance.
+
+ // creates Y.property.package
+ Y.namespace("property.package");
+
+ Dots in the input string cause `namespace` to create nested objects for
+ each token. If any part of the requested namespace already exists, the
+ current object will be left in place. This allows multiple calls to
+ `namespace` to preserve existing namespaced properties.
+
+ If the first token in the namespace string is "YAHOO", the token is
+ discarded.
+
+ Be careful with namespace tokens. Reserved words may work in some browsers
+ and not others. For instance, the following will fail in some browsers
+ because the supported version of JavaScript reserves the word "long":
+
+ Y.namespace("really.long.nested.namespace");
+
+ <em>Note: If you pass multiple arguments to create multiple namespaces, only
+ the last one created is returned from this function.</em>
+
+ @method namespace
+ @param {String} namespace* namespaces to create.
+ @return {Object} A reference to the last namespace object created.
+ **/
+ namespace: function() {
+ var a = arguments, o, i = 0, j, d, arg;
+
+ for (; i < a.length; i++) {
+ o = this; //Reset base object per argument or it will get reused from the last
+ arg = a[i];
+ if (arg.indexOf(PERIOD) > -1) { //Skip this if no "." is present
+ d = arg.split(PERIOD);
+ for (j = (d[0] == 'YAHOO') ? 1 : 0; j < d.length; j++) {
+ o[d[j]] = o[d[j]] || {};
+ o = o[d[j]];
+ }
+ } else {
+ o[arg] = o[arg] || {};
+ o = o[arg]; //Reset base object to the new object so it's returned
+ }
+ }
+ return o;
+ },
+
+ // this is replaced if the log module is included
+ log: NOOP,
+ message: NOOP,
+ // this is replaced if the dump module is included
+ dump: function (o) { return ''+o; },
+
+ /**
+ * Report an error. The reporting mechanism is controlled by
+ * the `throwFail` configuration attribute. If throwFail is
+ * not specified, the message is written to the Logger, otherwise
+ * a JS error is thrown. If an `errorFn` is specified in the config
+ * it must return `true` to keep the error from being thrown.
+ * @method error
+ * @param msg {String} the error message.
+ * @param e {Error|String} Optional JS error that was caught, or an error string.
+ * @param src Optional additional info (passed to `Y.config.errorFn` and `Y.message`)
+ * and `throwFail` is specified, this error will be re-thrown.
+ * @return {YUI} this YUI instance.
+ */
+ error: function(msg, e, src) {
+ //TODO Add check for window.onerror here
+
+ var Y = this, ret;
+
+ if (Y.config.errorFn) {
+ ret = Y.config.errorFn.apply(Y, arguments);
+ }
+
+ if (!ret) {
+ throw (e || new Error(msg));
+ } else {
+ Y.message(msg, 'error', ''+src); // don't scrub this one
+ }
+
+ return Y;
+ },
+
+ /**
+ * Generate an id that is unique among all YUI instances
+ * @method guid
+ * @param pre {String} optional guid prefix.
+ * @return {String} the guid.
+ */
+ guid: function(pre) {
+ var id = this.Env._guidp + '_' + (++this.Env._uidx);
+ return (pre) ? (pre + id) : id;
+ },
+
+ /**
+ * Returns a `guid` associated with an object. If the object
+ * does not have one, a new one is created unless `readOnly`
+ * is specified.
+ * @method stamp
+ * @param o {Object} The object to stamp.
+ * @param readOnly {Boolean} if `true`, a valid guid will only
+ * be returned if the object has one assigned to it.
+ * @return {String} The object's guid or null.
+ */
+ stamp: function(o, readOnly) {
+ var uid;
+ if (!o) {
+ return o;
+ }
+
+ // IE generates its own unique ID for dom nodes
+ // The uniqueID property of a document node returns a new ID
+ if (o.uniqueID && o.nodeType && o.nodeType !== 9) {
+ uid = o.uniqueID;
+ } else {
+ uid = (typeof o === 'string') ? o : o._yuid;
+ }
+
+ if (!uid) {
+ uid = this.guid();
+ if (!readOnly) {
+ try {
+ o._yuid = uid;
+ } catch (e) {
+ uid = null;
+ }
+ }
+ }
+ return uid;
+ },
+
+ /**
+ * Destroys the YUI instance
+ * @method destroy
+ * @since 3.3.0
+ */
+ destroy: function() {
+ var Y = this;
+ if (Y.Event) {
+ Y.Event._unload();
+ }
+ delete instances[Y.id];
+ delete Y.Env;
+ delete Y.config;
+ }
+
+ /**
+ * instanceof check for objects that works around
+ * memory leak in IE when the item tested is
+ * window/document
+ * @method instanceOf
+ * @param o {Object} The object to check.
+ * @param type {Object} The class to check against.
+ * @since 3.3.0
+ */
+};
+
+ YUI.prototype = proto;
+
+ // inheritance utilities are not available yet
+ for (prop in proto) {
+ if (proto.hasOwnProperty(prop)) {
+ YUI[prop] = proto[prop];
+ }
+ }
+
+ /**
+Static method on the Global YUI object to apply a config to all YUI instances.
+It's main use case is "mashups" where several third party scripts are trying to write to
+a global YUI config at the same time. This way they can all call `YUI.applyConfig({})` instead of
+overwriting other scripts configs.
+ at static
+ at since 3.5.0
+ at method applyConfig
+ at param {Object} o the configuration object.
+ at example
+
+ YUI.applyConfig({
+ modules: {
+ davglass: {
+ fullpath: './davglass.js'
+ }
+ }
+ });
+
+ YUI.applyConfig({
+ modules: {
+ foo: {
+ fullpath: './foo.js'
+ }
+ }
+ });
+
+ YUI().use('davglass', function(Y) {
+ //Module davglass will be available here..
+ });
+
+ */
+ YUI.applyConfig = function(o) {
+ if (!o) {
+ return;
+ }
+ //If there is a GlobalConfig, apply it first to set the defaults
+ if (YUI.GlobalConfig) {
+ this.prototype.applyConfig.call(this, YUI.GlobalConfig);
+ }
+ //Apply this config to it
+ this.prototype.applyConfig.call(this, o);
+ //Reset GlobalConfig to the combined config
+ YUI.GlobalConfig = this.config;
+ };
+
+ // set up the environment
+ YUI._init();
+
+ if (hasWin) {
+ // add a window load event at load time so we can capture
+ // the case where it fires before dynamic loading is
+ // complete.
+ add(window, 'load', handleLoad);
+ } else {
+ handleLoad();
+ }
+
+ YUI.Env.add = add;
+ YUI.Env.remove = remove;
+
+ /*global exports*/
+ // Support the CommonJS method for exporting our single global
+ if (typeof exports == 'object') {
+ exports.YUI = YUI;
+ }
+
+}());
+
+
+/**
+ * The config object contains all of the configuration options for
+ * the `YUI` instance. This object is supplied by the implementer
+ * when instantiating a `YUI` instance. Some properties have default
+ * values if they are not supplied by the implementer. This should
+ * not be updated directly because some values are cached. Use
+ * `applyConfig()` to update the config object on a YUI instance that
+ * has already been configured.
+ *
+ * @class config
+ * @static
+ */
+
+/**
+ * Allows the YUI seed file to fetch the loader component and library
+ * metadata to dynamically load additional dependencies.
+ *
+ * @property bootstrap
+ * @type boolean
+ * @default true
+ */
+
+/**
+ * Turns on writing Ylog messages to the browser console.
+ *
+ * @property debug
+ * @type boolean
+ * @default true
+ */
+
+/**
+ * Log to the browser console if debug is on and the browser has a
+ * supported console.
+ *
+ * @property useBrowserConsole
+ * @type boolean
+ * @default true
+ */
+
+/**
+ * A hash of log sources that should be logged. If specified, only
+ * log messages from these sources will be logged.
+ *
+ * @property logInclude
+ * @type object
+ */
+
+/**
+ * A hash of log sources that should be not be logged. If specified,
+ * all sources are logged if not on this list.
+ *
+ * @property logExclude
+ * @type object
+ */
+
+/**
+ * Set to true if the yui seed file was dynamically loaded in
+ * order to bootstrap components relying on the window load event
+ * and the `domready` custom event.
+ *
+ * @property injected
+ * @type boolean
+ * @default false
+ */
+
+/**
+ * If `throwFail` is set, `Y.error` will generate or re-throw a JS Error.
+ * Otherwise the failure is logged.
+ *
+ * @property throwFail
+ * @type boolean
+ * @default true
+ */
+
+/**
+ * The window/frame that this instance should operate in.
+ *
+ * @property win
+ * @type Window
+ * @default the window hosting YUI
+ */
+
+/**
+ * The document associated with the 'win' configuration.
+ *
+ * @property doc
+ * @type Document
+ * @default the document hosting YUI
+ */
+
+/**
+ * A list of modules that defines the YUI core (overrides the default list).
+ *
+ * @property core
+ * @type Array
+ * @default [ get,features,intl-base,yui-log,yui-later,loader-base, loader-rollup, loader-yui3 ]
+ */
+
+/**
+ * A list of languages in order of preference. This list is matched against
+ * the list of available languages in modules that the YUI instance uses to
+ * determine the best possible localization of language sensitive modules.
+ * Languages are represented using BCP 47 language tags, such as "en-GB" for
+ * English as used in the United Kingdom, or "zh-Hans-CN" for simplified
+ * Chinese as used in China. The list can be provided as a comma-separated
+ * list or as an array.
+ *
+ * @property lang
+ * @type string|string[]
+ */
+
+/**
+ * The default date format
+ * @property dateFormat
+ * @type string
+ * @deprecated use configuration in `DataType.Date.format()` instead.
+ */
+
+/**
+ * The default locale
+ * @property locale
+ * @type string
+ * @deprecated use `config.lang` instead.
+ */
+
+/**
+ * The default interval when polling in milliseconds.
+ * @property pollInterval
+ * @type int
+ * @default 20
+ */
+
+/**
+ * The number of dynamic nodes to insert by default before
+ * automatically removing them. This applies to script nodes
+ * because removing the node will not make the evaluated script
+ * unavailable. Dynamic CSS is not auto purged, because removing
+ * a linked style sheet will also remove the style definitions.
+ * @property purgethreshold
+ * @type int
+ * @default 20
+ */
+
+/**
+ * The default interval when polling in milliseconds.
+ * @property windowResizeDelay
+ * @type int
+ * @default 40
+ */
+
+/**
+ * Base directory for dynamic loading
+ * @property base
+ * @type string
+ */
+
+/*
+ * The secure base dir (not implemented)
+ * For dynamic loading.
+ * @property secureBase
+ * @type string
+ */
+
+/**
+ * The YUI combo service base dir. Ex: `http://yui.yahooapis.com/combo?`
+ * For dynamic loading.
+ * @property comboBase
+ * @type string
+ */
+
+/**
+ * The root path to prepend to module path for the combo service.
+ * Ex: 3.0.0b1/build/
+ * For dynamic loading.
+ * @property root
+ * @type string
+ */
+
+/**
+ * A filter to apply to result urls. This filter will modify the default
+ * path for all modules. The default path for the YUI library is the
+ * minified version of the files (e.g., event-min.js). The filter property
+ * can be a predefined filter or a custom filter. The valid predefined
+ * filters are:
+ * <dl>
+ * <dt>DEBUG</dt>
+ * <dd>Selects the debug versions of the library (e.g., event-debug.js).
+ * This option will automatically include the Logger widget</dd>
+ * <dt>RAW</dt>
+ * <dd>Selects the non-minified version of the library (e.g., event.js).</dd>
+ * </dl>
+ * You can also define a custom filter, which must be an object literal
+ * containing a search expression and a replace string:
+ *
+ * myFilter: {
+ * 'searchExp': "-min\\.js",
+ * 'replaceStr': "-debug.js"
+ * }
+ *
+ * For dynamic loading.
+ *
+ * @property filter
+ * @type string|object
+ */
+
+/**
+ * The `skin` config let's you configure application level skin
+ * customizations. It contains the following attributes which
+ * can be specified to override the defaults:
+ *
+ * // The default skin, which is automatically applied if not
+ * // overriden by a component-specific skin definition.
+ * // Change this in to apply a different skin globally
+ * defaultSkin: 'sam',
+ *
+ * // This is combined with the loader base property to get
+ * // the default root directory for a skin.
+ * base: 'assets/skins/',
+ *
+ * // Any component-specific overrides can be specified here,
+ * // making it possible to load different skins for different
+ * // components. It is possible to load more than one skin
+ * // for a given component as well.
+ * overrides: {
+ * slider: ['capsule', 'round']
+ * }
+ *
+ * For dynamic loading.
+ *
+ * @property skin
+ */
+
+/**
+ * Hash of per-component filter specification. If specified for a given
+ * component, this overrides the filter config.
+ *
+ * For dynamic loading.
+ *
+ * @property filters
+ */
+
+/**
+ * Use the YUI combo service to reduce the number of http connections
+ * required to load your dependencies. Turning this off will
+ * disable combo handling for YUI and all module groups configured
+ * with a combo service.
+ *
+ * For dynamic loading.
+ *
+ * @property combine
+ * @type boolean
+ * @default true if 'base' is not supplied, false if it is.
+ */
+
+/**
+ * A list of modules that should never be dynamically loaded
+ *
+ * @property ignore
+ * @type string[]
+ */
+
+/**
+ * A list of modules that should always be loaded when required, even if already
+ * present on the page.
+ *
+ * @property force
+ * @type string[]
+ */
+
+/**
+ * Node or id for a node that should be used as the insertion point for new
+ * nodes. For dynamic loading.
+ *
+ * @property insertBefore
+ * @type string
+ */
+
+/**
+ * Object literal containing attributes to add to dynamically loaded script
+ * nodes.
+ * @property jsAttributes
+ * @type string
+ */
+
+/**
+ * Object literal containing attributes to add to dynamically loaded link
+ * nodes.
+ * @property cssAttributes
+ * @type string
+ */
+
+/**
+ * Number of milliseconds before a timeout occurs when dynamically
+ * loading nodes. If not set, there is no timeout.
+ * @property timeout
+ * @type int
+ */
+
+/**
+ * Callback for the 'CSSComplete' event. When dynamically loading YUI
+ * components with CSS, this property fires when the CSS is finished
+ * loading but script loading is still ongoing. This provides an
+ * opportunity to enhance the presentation of a loading page a little
+ * bit before the entire loading process is done.
+ *
+ * @property onCSS
+ * @type function
+ */
+
+/**
+ * A hash of module definitions to add to the list of YUI components.
+ * These components can then be dynamically loaded side by side with
+ * YUI via the `use()` method. This is a hash, the key is the module
+ * name, and the value is an object literal specifying the metdata
+ * for the module. See `Loader.addModule` for the supported module
+ * metadata fields. Also see groups, which provides a way to
+ * configure the base and combo spec for a set of modules.
+ *
+ * modules: {
+ * mymod1: {
+ * requires: ['node'],
+ * fullpath: '/mymod1/mymod1.js'
+ * },
+ * mymod2: {
+ * requires: ['mymod1'],
+ * fullpath: '/mymod2/mymod2.js'
+ * },
+ * mymod3: '/js/mymod3.js',
+ * mycssmod: '/css/mycssmod.css'
+ * }
+ *
+ *
+ * @property modules
+ * @type object
+ */
+
+/**
+ * Aliases are dynamic groups of modules that can be used as
+ * shortcuts.
+ *
+ * YUI({
+ * aliases: {
+ * davglass: [ 'node', 'yql', 'dd' ],
+ * mine: [ 'davglass', 'autocomplete']
+ * }
+ * }).use('mine', function(Y) {
+ * //Node, YQL, DD & AutoComplete available here..
+ * });
+ *
+ * @property aliases
+ * @type object
+ */
+
+/**
+ * A hash of module group definitions. It for each group you
+ * can specify a list of modules and the base path and
+ * combo spec to use when dynamically loading the modules.
+ *
+ * groups: {
+ * yui2: {
+ * // specify whether or not this group has a combo service
+ * combine: true,
+ *
+ * // The comboSeperator to use with this group's combo handler
+ * comboSep: ';',
+ *
+ * // The maxURLLength for this server
+ * maxURLLength: 500,
+ *
+ * // the base path for non-combo paths
+ * base: 'http://yui.yahooapis.com/2.8.0r4/build/',
+ *
+ * // the path to the combo service
+ * comboBase: 'http://yui.yahooapis.com/combo?',
+ *
+ * // a fragment to prepend to the path attribute when
+ * // when building combo urls
+ * root: '2.8.0r4/build/',
+ *
+ * // the module definitions
+ * modules: {
+ * yui2_yde: {
+ * path: "yahoo-dom-event/yahoo-dom-event.js"
+ * },
+ * yui2_anim: {
+ * path: "animation/animation.js",
+ * requires: ['yui2_yde']
+ * }
+ * }
+ * }
+ * }
+ *
+ * @property groups
+ * @type object
+ */
+
+/**
+ * The loader 'path' attribute to the loader itself. This is combined
+ * with the 'base' attribute to dynamically load the loader component
+ * when boostrapping with the get utility alone.
+ *
+ * @property loaderPath
+ * @type string
+ * @default loader/loader-min.js
+ */
+
+/**
+ * Specifies whether or not YUI().use(...) will attempt to load CSS
+ * resources at all. Any truthy value will cause CSS dependencies
+ * to load when fetching script. The special value 'force' will
+ * cause CSS dependencies to be loaded even if no script is needed.
+ *
+ * @property fetchCSS
+ * @type boolean|string
+ * @default true
+ */
+
+/**
+ * The default gallery version to build gallery module urls
+ * @property gallery
+ * @type string
+ * @since 3.1.0
+ */
+
+/**
+ * The default YUI 2 version to build yui2 module urls. This is for
+ * intrinsic YUI 2 support via the 2in3 project. Also see the '2in3'
+ * config for pulling different revisions of the wrapped YUI 2
+ * modules.
+ * @since 3.1.0
+ * @property yui2
+ * @type string
+ * @default 2.9.0
+ */
+
+/**
+ * The 2in3 project is a deployment of the various versions of YUI 2
+ * deployed as first-class YUI 3 modules. Eventually, the wrapper
+ * for the modules will change (but the underlying YUI 2 code will
+ * be the same), and you can select a particular version of
+ * the wrapper modules via this config.
+ * @since 3.1.0
+ * @property 2in3
+ * @type string
+ * @default 4
+ */
+
+/**
+ * Alternative console log function for use in environments without
+ * a supported native console. The function is executed in the
+ * YUI instance context.
+ * @since 3.1.0
+ * @property logFn
+ * @type Function
+ */
+
+/**
+ * A callback to execute when Y.error is called. It receives the
+ * error message and an javascript error object if Y.error was
+ * executed because a javascript error was caught. The function
+ * is executed in the YUI instance context. Returning `true` from this
+ * function will stop the Error from being thrown.
+ *
+ * @since 3.2.0
+ * @property errorFn
+ * @type Function
+ */
+
+/**
+ * A callback to execute when the loader fails to load one or
+ * more resource. This could be because of a script load
+ * failure. It can also fail if a javascript module fails
+ * to register itself, but only when the 'requireRegistration'
+ * is true. If this function is defined, the use() callback will
+ * only be called when the loader succeeds, otherwise it always
+ * executes unless there was a javascript error when attaching
+ * a module.
+ *
+ * @since 3.3.0
+ * @property loadErrorFn
+ * @type Function
+ */
+
+/**
+ * When set to true, the YUI loader will expect that all modules
+ * it is responsible for loading will be first-class YUI modules
+ * that register themselves with the YUI global. If this is
+ * set to true, loader will fail if the module registration fails
+ * to happen after the script is loaded.
+ *
+ * @since 3.3.0
+ * @property requireRegistration
+ * @type boolean
+ * @default false
+ */
+
+/**
+ * Cache serviced use() requests.
+ * @since 3.3.0
+ * @property cacheUse
+ * @type boolean
+ * @default true
+ * @deprecated no longer used
+ */
+
+/**
+ * Whether or not YUI should use native ES5 functionality when available for
+ * features like `Y.Array.each()`, `Y.Object()`, etc. When `false`, YUI will
+ * always use its own fallback implementations instead of relying on ES5
+ * functionality, even when it's available.
+ *
+ * @property useNativeES5
+ * @type Boolean
+ * @default true
+ * @since 3.5.0
+ */
+
+/**
+Delay the `use` callback until a specific event has passed (`load`, `domready`, `contentready` or `available`)
+ at property delayUntil
+ at type String|Object
+ at since 3.6.0
+ at example
+
+You can use `load` or `domready` strings by default:
+
+ YUI({
+ delayUntil: 'domready'
+ }, function(Y) {
+ //This will not fire until 'domeready'
+ });
+
+Or you can delay until a node is available (with `available` or `contentready`):
+
+ YUI({
+ delayUntil: {
+ event: 'available',
+ args: '#foo'
+ }
+ }, function(Y) {
+ //This will not fire until '#foo' is
+ // available in the DOM
+ });
+
+
+*/
+YUI.add('yui-base', function(Y) {
+
+/*
+ * YUI stub
+ * @module yui
+ * @submodule yui-base
+ */
+/**
+ * The YUI module contains the components required for building the YUI
+ * seed file. This includes the script loading mechanism, a simple queue,
+ * and the core utilities for the library.
+ * @module yui
+ * @submodule yui-base
+ */
+
+/**
+ * Provides core language utilites and extensions used throughout YUI.
+ *
+ * @class Lang
+ * @static
+ */
+
+var L = Y.Lang || (Y.Lang = {}),
+
+STRING_PROTO = String.prototype,
+TOSTRING = Object.prototype.toString,
+
+TYPES = {
+ 'undefined' : 'undefined',
+ 'number' : 'number',
+ 'boolean' : 'boolean',
+ 'string' : 'string',
+ '[object Function]': 'function',
+ '[object RegExp]' : 'regexp',
+ '[object Array]' : 'array',
+ '[object Date]' : 'date',
+ '[object Error]' : 'error'
+},
+
+SUBREGEX = /\{\s*([^|}]+?)\s*(?:\|([^}]*))?\s*\}/g,
+TRIMREGEX = /^\s+|\s+$/g,
+NATIVE_FN_REGEX = /\{\s*\[(?:native code|function)\]\s*\}/i;
+
+// -- Protected Methods --------------------------------------------------------
+
+/**
+Returns `true` if the given function appears to be implemented in native code,
+`false` otherwise. Will always return `false` -- even in ES5-capable browsers --
+if the `useNativeES5` YUI config option is set to `false`.
+
+This isn't guaranteed to be 100% accurate and won't work for anything other than
+functions, but it can be useful for determining whether a function like
+`Array.prototype.forEach` is native or a JS shim provided by another library.
+
+There's a great article by @kangax discussing certain flaws with this technique:
+<http://perfectionkills.com/detecting-built-in-host-methods/>
+
+While his points are valid, it's still possible to benefit from this function
+as long as it's used carefully and sparingly, and in such a way that false
+negatives have minimal consequences. It's used internally to avoid using
+potentially broken non-native ES5 shims that have been added to the page by
+other libraries.
+
+ at method _isNative
+ at param {Function} fn Function to test.
+ at return {Boolean} `true` if _fn_ appears to be native, `false` otherwise.
+ at static
+ at protected
+ at since 3.5.0
+**/
+L._isNative = function (fn) {
+ return !!(Y.config.useNativeES5 && fn && NATIVE_FN_REGEX.test(fn));
+};
+
+// -- Public Methods -----------------------------------------------------------
+
+/**
+ * Determines whether or not the provided item is an array.
+ *
+ * Returns `false` for array-like collections such as the function `arguments`
+ * collection or `HTMLElement` collections. Use `Y.Array.test()` if you want to
+ * test for an array-like collection.
+ *
+ * @method isArray
+ * @param o The object to test.
+ * @return {boolean} true if o is an array.
+ * @static
+ */
+L.isArray = L._isNative(Array.isArray) ? Array.isArray : function (o) {
+ return L.type(o) === 'array';
+};
+
+/**
+ * Determines whether or not the provided item is a boolean.
+ * @method isBoolean
+ * @static
+ * @param o The object to test.
+ * @return {boolean} true if o is a boolean.
+ */
+L.isBoolean = function(o) {
+ return typeof o === 'boolean';
+};
+
+/**
+ * Determines whether or not the supplied item is a date instance.
+ * @method isDate
+ * @static
+ * @param o The object to test.
+ * @return {boolean} true if o is a date.
+ */
+L.isDate = function(o) {
+ return L.type(o) === 'date' && o.toString() !== 'Invalid Date' && !isNaN(o);
+};
+
+/**
+ * <p>
+ * Determines whether or not the provided item is a function.
+ * Note: Internet Explorer thinks certain functions are objects:
+ * </p>
+ *
+ * <pre>
+ * var obj = document.createElement("object");
+ * Y.Lang.isFunction(obj.getAttribute) // reports false in IE
+ *
+ * var input = document.createElement("input"); // append to body
+ * Y.Lang.isFunction(input.focus) // reports false in IE
+ * </pre>
+ *
+ * <p>
+ * You will have to implement additional tests if these functions
+ * matter to you.
+ * </p>
+ *
+ * @method isFunction
+ * @static
+ * @param o The object to test.
+ * @return {boolean} true if o is a function.
+ */
+L.isFunction = function(o) {
+ return L.type(o) === 'function';
+};
+
+/**
+ * Determines whether or not the provided item is null.
+ * @method isNull
+ * @static
+ * @param o The object to test.
+ * @return {boolean} true if o is null.
+ */
+L.isNull = function(o) {
+ return o === null;
+};
+
+/**
+ * Determines whether or not the provided item is a legal number.
+ * @method isNumber
+ * @static
+ * @param o The object to test.
+ * @return {boolean} true if o is a number.
+ */
+L.isNumber = function(o) {
+ return typeof o === 'number' && isFinite(o);
+};
+
+/**
+ * Determines whether or not the provided item is of type object
+ * or function. Note that arrays are also objects, so
+ * <code>Y.Lang.isObject([]) === true</code>.
+ * @method isObject
+ * @static
+ * @param o The object to test.
+ * @param failfn {boolean} fail if the input is a function.
+ * @return {boolean} true if o is an object.
+ * @see isPlainObject
+ */
+L.isObject = function(o, failfn) {
+ var t = typeof o;
+ return (o && (t === 'object' ||
+ (!failfn && (t === 'function' || L.isFunction(o))))) || false;
+};
+
+/**
+ * Determines whether or not the provided item is a string.
+ * @method isString
+ * @static
+ * @param o The object to test.
+ * @return {boolean} true if o is a string.
+ */
+L.isString = function(o) {
+ return typeof o === 'string';
+};
+
+/**
+ * Determines whether or not the provided item is undefined.
+ * @method isUndefined
+ * @static
+ * @param o The object to test.
+ * @return {boolean} true if o is undefined.
+ */
+L.isUndefined = function(o) {
+ return typeof o === 'undefined';
+};
+
+/**
+ * A convenience method for detecting a legitimate non-null value.
+ * Returns false for null/undefined/NaN, true for other values,
+ * including 0/false/''
+ * @method isValue
+ * @static
+ * @param o The item to test.
+ * @return {boolean} true if it is not null/undefined/NaN || false.
+ */
+L.isValue = function(o) {
+ var t = L.type(o);
+
+ switch (t) {
+ case 'number':
+ return isFinite(o);
+
+ case 'null': // fallthru
+ case 'undefined':
+ return false;
+
+ default:
+ return !!t;
+ }
+};
+
+/**
+ * Returns the current time in milliseconds.
+ *
+ * @method now
+ * @return {Number} Current time in milliseconds.
+ * @static
+ * @since 3.3.0
+ */
+L.now = Date.now || function () {
+ return new Date().getTime();
+};
+
+/**
+ * Lightweight version of <code>Y.substitute</code>. Uses the same template
+ * structure as <code>Y.substitute</code>, but doesn't support recursion,
+ * auto-object coersion, or formats.
+ * @method sub
+ * @param {string} s String to be modified.
+ * @param {object} o Object containing replacement values.
+ * @return {string} the substitute result.
+ * @static
+ * @since 3.2.0
+ */
+L.sub = function(s, o) {
+ return s.replace ? s.replace(SUBREGEX, function (match, key) {
+ return L.isUndefined(o[key]) ? match : o[key];
+ }) : s;
+};
+
+/**
+ * Returns a string without any leading or trailing whitespace. If
+ * the input is not a string, the input will be returned untouched.
+ * @method trim
+ * @static
+ * @param s {string} the string to trim.
+ * @return {string} the trimmed string.
+ */
+L.trim = STRING_PROTO.trim ? function(s) {
+ return s && s.trim ? s.trim() : s;
+} : function (s) {
+ try {
+ return s.replace(TRIMREGEX, '');
+ } catch (e) {
+ return s;
+ }
+};
+
+/**
+ * Returns a string without any leading whitespace.
+ * @method trimLeft
+ * @static
+ * @param s {string} the string to trim.
+ * @return {string} the trimmed string.
+ */
+L.trimLeft = STRING_PROTO.trimLeft ? function (s) {
+ return s.trimLeft();
+} : function (s) {
+ return s.replace(/^\s+/, '');
+};
+
+/**
+ * Returns a string without any trailing whitespace.
+ * @method trimRight
+ * @static
+ * @param s {string} the string to trim.
+ * @return {string} the trimmed string.
+ */
+L.trimRight = STRING_PROTO.trimRight ? function (s) {
+ return s.trimRight();
+} : function (s) {
+ return s.replace(/\s+$/, '');
+};
+
+/**
+Returns one of the following strings, representing the type of the item passed
+in:
+
+ * "array"
+ * "boolean"
+ * "date"
+ * "error"
+ * "function"
+ * "null"
+ * "number"
+ * "object"
+ * "regexp"
+ * "string"
+ * "undefined"
+
+Known issues:
+
+ * `typeof HTMLElementCollection` returns function in Safari, but
+ `Y.Lang.type()` reports "object", which could be a good thing --
+ but it actually caused the logic in <code>Y.Lang.isObject</code> to fail.
+
+ at method type
+ at param o the item to test.
+ at return {string} the detected type.
+ at static
+**/
+L.type = function(o) {
+ return TYPES[typeof o] || TYPES[TOSTRING.call(o)] || (o ? 'object' : 'null');
+};
+/**
+ at module yui
+ at submodule yui-base
+*/
+
+var Lang = Y.Lang,
+ Native = Array.prototype,
+
+ hasOwn = Object.prototype.hasOwnProperty;
+
+/**
+Provides utility methods for working with arrays. Additional array helpers can
+be found in the `collection` and `array-extras` modules.
+
+`Y.Array(thing)` returns a native array created from _thing_. Depending on
+_thing_'s type, one of the following will happen:
+
+ * Arrays are returned unmodified unless a non-zero _startIndex_ is
+ specified.
+ * Array-like collections (see `Array.test()`) are converted to arrays.
+ * For everything else, a new array is created with _thing_ as the sole
+ item.
+
+Note: elements that are also collections, such as `<form>` and `<select>`
+elements, are not automatically converted to arrays. To force a conversion,
+pass `true` as the value of the _force_ parameter.
+
+ at class Array
+ at constructor
+ at param {Any} thing The thing to arrayify.
+ at param {Number} [startIndex=0] If non-zero and _thing_ is an array or array-like
+ collection, a subset of items starting at the specified index will be
+ returned.
+ at param {Boolean} [force=false] If `true`, _thing_ will be treated as an
+ array-like collection no matter what.
+ at return {Array} A native array created from _thing_, according to the rules
+ described above.
+**/
+function YArray(thing, startIndex, force) {
+ var len, result;
+
+ startIndex || (startIndex = 0);
+
+ if (force || YArray.test(thing)) {
+ // IE throws when trying to slice HTMLElement collections.
+ try {
+ return Native.slice.call(thing, startIndex);
+ } catch (ex) {
+ result = [];
+
+ for (len = thing.length; startIndex < len; ++startIndex) {
+ result.push(thing[startIndex]);
+ }
+
+ return result;
+ }
+ }
+
+ return [thing];
+}
+
+Y.Array = YArray;
+
+/**
+Dedupes an array of strings, returning an array that's guaranteed to contain
+only one copy of a given string.
+
+This method differs from `Array.unique()` in that it's optimized for use only
+with strings, whereas `unique` may be used with other types (but is slower).
+Using `dedupe()` with non-string values may result in unexpected behavior.
+
+ at method dedupe
+ at param {String[]} array Array of strings to dedupe.
+ at return {Array} Deduped copy of _array_.
+ at static
+ at since 3.4.0
+**/
+YArray.dedupe = function (array) {
+ var hash = {},
+ results = [],
+ i, item, len;
+
+ for (i = 0, len = array.length; i < len; ++i) {
+ item = array[i];
+
+ if (!hasOwn.call(hash, item)) {
+ hash[item] = 1;
+ results.push(item);
+ }
+ }
+
+ return results;
+};
+
+/**
+Executes the supplied function on each item in the array. This method wraps
+the native ES5 `Array.forEach()` method if available.
+
+ at method each
+ at param {Array} array Array to iterate.
+ at param {Function} fn Function to execute on each item in the array. The function
+ will receive the following arguments:
+ @param {Any} fn.item Current array item.
+ @param {Number} fn.index Current array index.
+ @param {Array} fn.array Array being iterated.
+ at param {Object} [thisObj] `this` object to use when calling _fn_.
+ at return {YUI} The YUI instance.
+ at static
+**/
+YArray.each = YArray.forEach = Lang._isNative(Native.forEach) ? function (array, fn, thisObj) {
+ Native.forEach.call(array || [], fn, thisObj || Y);
+ return Y;
+} : function (array, fn, thisObj) {
+ for (var i = 0, len = (array && array.length) || 0; i < len; ++i) {
+ if (i in array) {
+ fn.call(thisObj || Y, array[i], i, array);
+ }
+ }
+
+ return Y;
+};
+
+/**
+Alias for `each()`.
+
+ at method forEach
+ at static
+**/
+
+/**
+Returns an object using the first array as keys and the second as values. If
+the second array is not provided, or if it doesn't contain the same number of
+values as the first array, then `true` will be used in place of the missing
+values.
+
+ at example
+
+ Y.Array.hash(['a', 'b', 'c'], ['foo', 'bar']);
+ // => {a: 'foo', b: 'bar', c: true}
+
+ at method hash
+ at param {String[]} keys Array of strings to use as keys.
+ at param {Array} [values] Array to use as values.
+ at return {Object} Hash using the first array as keys and the second as values.
+ at static
+**/
+YArray.hash = function (keys, values) {
+ var hash = {},
+ vlen = (values && values.length) || 0,
+ i, len;
+
+ for (i = 0, len = keys.length; i < len; ++i) {
+ if (i in keys) {
+ hash[keys[i]] = vlen > i && i in values ? values[i] : true;
+ }
+ }
+
+ return hash;
+};
+
+/**
+Returns the index of the first item in the array that's equal (using a strict
+equality check) to the specified _value_, or `-1` if the value isn't found.
+
+This method wraps the native ES5 `Array.indexOf()` method if available.
+
+ at method indexOf
+ at param {Array} array Array to search.
+ at param {Any} value Value to search for.
+ at param {Number} [from=0] The index at which to begin the search.
+ at return {Number} Index of the item strictly equal to _value_, or `-1` if not
+ found.
+ at static
+**/
+YArray.indexOf = Lang._isNative(Native.indexOf) ? function (array, value, from) {
+ return Native.indexOf.call(array, value, from);
+} : function (array, value, from) {
+ // http://es5.github.com/#x15.4.4.14
+ var len = array.length;
+
+ from = +from || 0;
+ from = (from > 0 || -1) * Math.floor(Math.abs(from));
+
+ if (from < 0) {
+ from += len;
+
+ if (from < 0) {
+ from = 0;
+ }
+ }
+
+ for (; from < len; ++from) {
+ if (from in array && array[from] === value) {
+ return from;
+ }
+ }
+
+ return -1;
+};
+
+/**
+Numeric sort convenience function.
+
+The native `Array.prototype.sort()` function converts values to strings and
+sorts them in lexicographic order, which is unsuitable for sorting numeric
+values. Provide `Array.numericSort` as a custom sort function when you want
+to sort values in numeric order.
+
+ at example
+
+ [42, 23, 8, 16, 4, 15].sort(Y.Array.numericSort);
+ // => [4, 8, 15, 16, 23, 42]
+
+ at method numericSort
+ at param {Number} a First value to compare.
+ at param {Number} b Second value to compare.
+ at return {Number} Difference between _a_ and _b_.
+ at static
+**/
+YArray.numericSort = function (a, b) {
+ return a - b;
+};
+
+/**
+Executes the supplied function on each item in the array. Returning a truthy
+value from the function will stop the processing of remaining items.
+
+ at method some
+ at param {Array} array Array to iterate over.
+ at param {Function} fn Function to execute on each item. The function will receive
+ the following arguments:
+ @param {Any} fn.value Current array item.
+ @param {Number} fn.index Current array index.
+ @param {Array} fn.array Array being iterated over.
+ at param {Object} [thisObj] `this` object to use when calling _fn_.
+ at return {Boolean} `true` if the function returns a truthy value on any of the
+ items in the array; `false` otherwise.
+ at static
+**/
+YArray.some = Lang._isNative(Native.some) ? function (array, fn, thisObj) {
+ return Native.some.call(array, fn, thisObj);
+} : function (array, fn, thisObj) {
+ for (var i = 0, len = array.length; i < len; ++i) {
+ if (i in array && fn.call(thisObj, array[i], i, array)) {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/**
+Evaluates _obj_ to determine if it's an array, an array-like collection, or
+something else. This is useful when working with the function `arguments`
+collection and `HTMLElement` collections.
+
+Note: This implementation doesn't consider elements that are also
+collections, such as `<form>` and `<select>`, to be array-like.
+
+ at method test
+ at param {Object} obj Object to test.
+ at return {Number} A number indicating the results of the test:
+
+ * 0: Neither an array nor an array-like collection.
+ * 1: Real array.
+ * 2: Array-like collection.
+
+ at static
+**/
+YArray.test = function (obj) {
+ var result = 0;
+
+ if (Lang.isArray(obj)) {
+ result = 1;
+ } else if (Lang.isObject(obj)) {
+ try {
+ // indexed, but no tagName (element) or alert (window),
+ // or functions without apply/call (Safari
+ // HTMLElementCollection bug).
+ if ('length' in obj && !obj.tagName && !obj.alert && !obj.apply) {
+ result = 2;
+ }
+ } catch (ex) {}
+ }
+
+ return result;
+};
+/**
+ * The YUI module contains the components required for building the YUI
+ * seed file. This includes the script loading mechanism, a simple queue,
+ * and the core utilities for the library.
+ * @module yui
+ * @submodule yui-base
+ */
+
+/**
+ * A simple FIFO queue. Items are added to the Queue with add(1..n items) and
+ * removed using next().
+ *
+ * @class Queue
+ * @constructor
+ * @param {MIXED} item* 0..n items to seed the queue.
+ */
+function Queue() {
+ this._init();
+ this.add.apply(this, arguments);
+}
+
+Queue.prototype = {
+ /**
+ * Initialize the queue
+ *
+ * @method _init
+ * @protected
+ */
+ _init: function() {
+ /**
+ * The collection of enqueued items
+ *
+ * @property _q
+ * @type Array
+ * @protected
+ */
+ this._q = [];
+ },
+
+ /**
+ * Get the next item in the queue. FIFO support
+ *
+ * @method next
+ * @return {MIXED} the next item in the queue.
+ */
+ next: function() {
+ return this._q.shift();
+ },
+
+ /**
+ * Get the last in the queue. LIFO support.
+ *
+ * @method last
+ * @return {MIXED} the last item in the queue.
+ */
+ last: function() {
+ return this._q.pop();
+ },
+
+ /**
+ * Add 0..n items to the end of the queue.
+ *
+ * @method add
+ * @param {MIXED} item* 0..n items.
+ * @return {object} this queue.
+ */
+ add: function() {
+ this._q.push.apply(this._q, arguments);
+
+ return this;
+ },
+
+ /**
+ * Returns the current number of queued items.
+ *
+ * @method size
+ * @return {Number} The size.
+ */
+ size: function() {
+ return this._q.length;
+ }
+};
+
+Y.Queue = Queue;
+
+YUI.Env._loaderQueue = YUI.Env._loaderQueue || new Queue();
+
+/**
+The YUI module contains the components required for building the YUI seed file.
+This includes the script loading mechanism, a simple queue, and the core
+utilities for the library.
+
+ at module yui
+ at submodule yui-base
+**/
+
+var CACHED_DELIMITER = '__',
+
+ hasOwn = Object.prototype.hasOwnProperty,
+ isObject = Y.Lang.isObject;
+
+/**
+Returns a wrapper for a function which caches the return value of that function,
+keyed off of the combined string representation of the argument values provided
+when the wrapper is called.
+
+Calling this function again with the same arguments will return the cached value
+rather than executing the wrapped function.
+
+Note that since the cache is keyed off of the string representation of arguments
+passed to the wrapper function, arguments that aren't strings and don't provide
+a meaningful `toString()` method may result in unexpected caching behavior. For
+example, the objects `{}` and `{foo: 'bar'}` would both be converted to the
+string `[object Object]` when used as a cache key.
+
+ at method cached
+ at param {Function} source The function to memoize.
+ at param {Object} [cache={}] Object in which to store cached values. You may seed
+ this object with pre-existing cached values if desired.
+ at param {any} [refetch] If supplied, this value is compared with the cached value
+ using a `==` comparison. If the values are equal, the wrapped function is
+ executed again even though a cached value exists.
+ at return {Function} Wrapped function.
+ at for YUI
+**/
+Y.cached = function (source, cache, refetch) {
+ cache || (cache = {});
+
+ return function (arg) {
+ var key = arguments.length > 1 ?
+ Array.prototype.join.call(arguments, CACHED_DELIMITER) :
+ String(arg);
+
+ if (!(key in cache) || (refetch && cache[key] == refetch)) {
+ cache[key] = source.apply(source, arguments);
+ }
+
+ return cache[key];
+ };
+};
+
+/**
+Returns the `location` object from the window/frame in which this YUI instance
+operates, or `undefined` when executing in a non-browser environment
+(e.g. Node.js).
+
+It is _not_ recommended to hold references to the `window.location` object
+outside of the scope of a function in which its properties are being accessed or
+its methods are being called. This is because of a nasty bug/issue that exists
+in both Safari and MobileSafari browsers:
+[WebKit Bug 34679](https://bugs.webkit.org/show_bug.cgi?id=34679).
+
+ at method getLocation
+ at return {location} The `location` object from the window/frame in which this YUI
+ instance operates.
+ at since 3.5.0
+**/
+Y.getLocation = function () {
+ // It is safer to look this up every time because yui-base is attached to a
+ // YUI instance before a user's config is applied; i.e. `Y.config.win` does
+ // not point the correct window object when this file is loaded.
+ var win = Y.config.win;
+
+ // It is not safe to hold a reference to the `location` object outside the
+ // scope in which it is being used. The WebKit engine used in Safari and
+ // MobileSafari will "disconnect" the `location` object from the `window`
+ // when a page is restored from back/forward history cache.
+ return win && win.location;
+};
+
+/**
+Returns a new object containing all of the properties of all the supplied
+objects. The properties from later objects will overwrite those in earlier
+objects.
+
+Passing in a single object will create a shallow copy of it. For a deep copy,
+use `clone()`.
+
+ at method merge
+ at param {Object} objects* One or more objects to merge.
+ at return {Object} A new merged object.
+**/
+Y.merge = function () {
+ var args = arguments,
+ i = 0,
+ len = args.length,
+ result = {};
+
+ for (; i < len; ++i) {
+ Y.mix(result, args[i], true);
+ }
+
+ return result;
+};
+
+/**
+Mixes _supplier_'s properties into _receiver_.
+
+Properties on _receiver_ or _receiver_'s prototype will not be overwritten or
+shadowed unless the _overwrite_ parameter is `true`, and will not be merged
+unless the _merge_ parameter is `true`.
+
+In the default mode (0), only properties the supplier owns are copied (prototype
+properties are not copied). The following copying modes are available:
+
+ * `0`: _Default_. Object to object.
+ * `1`: Prototype to prototype.
+ * `2`: Prototype to prototype and object to object.
+ * `3`: Prototype to object.
+ * `4`: Object to prototype.
+
+ at method mix
+ at param {Function|Object} receiver The object or function to receive the mixed
+ properties.
+ at param {Function|Object} supplier The object or function supplying the
+ properties to be mixed.
+ at param {Boolean} [overwrite=false] If `true`, properties that already exist
+ on the receiver will be overwritten with properties from the supplier.
+ at param {String[]} [whitelist] An array of property names to copy. If
+ specified, only the whitelisted properties will be copied, and all others
+ will be ignored.
+ at param {Number} [mode=0] Mix mode to use. See above for available modes.
+ at param {Boolean} [merge=false] If `true`, objects and arrays that already
+ exist on the receiver will have the corresponding object/array from the
+ supplier merged into them, rather than being skipped or overwritten. When
+ both _overwrite_ and _merge_ are `true`, _merge_ takes precedence.
+ at return {Function|Object|YUI} The receiver, or the YUI instance if the
+ specified receiver is falsy.
+**/
+Y.mix = function(receiver, supplier, overwrite, whitelist, mode, merge) {
+ var alwaysOverwrite, exists, from, i, key, len, to;
+
+ // If no supplier is given, we return the receiver. If no receiver is given,
+ // we return Y. Returning Y doesn't make much sense to me, but it's
+ // grandfathered in for backcompat reasons.
+ if (!receiver || !supplier) {
+ return receiver || Y;
+ }
+
+ if (mode) {
+ // In mode 2 (prototype to prototype and object to object), we recurse
+ // once to do the proto to proto mix. The object to object mix will be
+ // handled later on.
+ if (mode === 2) {
+ Y.mix(receiver.prototype, supplier.prototype, overwrite,
+ whitelist, 0, merge);
+ }
+
+ // Depending on which mode is specified, we may be copying from or to
+ // the prototypes of the supplier and receiver.
+ from = mode === 1 || mode === 3 ? supplier.prototype : supplier;
+ to = mode === 1 || mode === 4 ? receiver.prototype : receiver;
+
+ // If either the supplier or receiver doesn't actually have a
+ // prototype property, then we could end up with an undefined `from`
+ // or `to`. If that happens, we abort and return the receiver.
+ if (!from || !to) {
+ return receiver;
+ }
+ } else {
+ from = supplier;
+ to = receiver;
+ }
+
+ // If `overwrite` is truthy and `merge` is falsy, then we can skip a
+ // property existence check on each iteration and save some time.
+ alwaysOverwrite = overwrite && !merge;
+
+ if (whitelist) {
+ for (i = 0, len = whitelist.length; i < len; ++i) {
+ key = whitelist[i];
+
+ // We call `Object.prototype.hasOwnProperty` instead of calling
+ // `hasOwnProperty` on the object itself, since the object's
+ // `hasOwnProperty` method may have been overridden or removed.
+ // Also, some native objects don't implement a `hasOwnProperty`
+ // method.
+ if (!hasOwn.call(from, key)) {
+ continue;
+ }
+
+ // The `key in to` check here is (sadly) intentional for backwards
+ // compatibility reasons. It prevents undesired shadowing of
+ // prototype members on `to`.
+ exists = alwaysOverwrite ? false : key in to;
+
+ if (merge && exists && isObject(to[key], true)
+ && isObject(from[key], true)) {
+ // If we're in merge mode, and the key is present on both
+ // objects, and the value on both objects is either an object or
+ // an array (but not a function), then we recurse to merge the
+ // `from` value into the `to` value instead of overwriting it.
+ //
+ // Note: It's intentional that the whitelist isn't passed to the
+ // recursive call here. This is legacy behavior that lots of
+ // code still depends on.
+ Y.mix(to[key], from[key], overwrite, null, 0, merge);
+ } else if (overwrite || !exists) {
+ // We're not in merge mode, so we'll only copy the `from` value
+ // to the `to` value if we're in overwrite mode or if the
+ // current key doesn't exist on the `to` object.
+ to[key] = from[key];
+ }
+ }
+ } else {
+ for (key in from) {
+ // The code duplication here is for runtime performance reasons.
+ // Combining whitelist and non-whitelist operations into a single
+ // loop or breaking the shared logic out into a function both result
+ // in worse performance, and Y.mix is critical enough that the byte
+ // tradeoff is worth it.
+ if (!hasOwn.call(from, key)) {
+ continue;
+ }
+
+ // The `key in to` check here is (sadly) intentional for backwards
+ // compatibility reasons. It prevents undesired shadowing of
+ // prototype members on `to`.
+ exists = alwaysOverwrite ? false : key in to;
+
+ if (merge && exists && isObject(to[key], true)
+ && isObject(from[key], true)) {
+ Y.mix(to[key], from[key], overwrite, null, 0, merge);
+ } else if (overwrite || !exists) {
+ to[key] = from[key];
+ }
+ }
+
+ // If this is an IE browser with the JScript enumeration bug, force
+ // enumeration of the buggy properties by making a recursive call with
+ // the buggy properties as the whitelist.
+ if (Y.Object._hasEnumBug) {
+ Y.mix(to, from, overwrite, Y.Object._forceEnum, mode, merge);
+ }
+ }
+
+ return receiver;
+};
+/**
+ * The YUI module contains the components required for building the YUI
+ * seed file. This includes the script loading mechanism, a simple queue,
+ * and the core utilities for the library.
+ * @module yui
+ * @submodule yui-base
+ */
+
+/**
+ * Adds utilities to the YUI instance for working with objects.
+ *
+ * @class Object
+ */
+
+var Lang = Y.Lang,
+ hasOwn = Object.prototype.hasOwnProperty,
+
+ UNDEFINED, // <-- Note the comma. We're still declaring vars.
+
+/**
+ * Returns a new object that uses _obj_ as its prototype. This method wraps the
+ * native ES5 `Object.create()` method if available, but doesn't currently
+ * pass through `Object.create()`'s second argument (properties) in order to
+ * ensure compatibility with older browsers.
+ *
+ * @method ()
+ * @param {Object} obj Prototype object.
+ * @return {Object} New object using _obj_ as its prototype.
+ * @static
+ */
+O = Y.Object = Lang._isNative(Object.create) ? function (obj) {
+ // We currently wrap the native Object.create instead of simply aliasing it
+ // to ensure consistency with our fallback shim, which currently doesn't
+ // support Object.create()'s second argument (properties). Once we have a
+ // safe fallback for the properties arg, we can stop wrapping
+ // Object.create().
+ return Object.create(obj);
+} : (function () {
+ // Reusable constructor function for the Object.create() shim.
+ function F() {}
+
+ // The actual shim.
+ return function (obj) {
+ F.prototype = obj;
+ return new F();
+ };
+}()),
+
+/**
+ * Property names that IE doesn't enumerate in for..in loops, even when they
+ * should be enumerable. When `_hasEnumBug` is `true`, it's necessary to
+ * manually enumerate these properties.
+ *
+ * @property _forceEnum
+ * @type String[]
+ * @protected
+ * @static
+ */
+forceEnum = O._forceEnum = [
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'toString',
+ 'toLocaleString',
+ 'valueOf'
+],
+
+/**
+ * `true` if this browser has the JScript enumeration bug that prevents
+ * enumeration of the properties named in the `_forceEnum` array, `false`
+ * otherwise.
+ *
+ * See:
+ * - <https://developer.mozilla.org/en/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug>
+ * - <http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation>
+ *
+ * @property _hasEnumBug
+ * @type Boolean
+ * @protected
+ * @static
+ */
+hasEnumBug = O._hasEnumBug = !{valueOf: 0}.propertyIsEnumerable('valueOf'),
+
+/**
+ * `true` if this browser incorrectly considers the `prototype` property of
+ * functions to be enumerable. Currently known to affect Opera 11.50.
+ *
+ * @property _hasProtoEnumBug
+ * @type Boolean
+ * @protected
+ * @static
+ */
+hasProtoEnumBug = O._hasProtoEnumBug = (function () {}).propertyIsEnumerable('prototype'),
+
+/**
+ * Returns `true` if _key_ exists on _obj_, `false` if _key_ doesn't exist or
+ * exists only on _obj_'s prototype. This is essentially a safer version of
+ * `obj.hasOwnProperty()`.
+ *
+ * @method owns
+ * @param {Object} obj Object to test.
+ * @param {String} key Property name to look for.
+ * @return {Boolean} `true` if _key_ exists on _obj_, `false` otherwise.
+ * @static
+ */
+owns = O.owns = function (obj, key) {
+ return !!obj && hasOwn.call(obj, key);
+}; // <-- End of var declarations.
+
+/**
+ * Alias for `owns()`.
+ *
+ * @method hasKey
+ * @param {Object} obj Object to test.
+ * @param {String} key Property name to look for.
+ * @return {Boolean} `true` if _key_ exists on _obj_, `false` otherwise.
+ * @static
+ */
+O.hasKey = owns;
+
+/**
+ * Returns an array containing the object's enumerable keys. Does not include
+ * prototype keys or non-enumerable keys.
+ *
+ * Note that keys are returned in enumeration order (that is, in the same order
+ * that they would be enumerated by a `for-in` loop), which may not be the same
+ * as the order in which they were defined.
+ *
+ * This method is an alias for the native ES5 `Object.keys()` method if
+ * available.
+ *
+ * @example
+ *
+ * Y.Object.keys({a: 'foo', b: 'bar', c: 'baz'});
+ * // => ['a', 'b', 'c']
+ *
+ * @method keys
+ * @param {Object} obj An object.
+ * @return {String[]} Array of keys.
+ * @static
+ */
+O.keys = Lang._isNative(Object.keys) ? Object.keys : function (obj) {
+ if (!Lang.isObject(obj)) {
+ throw new TypeError('Object.keys called on a non-object');
+ }
+
+ var keys = [],
+ i, key, len;
+
+ if (hasProtoEnumBug && typeof obj === 'function') {
+ for (key in obj) {
+ if (owns(obj, key) && key !== 'prototype') {
+ keys.push(key);
+ }
+ }
+ } else {
+ for (key in obj) {
+ if (owns(obj, key)) {
+ keys.push(key);
+ }
+ }
+ }
+
+ if (hasEnumBug) {
+ for (i = 0, len = forceEnum.length; i < len; ++i) {
+ key = forceEnum[i];
+
+ if (owns(obj, key)) {
+ keys.push(key);
+ }
+ }
+ }
+
+ return keys;
+};
+
+/**
+ * Returns an array containing the values of the object's enumerable keys.
+ *
+ * Note that values are returned in enumeration order (that is, in the same
+ * order that they would be enumerated by a `for-in` loop), which may not be the
+ * same as the order in which they were defined.
+ *
+ * @example
+ *
+ * Y.Object.values({a: 'foo', b: 'bar', c: 'baz'});
+ * // => ['foo', 'bar', 'baz']
+ *
+ * @method values
+ * @param {Object} obj An object.
+ * @return {Array} Array of values.
+ * @static
+ */
+O.values = function (obj) {
+ var keys = O.keys(obj),
+ i = 0,
+ len = keys.length,
+ values = [];
+
+ for (; i < len; ++i) {
+ values.push(obj[keys[i]]);
+ }
+
+ return values;
+};
+
+/**
+ * Returns the number of enumerable keys owned by an object.
+ *
+ * @method size
+ * @param {Object} obj An object.
+ * @return {Number} The object's size.
+ * @static
+ */
+O.size = function (obj) {
+ try {
+ return O.keys(obj).length;
+ } catch (ex) {
+ return 0; // Legacy behavior for non-objects.
+ }
+};
+
+/**
+ * Returns `true` if the object owns an enumerable property with the specified
+ * value.
+ *
+ * @method hasValue
+ * @param {Object} obj An object.
+ * @param {any} value The value to search for.
+ * @return {Boolean} `true` if _obj_ contains _value_, `false` otherwise.
+ * @static
+ */
+O.hasValue = function (obj, value) {
+ return Y.Array.indexOf(O.values(obj), value) > -1;
+};
+
+/**
+ * Executes a function on each enumerable property in _obj_. The function
+ * receives the value, the key, and the object itself as parameters (in that
+ * order).
+ *
+ * By default, only properties owned by _obj_ are enumerated. To include
+ * prototype properties, set the _proto_ parameter to `true`.
+ *
+ * @method each
+ * @param {Object} obj Object to enumerate.
+ * @param {Function} fn Function to execute on each enumerable property.
+ * @param {mixed} fn.value Value of the current property.
+ * @param {String} fn.key Key of the current property.
+ * @param {Object} fn.obj Object being enumerated.
+ * @param {Object} [thisObj] `this` object to use when calling _fn_.
+ * @param {Boolean} [proto=false] Include prototype properties.
+ * @return {YUI} the YUI instance.
+ * @chainable
+ * @static
+ */
+O.each = function (obj, fn, thisObj, proto) {
+ var key;
+
+ for (key in obj) {
+ if (proto || owns(obj, key)) {
+ fn.call(thisObj || Y, obj[key], key, obj);
+ }
+ }
+
+ return Y;
+};
+
+/**
+ * Executes a function on each enumerable property in _obj_, but halts if the
+ * function returns a truthy value. The function receives the value, the key,
+ * and the object itself as paramters (in that order).
+ *
+ * By default, only properties owned by _obj_ are enumerated. To include
+ * prototype properties, set the _proto_ parameter to `true`.
+ *
+ * @method some
+ * @param {Object} obj Object to enumerate.
+ * @param {Function} fn Function to execute on each enumerable property.
+ * @param {mixed} fn.value Value of the current property.
+ * @param {String} fn.key Key of the current property.
+ * @param {Object} fn.obj Object being enumerated.
+ * @param {Object} [thisObj] `this` object to use when calling _fn_.
+ * @param {Boolean} [proto=false] Include prototype properties.
+ * @return {Boolean} `true` if any execution of _fn_ returns a truthy value,
+ * `false` otherwise.
+ * @static
+ */
+O.some = function (obj, fn, thisObj, proto) {
+ var key;
+
+ for (key in obj) {
+ if (proto || owns(obj, key)) {
+ if (fn.call(thisObj || Y, obj[key], key, obj)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Retrieves the sub value at the provided path,
+ * from the value object provided.
+ *
+ * @method getValue
+ * @static
+ * @param o The object from which to extract the property value.
+ * @param path {Array} A path array, specifying the object traversal path
+ * from which to obtain the sub value.
+ * @return {Any} The value stored in the path, undefined if not found,
+ * undefined if the source is not an object. Returns the source object
+ * if an empty path is provided.
+ */
+O.getValue = function(o, path) {
+ if (!Lang.isObject(o)) {
+ return UNDEFINED;
+ }
+
+ var i,
+ p = Y.Array(path),
+ l = p.length;
+
+ for (i = 0; o !== UNDEFINED && i < l; i++) {
+ o = o[p[i]];
+ }
+
+ return o;
+};
+
+/**
+ * Sets the sub-attribute value at the provided path on the
+ * value object. Returns the modified value object, or
+ * undefined if the path is invalid.
+ *
+ * @method setValue
+ * @static
+ * @param o The object on which to set the sub value.
+ * @param path {Array} A path array, specifying the object traversal path
+ * at which to set the sub value.
+ * @param val {Any} The new value for the sub-attribute.
+ * @return {Object} The modified object, with the new sub value set, or
+ * undefined, if the path was invalid.
+ */
+O.setValue = function(o, path, val) {
+ var i,
+ p = Y.Array(path),
+ leafIdx = p.length - 1,
+ ref = o;
+
+ if (leafIdx >= 0) {
+ for (i = 0; ref !== UNDEFINED && i < leafIdx; i++) {
+ ref = ref[p[i]];
+ }
+
+ if (ref !== UNDEFINED) {
+ ref[p[i]] = val;
+ } else {
+ return UNDEFINED;
+ }
+ }
+
+ return o;
+};
+
+/**
+ * Returns `true` if the object has no enumerable properties of its own.
+ *
+ * @method isEmpty
+ * @param {Object} obj An object.
+ * @return {Boolean} `true` if the object is empty.
+ * @static
+ * @since 3.2.0
+ */
+O.isEmpty = function (obj) {
+ return !O.keys(Object(obj)).length;
+};
+/**
+ * The YUI module contains the components required for building the YUI seed
+ * file. This includes the script loading mechanism, a simple queue, and the
+ * core utilities for the library.
+ * @module yui
+ * @submodule yui-base
+ */
+
+/**
+ * YUI user agent detection.
+ * Do not fork for a browser if it can be avoided. Use feature detection when
+ * you can. Use the user agent as a last resort. For all fields listed
+ * as @type float, UA stores a version number for the browser engine,
+ * 0 otherwise. This value may or may not map to the version number of
+ * the browser using the engine. The value is presented as a float so
+ * that it can easily be used for boolean evaluation as well as for
+ * looking for a particular range of versions. Because of this,
+ * some of the granularity of the version info may be lost. The fields that
+ * are @type string default to null. The API docs list the values that
+ * these fields can have.
+ * @class UA
+ * @static
+ */
+
+/**
+* Static method on `YUI.Env` for parsing a UA string. Called at instantiation
+* to populate `Y.UA`.
+*
+* @static
+* @method parseUA
+* @param {String} [subUA=navigator.userAgent] UA string to parse
+* @return {Object} The Y.UA object
+*/
+YUI.Env.parseUA = function(subUA) {
+
+ var numberify = function(s) {
+ var c = 0;
+ return parseFloat(s.replace(/\./g, function() {
+ return (c++ == 1) ? '' : '.';
+ }));
+ },
+
+ win = Y.config.win,
+
+ nav = win && win.navigator,
+
+ o = {
+
+ /**
+ * Internet Explorer version number or 0. Example: 6
+ * @property ie
+ * @type float
+ * @static
+ */
+ ie: 0,
+
+ /**
+ * Opera version number or 0. Example: 9.2
+ * @property opera
+ * @type float
+ * @static
+ */
+ opera: 0,
+
+ /**
+ * Gecko engine revision number. Will evaluate to 1 if Gecko
+ * is detected but the revision could not be found. Other browsers
+ * will be 0. Example: 1.8
+ * <pre>
+ * Firefox 1.0.0.4: 1.7.8 <-- Reports 1.7
+ * Firefox 1.5.0.9: 1.8.0.9 <-- 1.8
+ * Firefox 2.0.0.3: 1.8.1.3 <-- 1.81
+ * Firefox 3.0 <-- 1.9
+ * Firefox 3.5 <-- 1.91
+ * </pre>
+ * @property gecko
+ * @type float
+ * @static
+ */
+ gecko: 0,
+
+ /**
+ * AppleWebKit version. KHTML browsers that are not WebKit browsers
+ * will evaluate to 1, other browsers 0. Example: 418.9
+ * <pre>
+ * Safari 1.3.2 (312.6): 312.8.1 <-- Reports 312.8 -- currently the
+ * latest available for Mac OSX 10.3.
+ * Safari 2.0.2: 416 <-- hasOwnProperty introduced
+ * Safari 2.0.4: 418 <-- preventDefault fixed
+ * Safari 2.0.4 (419.3): 418.9.1 <-- One version of Safari may run
+ * different versions of webkit
+ * Safari 2.0.4 (419.3): 419 <-- Tiger installations that have been
+ * updated, but not updated
+ * to the latest patch.
+ * Webkit 212 nightly: 522+ <-- Safari 3.0 precursor (with native
+ * SVG and many major issues fixed).
+ * Safari 3.0.4 (523.12) 523.12 <-- First Tiger release - automatic
+ * update from 2.x via the 10.4.11 OS patch.
+ * Webkit nightly 1/2008:525+ <-- Supports DOMContentLoaded event.
+ * yahoo.com user agent hack removed.
+ * </pre>
+ * http://en.wikipedia.org/wiki/Safari_version_history
+ * @property webkit
+ * @type float
+ * @static
+ */
+ webkit: 0,
+
+ /**
+ * Safari will be detected as webkit, but this property will also
+ * be populated with the Safari version number
+ * @property safari
+ * @type float
+ * @static
+ */
+ safari: 0,
+
+ /**
+ * Chrome will be detected as webkit, but this property will also
+ * be populated with the Chrome version number
+ * @property chrome
+ * @type float
+ * @static
+ */
+ chrome: 0,
+
+ /**
+ * The mobile property will be set to a string containing any relevant
+ * user agent information when a modern mobile browser is detected.
+ * Currently limited to Safari on the iPhone/iPod Touch, Nokia N-series
+ * devices with the WebKit-based browser, and Opera Mini.
+ * @property mobile
+ * @type string
+ * @default null
+ * @static
+ */
+ mobile: null,
+
+ /**
+ * Adobe AIR version number or 0. Only populated if webkit is detected.
+ * Example: 1.0
+ * @property air
+ * @type float
+ */
+ air: 0,
+ /**
+ * PhantomJS version number or 0. Only populated if webkit is detected.
+ * Example: 1.0
+ * @property phantomjs
+ * @type float
+ */
+ phantomjs: 0,
+ /**
+ * Adobe AIR version number or 0. Only populated if webkit is detected.
+ * Example: 1.0
+ * @property air
+ * @type float
+ */
+ air: 0,
+ /**
+ * Detects Apple iPad's OS version
+ * @property ipad
+ * @type float
+ * @static
+ */
+ ipad: 0,
+ /**
+ * Detects Apple iPhone's OS version
+ * @property iphone
+ * @type float
+ * @static
+ */
+ iphone: 0,
+ /**
+ * Detects Apples iPod's OS version
+ * @property ipod
+ * @type float
+ * @static
+ */
+ ipod: 0,
+ /**
+ * General truthy check for iPad, iPhone or iPod
+ * @property ios
+ * @type Boolean
+ * @default null
+ * @static
+ */
+ ios: null,
+ /**
+ * Detects Googles Android OS version
+ * @property android
+ * @type float
+ * @static
+ */
+ android: 0,
+ /**
+ * Detects Kindle Silk
+ * @property silk
+ * @type float
+ * @static
+ */
+ silk: 0,
+ /**
+ * Detects Kindle Silk Acceleration
+ * @property accel
+ * @type Boolean
+ * @static
+ */
+ accel: false,
+ /**
+ * Detects Palms WebOS version
+ * @property webos
+ * @type float
+ * @static
+ */
+ webos: 0,
+
+ /**
+ * Google Caja version number or 0.
+ * @property caja
+ * @type float
+ */
+ caja: nav && nav.cajaVersion,
+
+ /**
+ * Set to true if the page appears to be in SSL
+ * @property secure
+ * @type boolean
+ * @static
+ */
+ secure: false,
+
+ /**
+ * The operating system. Currently only detecting windows or macintosh
+ * @property os
+ * @type string
+ * @default null
+ * @static
+ */
+ os: null,
+
+ /**
+ * The Nodejs Version
+ * @property nodejs
+ * @type float
+ * @default 0
+ * @static
+ */
+ nodejs: 0
+ },
+
+ ua = subUA || nav && nav.userAgent,
+
+ loc = win && win.location,
+
+ href = loc && loc.href,
+
+ m;
+
+ /**
+ * The User Agent string that was parsed
+ * @property userAgent
+ * @type String
+ * @static
+ */
+ o.userAgent = ua;
+
+
+ o.secure = href && (href.toLowerCase().indexOf('https') === 0);
+
+ if (ua) {
+
+ if ((/windows|win32/i).test(ua)) {
+ o.os = 'windows';
+ } else if ((/macintosh|mac_powerpc/i).test(ua)) {
+ o.os = 'macintosh';
+ } else if ((/android/i).test(ua)) {
+ o.os = 'android';
+ } else if ((/symbos/i).test(ua)) {
+ o.os = 'symbos';
+ } else if ((/linux/i).test(ua)) {
+ o.os = 'linux';
+ } else if ((/rhino/i).test(ua)) {
+ o.os = 'rhino';
+ }
+
+ // Modern KHTML browsers should qualify as Safari X-Grade
+ if ((/KHTML/).test(ua)) {
+ o.webkit = 1;
+ }
+ if ((/IEMobile|XBLWP7/).test(ua)) {
+ o.mobile = 'windows';
+ }
+ if ((/Fennec/).test(ua)) {
+ o.mobile = 'gecko';
+ }
+ // Modern WebKit browsers are at least X-Grade
+ m = ua.match(/AppleWebKit\/([^\s]*)/);
+ if (m && m[1]) {
+ o.webkit = numberify(m[1]);
+ o.safari = o.webkit;
+
+ if (/PhantomJS/.test(ua)) {
+ m = ua.match(/PhantomJS\/([^\s]*)/);
+ if (m && m[1]) {
+ o.phantomjs = numberify(m[1]);
+ }
+ }
+
+ // Mobile browser check
+ if (/ Mobile\//.test(ua) || (/iPad|iPod|iPhone/).test(ua)) {
+ o.mobile = 'Apple'; // iPhone or iPod Touch
+
+ m = ua.match(/OS ([^\s]*)/);
+ if (m && m[1]) {
+ m = numberify(m[1].replace('_', '.'));
+ }
+ o.ios = m;
+ o.os = 'ios';
+ o.ipad = o.ipod = o.iphone = 0;
+
+ m = ua.match(/iPad|iPod|iPhone/);
+ if (m && m[0]) {
+ o[m[0].toLowerCase()] = o.ios;
+ }
+ } else {
+ m = ua.match(/NokiaN[^\/]*|webOS\/\d\.\d/);
+ if (m) {
+ // Nokia N-series, webOS, ex: NokiaN95
+ o.mobile = m[0];
+ }
+ if (/webOS/.test(ua)) {
+ o.mobile = 'WebOS';
+ m = ua.match(/webOS\/([^\s]*);/);
+ if (m && m[1]) {
+ o.webos = numberify(m[1]);
+ }
+ }
+ if (/ Android/.test(ua)) {
+ if (/Mobile/.test(ua)) {
+ o.mobile = 'Android';
+ }
+ m = ua.match(/Android ([^\s]*);/);
+ if (m && m[1]) {
+ o.android = numberify(m[1]);
+ }
+
+ }
+ if (/Silk/.test(ua)) {
+ m = ua.match(/Silk\/([^\s]*)\)/);
+ if (m && m[1]) {
+ o.silk = numberify(m[1]);
+ }
+ if (!o.android) {
+ o.android = 2.34; //Hack for desktop mode in Kindle
+ o.os = 'Android';
+ }
+ if (/Accelerated=true/.test(ua)) {
+ o.accel = true;
+ }
+ }
+ }
+
+ m = ua.match(/(Chrome|CrMo|CriOS)\/([^\s]*)/);
+ if (m && m[1] && m[2]) {
+ o.chrome = numberify(m[2]); // Chrome
+ o.safari = 0; //Reset safari back to 0
+ if (m[1] === 'CrMo') {
+ o.mobile = 'chrome';
+ }
+ } else {
+ m = ua.match(/AdobeAIR\/([^\s]*)/);
+ if (m) {
+ o.air = m[0]; // Adobe AIR 1.0 or better
+ }
+ }
+ }
+
+ if (!o.webkit) { // not webkit
+// @todo check Opera/8.01 (J2ME/MIDP; Opera Mini/2.0.4509/1316; fi; U; ssr)
+ if (/Opera/.test(ua)) {
+ m = ua.match(/Opera[\s\/]([^\s]*)/);
+ if (m && m[1]) {
+ o.opera = numberify(m[1]);
+ }
+ m = ua.match(/Version\/([^\s]*)/);
+ if (m && m[1]) {
+ o.opera = numberify(m[1]); // opera 10+
+ }
+
+ if (/Opera Mobi/.test(ua)) {
+ o.mobile = 'opera';
+ m = ua.replace('Opera Mobi', '').match(/Opera ([^\s]*)/);
+ if (m && m[1]) {
+ o.opera = numberify(m[1]);
+ }
+ }
+ m = ua.match(/Opera Mini[^;]*/);
+
+ if (m) {
+ o.mobile = m[0]; // ex: Opera Mini/2.0.4509/1316
+ }
+ } else { // not opera or webkit
+ m = ua.match(/MSIE\s([^;]*)/);
+ if (m && m[1]) {
+ o.ie = numberify(m[1]);
+ } else { // not opera, webkit, or ie
+ m = ua.match(/Gecko\/([^\s]*)/);
+ if (m) {
+ o.gecko = 1; // Gecko detected, look for revision
+ m = ua.match(/rv:([^\s\)]*)/);
+ if (m && m[1]) {
+ o.gecko = numberify(m[1]);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //It was a parsed UA, do not assign the global value.
+ if (!subUA) {
+
+ if (typeof process == 'object') {
+
+ if (process.versions && process.versions.node) {
+ //NodeJS
+ o.os = process.platform;
+ o.nodejs = numberify(process.versions.node);
+ }
+ }
+
+ YUI.Env.UA = o;
+
+ }
+
+ return o;
+};
+
+
+Y.UA = YUI.Env.UA || YUI.Env.parseUA();
+
+/**
+Performs a simple comparison between two version numbers, accounting for
+standard versioning logic such as the fact that "535.8" is a lower version than
+"535.24", even though a simple numerical comparison would indicate that it's
+greater. Also accounts for cases such as "1.1" vs. "1.1.0", which are
+considered equivalent.
+
+Returns -1 if version _a_ is lower than version _b_, 0 if they're equivalent,
+1 if _a_ is higher than _b_.
+
+Versions may be numbers or strings containing numbers and dots. For example,
+both `535` and `"535.8.10"` are acceptable. A version string containing
+non-numeric characters, like `"535.8.beta"`, may produce unexpected results.
+
+ at method compareVersions
+ at param {Number|String} a First version number to compare.
+ at param {Number|String} b Second version number to compare.
+ at return -1 if _a_ is lower than _b_, 0 if they're equivalent, 1 if _a_ is
+ higher than _b_.
+**/
+Y.UA.compareVersions = function (a, b) {
+ var aPart, aParts, bPart, bParts, i, len;
+
+ if (a === b) {
+ return 0;
+ }
+
+ aParts = (a + '').split('.');
+ bParts = (b + '').split('.');
+
+ for (i = 0, len = Math.max(aParts.length, bParts.length); i < len; ++i) {
+ aPart = parseInt(aParts[i], 10);
+ bPart = parseInt(bParts[i], 10);
+
+ isNaN(aPart) && (aPart = 0);
+ isNaN(bPart) && (bPart = 0);
+
+ if (aPart < bPart) {
+ return -1;
+ }
+
+ if (aPart > bPart) {
+ return 1;
+ }
+ }
+
+ return 0;
+};
+YUI.Env.aliases = {
+ "anim": ["anim-base","anim-color","anim-curve","anim-easing","anim-node-plugin","anim-scroll","anim-xy"],
+ "app": ["app-base","app-transitions","lazy-model-list","model","model-list","model-sync-rest","router","view","view-node-map"],
+ "attribute": ["attribute-base","attribute-complex"],
+ "autocomplete": ["autocomplete-base","autocomplete-sources","autocomplete-list","autocomplete-plugin"],
+ "base": ["base-base","base-pluginhost","base-build"],
+ "cache": ["cache-base","cache-offline","cache-plugin"],
+ "collection": ["array-extras","arraylist","arraylist-add","arraylist-filter","array-invoke"],
+ "controller": ["router"],
+ "dataschema": ["dataschema-base","dataschema-json","dataschema-xml","dataschema-array","dataschema-text"],
+ "datasource": ["datasource-local","datasource-io","datasource-get","datasource-function","datasource-cache","datasource-jsonschema","datasource-xmlschema","datasource-arrayschema","datasource-textschema","datasource-polling"],
+ "datatable": ["datatable-core","datatable-table","datatable-head","datatable-body","datatable-base","datatable-column-widths","datatable-message","datatable-mutable","datatable-sort","datatable-datasource"],
+ "datatable-deprecated": ["datatable-base-deprecated","datatable-datasource-deprecated","datatable-sort-deprecated","datatable-scroll-deprecated"],
+ "datatype": ["datatype-number","datatype-date","datatype-xml"],
+ "datatype-date": ["datatype-date-parse","datatype-date-format"],
+ "datatype-number": ["datatype-number-parse","datatype-number-format"],
+ "datatype-xml": ["datatype-xml-parse","datatype-xml-format"],
+ "dd": ["dd-ddm-base","dd-ddm","dd-ddm-drop","dd-drag","dd-proxy","dd-constrain","dd-drop","dd-scroll","dd-delegate"],
+ "dom": ["dom-base","dom-screen","dom-style","selector-native","selector"],
+ "editor": ["frame","editor-selection","exec-command","editor-base","editor-para","editor-br","editor-bidi","editor-tab","createlink-base"],
+ "event": ["event-base","event-delegate","event-synthetic","event-mousewheel","event-mouseenter","event-key","event-focus","event-resize","event-hover","event-outside","event-touch","event-move","event-flick","event-valuechange"],
+ "event-custom": ["event-custom-base","event-custom-complex"],
+ "event-gestures": ["event-flick","event-move"],
+ "handlebars": ["handlebars-compiler"],
+ "highlight": ["highlight-base","highlight-accentfold"],
+ "history": ["history-base","history-hash","history-hash-ie","history-html5"],
+ "io": ["io-base","io-xdr","io-form","io-upload-iframe","io-queue"],
+ "json": ["json-parse","json-stringify"],
+ "loader": ["loader-base","loader-rollup","loader-yui3"],
+ "node": ["node-base","node-event-delegate","node-pluginhost","node-screen","node-style"],
+ "pluginhost": ["pluginhost-base","pluginhost-config"],
+ "querystring": ["querystring-parse","querystring-stringify"],
+ "recordset": ["recordset-base","recordset-sort","recordset-filter","recordset-indexer"],
+ "resize": ["resize-base","resize-proxy","resize-constrain"],
+ "slider": ["slider-base","slider-value-range","clickable-rail","range-slider"],
+ "text": ["text-accentfold","text-wordbreak"],
+ "widget": ["widget-base","widget-htmlparser","widget-skin","widget-uievents"]
+};
+
+
+}, '@VERSION@' );
+YUI.add('get', function(Y) {
+
+/*jslint boss:true, expr:true, laxbreak: true */
+
+/**
+Provides dynamic loading of remote JavaScript and CSS resources.
+
+ at module get
+ at class Get
+ at static
+**/
+
+var Lang = Y.Lang,
+
+ CUSTOM_ATTRS, // defined lazily in Y.Get.Transaction._createNode()
+
+ Get, Transaction;
+
+Y.Get = Get = {
+ // -- Public Properties ----------------------------------------------------
+
+ /**
+ Default options for CSS requests. Options specified here will override
+ global defaults for CSS requests.
+
+ See the `options` property for all available options.
+
+ @property cssOptions
+ @type Object
+ @static
+ @since 3.5.0
+ **/
+ cssOptions: {
+ attributes: {
+ rel: 'stylesheet'
+ },
+
+ doc : Y.config.linkDoc || Y.config.doc,
+ pollInterval: 50
+ },
+
+ /**
+ Default options for JS requests. Options specified here will override global
+ defaults for JS requests.
+
+ See the `options` property for all available options.
+
+ @property jsOptions
+ @type Object
+ @static
+ @since 3.5.0
+ **/
+ jsOptions: {
+ autopurge: true,
+ doc : Y.config.scriptDoc || Y.config.doc
+ },
+
+ /**
+ Default options to use for all requests.
+
+ Note that while all available options are documented here for ease of
+ discovery, some options (like callback functions) only make sense at the
+ transaction level.
+
+ Callback functions specified via the options object or the `options`
+ parameter of the `css()`, `js()`, or `load()` methods will receive the
+ transaction object as a parameter. See `Y.Get.Transaction` for details on
+ the properties and methods available on transactions.
+
+ @static
+ @since 3.5.0
+ @property {Object} options
+
+ @property {Boolean} [options.async=false] Whether or not to load scripts
+ asynchronously, meaning they're requested in parallel and execution
+ order is not guaranteed. Has no effect on CSS, since CSS is always
+ loaded asynchronously.
+
+ @property {Object} [options.attributes] HTML attribute name/value pairs that
+ should be added to inserted nodes. By default, the `charset` attribute
+ will be set to "utf-8" and nodes will be given an auto-generated `id`
+ attribute, but you can override these with your own values if desired.
+
+ @property {Boolean} [options.autopurge] Whether or not to automatically
+ purge inserted nodes after the purge threshold is reached. This is
+ `true` by default for JavaScript, but `false` for CSS since purging a
+ CSS node will also remove any styling applied by the referenced file.
+
+ @property {Object} [options.context] `this` object to use when calling
+ callback functions. Defaults to the transaction object.
+
+ @property {Mixed} [options.data] Arbitrary data object to pass to "on*"
+ callbacks.
+
+ @property {Document} [options.doc] Document into which nodes should be
+ inserted. By default, the current document is used.
+
+ @property {HTMLElement|String} [options.insertBefore] HTML element or id
+ string of an element before which all generated nodes should be
+ inserted. If not specified, Get will automatically determine the best
+ place to insert nodes for maximum compatibility.
+
+ @property {Function} [options.onEnd] Callback to execute after a transaction
+ is complete, regardless of whether it succeeded or failed.
+
+ @property {Function} [options.onFailure] Callback to execute after a
+ transaction fails, times out, or is aborted.
+
+ @property {Function} [options.onProgress] Callback to execute after each
+ individual request in a transaction either succeeds or fails.
+
+ @property {Function} [options.onSuccess] Callback to execute after a
+ transaction completes successfully with no errors. Note that in browsers
+ that don't support the `error` event on CSS `<link>` nodes, a failed CSS
+ request may still be reported as a success because in these browsers
+ it can be difficult or impossible to distinguish between success and
+ failure for CSS resources.
+
+ @property {Function} [options.onTimeout] Callback to execute after a
+ transaction times out.
+
+ @property {Number} [options.pollInterval=50] Polling interval (in
+ milliseconds) for detecting CSS load completion in browsers that don't
+ support the `load` event on `<link>` nodes. This isn't used for
+ JavaScript.
+
+ @property {Number} [options.purgethreshold=20] Number of nodes to insert
+ before triggering an automatic purge when `autopurge` is `true`.
+
+ @property {Number} [options.timeout] Number of milliseconds to wait before
+ aborting a transaction. When a timeout occurs, the `onTimeout` callback
+ is called, followed by `onFailure` and finally `onEnd`. By default,
+ there is no timeout.
+
+ @property {String} [options.type] Resource type ("css" or "js"). This option
+ is set automatically by the `css()` and `js()` functions and will be
+ ignored there, but may be useful when using the `load()` function. If
+ not specified, the type will be inferred from the URL, defaulting to
+ "js" if the URL doesn't contain a recognizable file extension.
+ **/
+ options: {
+ attributes: {
+ charset: 'utf-8'
+ },
+
+ purgethreshold: 20
+ },
+
+ // -- Protected Properties -------------------------------------------------
+
+ /**
+ Regex that matches a CSS URL. Used to guess the file type when it's not
+ specified.
+
+ @property REGEX_CSS
+ @type RegExp
+ @final
+ @protected
+ @static
+ @since 3.5.0
+ **/
+ REGEX_CSS: /\.css(?:[?;].*)?$/i,
+
+ /**
+ Regex that matches a JS URL. Used to guess the file type when it's not
+ specified.
+
+ @property REGEX_JS
+ @type RegExp
+ @final
+ @protected
+ @static
+ @since 3.5.0
+ **/
+ REGEX_JS : /\.js(?:[?;].*)?$/i,
+
+ /**
+ Contains information about the current environment, such as what script and
+ link injection features it supports.
+
+ This object is created and populated the first time the `_getEnv()` method
+ is called.
+
+ @property _env
+ @type Object
+ @protected
+ @static
+ @since 3.5.0
+ **/
+
+ /**
+ Mapping of document _yuid strings to <head> or <base> node references so we
+ don't have to look the node up each time we want to insert a request node.
+
+ @property _insertCache
+ @type Object
+ @protected
+ @static
+ @since 3.5.0
+ **/
+ _insertCache: {},
+
+ /**
+ Information about the currently pending transaction, if any.
+
+ This is actually an object with two properties: `callback`, containing the
+ optional callback passed to `css()`, `load()`, or `js()`; and `transaction`,
+ containing the actual transaction instance.
+
+ @property _pending
+ @type Object
+ @protected
+ @static
+ @since 3.5.0
+ **/
+ _pending: null,
+
+ /**
+ HTML nodes eligible to be purged next time autopurge is triggered.
+
+ @property _purgeNodes
+ @type HTMLElement[]
+ @protected
+ @static
+ @since 3.5.0
+ **/
+ _purgeNodes: [],
+
+ /**
+ Queued transactions and associated callbacks.
+
+ @property _queue
+ @type Object[]
+ @protected
+ @static
+ @since 3.5.0
+ **/
+ _queue: [],
+
+ // -- Public Methods -------------------------------------------------------
+
+ /**
+ Aborts the specified transaction.
+
+ This will cause the transaction's `onFailure` callback to be called and
+ will prevent any new script and link nodes from being added to the document,
+ but any resources that have already been requested will continue loading
+ (there's no safe way to prevent this, unfortunately).
+
+ *Note:* This method is deprecated as of 3.5.0, and will be removed in a
+ future version of YUI. Use the transaction-level `abort()` method instead.
+
+ @method abort
+ @param {Get.Transaction} transaction Transaction to abort.
+ @deprecated Use the `abort()` method on the transaction instead.
+ @static
+ **/
+ abort: function (transaction) {
+ var i, id, item, len, pending;
+
+
+ if (!transaction.abort) {
+ id = transaction;
+ pending = this._pending;
+ transaction = null;
+
+ if (pending && pending.transaction.id === id) {
+ transaction = pending.transaction;
+ this._pending = null;
+ } else {
+ for (i = 0, len = this._queue.length; i < len; ++i) {
+ item = this._queue[i].transaction;
+
+ if (item.id === id) {
+ transaction = item;
+ this._queue.splice(i, 1);
+ break;
+ }
+ }
+ }
+ }
+
+ transaction && transaction.abort();
+ },
+
+ /**
+ Loads one or more CSS files.
+
+ The _urls_ parameter may be provided as a URL string, a request object,
+ or an array of URL strings and/or request objects.
+
+ A request object is just an object that contains a `url` property and zero
+ or more options that should apply specifically to that request.
+ Request-specific options take priority over transaction-level options and
+ default options.
+
+ URLs may be relative or absolute, and do not have to have the same origin
+ as the current page.
+
+ The `options` parameter may be omitted completely and a callback passed in
+ its place, if desired.
+
+ @example
+
+ // Load a single CSS file and log a message on completion.
+ Y.Get.css('foo.css', function (err) {
+ if (err) {
+ } else {
+ }
+ });
+
+ // Load multiple CSS files and log a message when all have finished
+ // loading.
+ var urls = ['foo.css', 'http://example.com/bar.css', 'baz/quux.css'];
+
+ Y.Get.css(urls, function (err) {
+ if (err) {
+ } else {
+ }
+ });
+
+ // Specify transaction-level options, which will apply to all requests
+ // within the transaction.
+ Y.Get.css(urls, {
+ attributes: {'class': 'my-css'},
+ timeout : 5000
+ });
+
+ // Specify per-request options, which override transaction-level and
+ // default options.
+ Y.Get.css([
+ {url: 'foo.css', attributes: {id: 'foo'}},
+ {url: 'bar.css', attributes: {id: 'bar', charset: 'iso-8859-1'}}
+ ]);
+
+ @method css
+ @param {String|Object|Array} urls URL string, request object, or array
+ of URLs and/or request objects to load.
+ @param {Object} [options] Options for this transaction. See the
+ `Y.Get.options` property for a complete list of available options.
+ @param {Function} [callback] Callback function to be called on completion.
+ This is a general callback and will be called before any more granular
+ callbacks (`onSuccess`, `onFailure`, etc.) specified in the `options`
+ object.
+
+ @param {Array|null} callback.err Array of errors that occurred during
+ the transaction, or `null` on success.
+ @param {Get.Transaction} callback.transaction Transaction object.
+
+ @return {Get.Transaction} Transaction object.
+ @static
+ **/
+ css: function (urls, options, callback) {
+ return this._load('css', urls, options, callback);
+ },
+
+ /**
+ Loads one or more JavaScript resources.
+
+ The _urls_ parameter may be provided as a URL string, a request object,
+ or an array of URL strings and/or request objects.
+
+ A request object is just an object that contains a `url` property and zero
+ or more options that should apply specifically to that request.
+ Request-specific options take priority over transaction-level options and
+ default options.
+
+ URLs may be relative or absolute, and do not have to have the same origin
+ as the current page.
+
+ The `options` parameter may be omitted completely and a callback passed in
+ its place, if desired.
+
+ Scripts will be executed in the order they're specified unless the `async`
+ option is `true`, in which case they'll be loaded in parallel and executed
+ in whatever order they finish loading.
+
+ @example
+
+ // Load a single JS file and log a message on completion.
+ Y.Get.js('foo.js', function (err) {
+ if (err) {
+ } else {
+ }
+ });
+
+ // Load multiple JS files, execute them in order, and log a message when
+ // all have finished loading.
+ var urls = ['foo.js', 'http://example.com/bar.js', 'baz/quux.js'];
+
+ Y.Get.js(urls, function (err) {
+ if (err) {
+ } else {
+ }
+ });
+
+ // Specify transaction-level options, which will apply to all requests
+ // within the transaction.
+ Y.Get.js(urls, {
+ attributes: {'class': 'my-js'},
+ timeout : 5000
+ });
+
+ // Specify per-request options, which override transaction-level and
+ // default options.
+ Y.Get.js([
+ {url: 'foo.js', attributes: {id: 'foo'}},
+ {url: 'bar.js', attributes: {id: 'bar', charset: 'iso-8859-1'}}
+ ]);
+
+ @method js
+ @param {String|Object|Array} urls URL string, request object, or array
+ of URLs and/or request objects to load.
+ @param {Object} [options] Options for this transaction. See the
+ `Y.Get.options` property for a complete list of available options.
+ @param {Function} [callback] Callback function to be called on completion.
+ This is a general callback and will be called before any more granular
+ callbacks (`onSuccess`, `onFailure`, etc.) specified in the `options`
+ object.
+
+ @param {Array|null} callback.err Array of errors that occurred during
+ the transaction, or `null` on success.
+ @param {Get.Transaction} callback.transaction Transaction object.
+
+ @return {Get.Transaction} Transaction object.
+ @since 3.5.0
+ @static
+ **/
+ js: function (urls, options, callback) {
+ return this._load('js', urls, options, callback);
+ },
+
+ /**
+ Loads one or more CSS and/or JavaScript resources in the same transaction.
+
+ Use this method when you want to load both CSS and JavaScript in a single
+ transaction and be notified when all requested URLs have finished loading,
+ regardless of type.
+
+ Behavior and options are the same as for the `css()` and `js()` methods. If
+ a resource type isn't specified in per-request options or transaction-level
+ options, Get will guess the file type based on the URL's extension (`.css`
+ or `.js`, with or without a following query string). If the file type can't
+ be guessed from the URL, a warning will be logged and Get will assume the
+ URL is a JavaScript resource.
+
+ @example
+
+ // Load both CSS and JS files in a single transaction, and log a message
+ // when all files have finished loading.
+ Y.Get.load(['foo.css', 'bar.js', 'baz.css'], function (err) {
+ if (err) {
+ } else {
+ }
+ });
+
+ @method load
+ @param {String|Object|Array} urls URL string, request object, or array
+ of URLs and/or request objects to load.
+ @param {Object} [options] Options for this transaction. See the
+ `Y.Get.options` property for a complete list of available options.
+ @param {Function} [callback] Callback function to be called on completion.
+ This is a general callback and will be called before any more granular
+ callbacks (`onSuccess`, `onFailure`, etc.) specified in the `options`
+ object.
+
+ @param {Array|null} err Array of errors that occurred during the
+ transaction, or `null` on success.
+ @param {Get.Transaction} Transaction object.
+
+ @return {Get.Transaction} Transaction object.
+ @since 3.5.0
+ @static
+ **/
+ load: function (urls, options, callback) {
+ return this._load(null, urls, options, callback);
+ },
+
+ // -- Protected Methods ----------------------------------------------------
+
+ /**
+ Triggers an automatic purge if the purge threshold has been reached.
+
+ @method _autoPurge
+ @param {Number} threshold Purge threshold to use, in milliseconds.
+ @protected
+ @since 3.5.0
+ @static
+ **/
+ _autoPurge: function (threshold) {
+ if (threshold && this._purgeNodes.length >= threshold) {
+ this._purge(this._purgeNodes);
+ }
+ },
+
+ /**
+ Populates the `_env` property with information about the current
+ environment.
+
+ @method _getEnv
+ @return {Object} Environment information.
+ @protected
+ @since 3.5.0
+ @static
+ **/
+ _getEnv: function () {
+ var doc = Y.config.doc,
+ ua = Y.UA;
+
+ // Note: some of these checks require browser sniffs since it's not
+ // feasible to load test files on every pageview just to perform a
+ // feature test. I'm sorry if this makes you sad.
+ return (this._env = {
+ // True if this is a browser that supports disabling async mode on
+ // dynamically created script nodes. See
+ // https://developer.mozilla.org/En/HTML/Element/Script#Attributes
+ async: doc && doc.createElement('script').async === true,
+
+ // True if this browser fires an event when a dynamically injected
+ // link node fails to load. This is currently true for Firefox 9+
+ // and WebKit 535.24+.
+ cssFail: ua.gecko >= 9 || ua.compareVersions(ua.webkit, 535.24) >= 0,
+
+ // True if this browser fires an event when a dynamically injected
+ // link node finishes loading. This is currently true for IE, Opera,
+ // Firefox 9+, and WebKit 535.24+. Note that IE versions <9 fire the
+ // DOM 0 "onload" event, but not "load". All versions of IE fire
+ // "onload".
+ // davglass: Seems that Chrome on Android needs this to be false.
+ cssLoad: (
+ (!ua.gecko && !ua.webkit) || ua.gecko >= 9 ||
+ ua.compareVersions(ua.webkit, 535.24) >= 0
+ ) && !(ua.chrome && ua.chrome <= 18),
+
+ // True if this browser preserves script execution order while
+ // loading scripts in parallel as long as the script node's `async`
+ // attribute is set to false to explicitly disable async execution.
+ preservesScriptOrder: !!(ua.gecko || ua.opera)
+ });
+ },
+
+ _getTransaction: function (urls, options) {
+ var requests = [],
+ i, len, req, url;
+
+ if (!Lang.isArray(urls)) {
+ urls = [urls];
+ }
+
+ options = Y.merge(this.options, options);
+
+ // Clone the attributes object so we don't end up modifying it by ref.
+ options.attributes = Y.merge(this.options.attributes,
+ options.attributes);
+
+ for (i = 0, len = urls.length; i < len; ++i) {
+ url = urls[i];
+ req = {attributes: {}};
+
+ // If `url` is a string, we create a URL object for it, then mix in
+ // global options and request-specific options. If it's an object
+ // with a "url" property, we assume it's a request object containing
+ // URL-specific options.
+ if (typeof url === 'string') {
+ req.url = url;
+ } else if (url.url) {
+ // URL-specific options override both global defaults and
+ // request-specific options.
+ Y.mix(req, url, false, null, 0, true);
+ url = url.url; // Make url a string so we can use it later.
+ } else {
+ continue;
+ }
+
+ Y.mix(req, options, false, null, 0, true);
+
+ // If we didn't get an explicit type for this URL either in the
+ // request options or the URL-specific options, try to determine
+ // one from the file extension.
+ if (!req.type) {
+ if (this.REGEX_CSS.test(url)) {
+ req.type = 'css';
+ } else {
+ if (!this.REGEX_JS.test(url)) {
+ }
+
+ req.type = 'js';
+ }
+ }
+
+ // Mix in type-specific default options, but don't overwrite any
+ // options that have already been set.
+ Y.mix(req, req.type === 'js' ? this.jsOptions : this.cssOptions,
+ false, null, 0, true);
+
+ // Give the node an id attribute if it doesn't already have one.
+ req.attributes.id || (req.attributes.id = Y.guid());
+
+ // Backcompat for <3.5.0 behavior.
+ if (req.win) {
+ req.doc = req.win.document;
+ } else {
+ req.win = req.doc.defaultView || req.doc.parentWindow;
+ }
+
+ if (req.charset) {
+ req.attributes.charset = req.charset;
+ }
+
+ requests.push(req);
+ }
+
+ return new Transaction(requests, options);
+ },
+
+ _load: function (type, urls, options, callback) {
+ var transaction;
+
+ // Allow callback as third param.
+ if (typeof options === 'function') {
+ callback = options;
+ options = {};
+ }
+
+ options || (options = {});
+ options.type = type;
+
+ if (!this._env) {
+ this._getEnv();
+ }
+
+ transaction = this._getTransaction(urls, options);
+
+ this._queue.push({
+ callback : callback,
+ transaction: transaction
+ });
+
+ this._next();
+
+ return transaction;
+ },
+
+ _next: function () {
+ var item;
+
+ if (this._pending) {
+ return;
+ }
+
+ item = this._queue.shift();
+
+ if (item) {
+ this._pending = item;
+
+ item.transaction.execute(function () {
+ item.callback && item.callback.apply(this, arguments);
+
+ Get._pending = null;
+ Get._next();
+ });
+ }
+ },
+
+ _purge: function (nodes) {
+ var purgeNodes = this._purgeNodes,
+ isTransaction = nodes !== purgeNodes,
+ index, node;
+
+ while (node = nodes.pop()) { // assignment
+ // Don't purge nodes that haven't finished loading (or errored out),
+ // since this can hang the transaction.
+ if (!node._yuiget_finished) {
+ continue;
+ }
+
+ node.parentNode && node.parentNode.removeChild(node);
+
+ // If this is a transaction-level purge and this node also exists in
+ // the Get-level _purgeNodes array, we need to remove it from
+ // _purgeNodes to avoid creating a memory leak. The indexOf lookup
+ // sucks, but until we get WeakMaps, this is the least troublesome
+ // way to do this (we can't just hold onto node ids because they may
+ // not be in the same document).
+ if (isTransaction) {
+ index = Y.Array.indexOf(purgeNodes, node);
+
+ if (index > -1) {
+ purgeNodes.splice(index, 1);
+ }
+ }
+ }
+ }
+};
+
+/**
+Alias for `js()`.
+
+ at method script
+ at static
+**/
+Get.script = Get.js;
+
+/**
+Represents a Get transaction, which may contain requests for one or more JS or
+CSS files.
+
+This class should not be instantiated manually. Instances will be created and
+returned as needed by Y.Get's `css()`, `js()`, and `load()` methods.
+
+ at class Get.Transaction
+ at constructor
+ at since 3.5.0
+**/
+Get.Transaction = Transaction = function (requests, options) {
+ var self = this;
+
+ self.id = Transaction._lastId += 1;
+ self.data = options.data;
+ self.errors = [];
+ self.nodes = [];
+ self.options = options;
+ self.requests = requests;
+
+ self._callbacks = []; // callbacks to call after execution finishes
+ self._queue = [];
+ self._waiting = 0;
+
+ // Deprecated pre-3.5.0 properties.
+ self.tId = self.id; // Use `id` instead.
+ self.win = options.win || Y.config.win;
+};
+
+/**
+Arbitrary data object associated with this transaction.
+
+This object comes from the options passed to `Get.css()`, `Get.js()`, or
+`Get.load()`, and will be `undefined` if no data object was specified.
+
+ at property {Object} data
+**/
+
+/**
+Array of errors that have occurred during this transaction, if any.
+
+ at since 3.5.0
+ at property {Object[]} errors
+ at property {String} errors.error Error message.
+ at property {Object} errors.request Request object related to the error.
+**/
+
+/**
+Numeric id for this transaction, unique among all transactions within the same
+YUI sandbox in the current pageview.
+
+ at property {Number} id
+ at since 3.5.0
+**/
+
+/**
+HTMLElement nodes (native ones, not YUI Node instances) that have been inserted
+during the current transaction.
+
+ at property {HTMLElement[]} nodes
+**/
+
+/**
+Options associated with this transaction.
+
+See `Get.options` for the full list of available options.
+
+ at property {Object} options
+ at since 3.5.0
+**/
+
+/**
+Request objects contained in this transaction. Each request object represents
+one CSS or JS URL that will be (or has been) requested and loaded into the page.
+
+ at property {Object} requests
+ at since 3.5.0
+**/
+
+/**
+Id of the most recent transaction.
+
+ at property _lastId
+ at type Number
+ at protected
+ at static
+**/
+Transaction._lastId = 0;
+
+Transaction.prototype = {
+ // -- Public Properties ----------------------------------------------------
+
+ /**
+ Current state of this transaction. One of "new", "executing", or "done".
+
+ @property _state
+ @type String
+ @protected
+ **/
+ _state: 'new', // "new", "executing", or "done"
+
+ // -- Public Methods -------------------------------------------------------
+
+ /**
+ Aborts this transaction.
+
+ This will cause the transaction's `onFailure` callback to be called and
+ will prevent any new script and link nodes from being added to the document,
+ but any resources that have already been requested will continue loading
+ (there's no safe way to prevent this, unfortunately).
+
+ @method abort
+ @param {String} [msg="Aborted."] Optional message to use in the `errors`
+ array describing why the transaction was aborted.
+ **/
+ abort: function (msg) {
+ this._pending = null;
+ this._pendingCSS = null;
+ this._pollTimer = clearTimeout(this._pollTimer);
+ this._queue = [];
+ this._waiting = 0;
+
+ this.errors.push({error: msg || 'Aborted'});
+ this._finish();
+ },
+
+ /**
+ Begins execting the transaction.
+
+ There's usually no reason to call this manually, since Get will call it
+ automatically when other pending transactions have finished. If you really
+ want to execute your transaction before Get does, you can, but be aware that
+ this transaction's scripts may end up executing before the scripts in other
+ pending transactions.
+
+ If the transaction is already executing, the specified callback (if any)
+ will be queued and called after execution finishes. If the transaction has
+ already finished, the callback will be called immediately (the transaction
+ will not be executed again).
+
+ @method execute
+ @param {Function} callback Callback function to execute after all requests
+ in the transaction are complete, or after the transaction is aborted.
+ **/
+ execute: function (callback) {
+ var self = this,
+ requests = self.requests,
+ state = self._state,
+ i, len, queue, req;
+
+ if (state === 'done') {
+ callback && callback(self.errors.length ? self.errors : null, self);
+ return;
+ } else {
+ callback && self._callbacks.push(callback);
+
+ if (state === 'executing') {
+ return;
+ }
+ }
+
+ self._state = 'executing';
+ self._queue = queue = [];
+
+ if (self.options.timeout) {
+ self._timeout = setTimeout(function () {
+ self.abort('Timeout');
+ }, self.options.timeout);
+ }
+
+ for (i = 0, len = requests.length; i < len; ++i) {
+ req = self.requests[i];
+
+ if (req.async || req.type === 'css') {
+ // No need to queue CSS or fully async JS.
+ self._insert(req);
+ } else {
+ queue.push(req);
+ }
+ }
+
+ self._next();
+ },
+
+ /**
+ Manually purges any `<script>` or `<link>` nodes this transaction has
+ created.
+
+ Be careful when purging a transaction that contains CSS requests, since
+ removing `<link>` nodes will also remove any styles they applied.
+
+ @method purge
+ **/
+ purge: function () {
+ Get._purge(this.nodes);
+ },
+
+ // -- Protected Methods ----------------------------------------------------
+ _createNode: function (name, attrs, doc) {
+ var node = doc.createElement(name),
+ attr, testEl;
+
+ if (!CUSTOM_ATTRS) {
+ // IE6 and IE7 expect property names rather than attribute names for
+ // certain attributes. Rather than sniffing, we do a quick feature
+ // test the first time _createNode() runs to determine whether we
+ // need to provide a workaround.
+ testEl = doc.createElement('div');
+ testEl.setAttribute('class', 'a');
+
+ CUSTOM_ATTRS = testEl.className === 'a' ? {} : {
+ 'for' : 'htmlFor',
+ 'class': 'className'
+ };
+ }
+
+ for (attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ node.setAttribute(CUSTOM_ATTRS[attr] || attr, attrs[attr]);
+ }
+ }
+
+ return node;
+ },
+
+ _finish: function () {
+ var errors = this.errors.length ? this.errors : null,
+ options = this.options,
+ thisObj = options.context || this,
+ data, i, len;
+
+ if (this._state === 'done') {
+ return;
+ }
+
+ this._state = 'done';
+
+ for (i = 0, len = this._callbacks.length; i < len; ++i) {
+ this._callbacks[i].call(thisObj, errors, this);
+ }
+
+ data = this._getEventData();
+
+ if (errors) {
+ if (options.onTimeout && errors[errors.length - 1].error === 'Timeout') {
+ options.onTimeout.call(thisObj, data);
+ }
+
+ if (options.onFailure) {
+ options.onFailure.call(thisObj, data);
+ }
+ } else if (options.onSuccess) {
+ options.onSuccess.call(thisObj, data);
+ }
+
+ if (options.onEnd) {
+ options.onEnd.call(thisObj, data);
+ }
+ },
+
+ _getEventData: function (req) {
+ if (req) {
+ // This merge is necessary for backcompat. I hate it.
+ return Y.merge(this, {
+ abort : this.abort, // have to copy these because the prototype isn't preserved
+ purge : this.purge,
+ request: req,
+ url : req.url,
+ win : req.win
+ });
+ } else {
+ return this;
+ }
+ },
+
+ _getInsertBefore: function (req) {
+ var doc = req.doc,
+ el = req.insertBefore,
+ cache, cachedNode, docStamp;
+
+ if (el) {
+ return typeof el === 'string' ? doc.getElementById(el) : el;
+ }
+
+ cache = Get._insertCache;
+ docStamp = Y.stamp(doc);
+
+ if ((el = cache[docStamp])) { // assignment
+ return el;
+ }
+
+ // Inserting before a <base> tag apparently works around an IE bug
+ // (according to a comment from pre-3.5.0 Y.Get), but I'm not sure what
+ // bug that is, exactly. Better safe than sorry?
+ if ((el = doc.getElementsByTagName('base')[0])) { // assignment
+ return (cache[docStamp] = el);
+ }
+
+ // Look for a <head> element.
+ el = doc.head || doc.getElementsByTagName('head')[0];
+
+ if (el) {
+ // Create a marker node at the end of <head> to use as an insertion
+ // point. Inserting before this node will ensure that all our CSS
+ // gets inserted in the correct order, to maintain style precedence.
+ el.appendChild(doc.createTextNode(''));
+ return (cache[docStamp] = el.lastChild);
+ }
+
+ // If all else fails, just insert before the first script node on the
+ // page, which is virtually guaranteed to exist.
+ return (cache[docStamp] = doc.getElementsByTagName('script')[0]);
+ },
+
+ _insert: function (req) {
+ var env = Get._env,
+ insertBefore = this._getInsertBefore(req),
+ isScript = req.type === 'js',
+ node = req.node,
+ self = this,
+ ua = Y.UA,
+ cssTimeout, nodeType;
+
+ if (!node) {
+ if (isScript) {
+ nodeType = 'script';
+ } else if (!env.cssLoad && ua.gecko) {
+ nodeType = 'style';
+ } else {
+ nodeType = 'link';
+ }
+
+ node = req.node = this._createNode(nodeType, req.attributes,
+ req.doc);
+ }
+
+ function onError() {
+ self._progress('Failed to load ' + req.url, req);
+ }
+
+ function onLoad() {
+ if (cssTimeout) {
+ clearTimeout(cssTimeout);
+ }
+
+ self._progress(null, req);
+ }
+
+
+ // Deal with script asynchronicity.
+ if (isScript) {
+ node.setAttribute('src', req.url);
+
+ if (req.async) {
+ // Explicitly indicate that we want the browser to execute this
+ // script asynchronously. This is necessary for older browsers
+ // like Firefox <4.
+ node.async = true;
+ } else {
+ if (env.async) {
+ // This browser treats injected scripts as async by default
+ // (standard HTML5 behavior) but asynchronous loading isn't
+ // desired, so tell the browser not to mark this script as
+ // async.
+ node.async = false;
+ }
+
+ // If this browser doesn't preserve script execution order based
+ // on insertion order, we'll need to avoid inserting other
+ // scripts until this one finishes loading.
+ if (!env.preservesScriptOrder) {
+ this._pending = req;
+ }
+ }
+ } else {
+ if (!env.cssLoad && ua.gecko) {
+ // In Firefox <9, we can import the requested URL into a <style>
+ // node and poll for the existence of node.sheet.cssRules. This
+ // gives us a reliable way to determine CSS load completion that
+ // also works for cross-domain stylesheets.
+ //
+ // Props to Zach Leatherman for calling my attention to this
+ // technique.
+ node.innerHTML = (req.attributes.charset ?
+ '@charset "' + req.attributes.charset + '";' : '') +
+ '@import "' + req.url + '";';
+ } else {
+ node.setAttribute('href', req.url);
+ }
+ }
+
+ // Inject the node.
+ if (isScript && ua.ie && (ua.ie < 9 || (document.documentMode && document.documentMode < 9))) {
+ // Script on IE < 9, and IE 9+ when in IE 8 or older modes, including quirks mode.
+ node.onreadystatechange = function () {
+ if (/loaded|complete/.test(node.readyState)) {
+ node.onreadystatechange = null;
+ onLoad();
+ }
+ };
+ } else if (!isScript && !env.cssLoad) {
+ // CSS on Firefox <9 or WebKit.
+ this._poll(req);
+ } else {
+ // Script or CSS on everything else. Using DOM 0 events because that
+ // evens the playing field with older IEs.
+ node.onerror = onError;
+ node.onload = onLoad;
+
+ // If this browser doesn't fire an event when CSS fails to load,
+ // fail after a timeout to avoid blocking the transaction queue.
+ if (!env.cssFail && !isScript) {
+ cssTimeout = setTimeout(onError, req.timeout || 3000);
+ }
+ }
+
+ this._waiting += 1;
+
+ this.nodes.push(node);
+ insertBefore.parentNode.insertBefore(node, insertBefore);
+ },
+
+ _next: function () {
+ if (this._pending) {
+ return;
+ }
+
+ // If there are requests in the queue, insert the next queued request.
+ // Otherwise, if we're waiting on already-inserted requests to finish,
+ // wait longer. If there are no queued requests and we're not waiting
+ // for anything to load, then we're done!
+ if (this._queue.length) {
+ this._insert(this._queue.shift());
+ } else if (!this._waiting) {
+ this._finish();
+ }
+ },
+
+ _poll: function (newReq) {
+ var self = this,
+ pendingCSS = self._pendingCSS,
+ isWebKit = Y.UA.webkit,
+ i, hasRules, j, nodeHref, req, sheets;
+
+ if (newReq) {
+ pendingCSS || (pendingCSS = self._pendingCSS = []);
+ pendingCSS.push(newReq);
+
+ if (self._pollTimer) {
+ // A poll timeout is already pending, so no need to create a
+ // new one.
+ return;
+ }
+ }
+
+ self._pollTimer = null;
+
+ // Note: in both the WebKit and Gecko hacks below, a CSS URL that 404s
+ // will still be treated as a success. There's no good workaround for
+ // this.
+
+ for (i = 0; i < pendingCSS.length; ++i) {
+ req = pendingCSS[i];
+
+ if (isWebKit) {
+ // Look for a stylesheet matching the pending URL.
+ sheets = req.doc.styleSheets;
+ j = sheets.length;
+ nodeHref = req.node.href;
+
+ while (--j >= 0) {
+ if (sheets[j].href === nodeHref) {
+ pendingCSS.splice(i, 1);
+ i -= 1;
+ self._progress(null, req);
+ break;
+ }
+ }
+ } else {
+ // Many thanks to Zach Leatherman for calling my attention to
+ // the @import-based cross-domain technique used here, and to
+ // Oleg Slobodskoi for an earlier same-domain implementation.
+ //
+ // See Zach's blog for more details:
+ // http://www.zachleat.com/web/2010/07/29/load-css-dynamically/
+ try {
+ // We don't really need to store this value since we never
+ // use it again, but if we don't store it, Closure Compiler
+ // assumes the code is useless and removes it.
+ hasRules = !!req.node.sheet.cssRules;
+
+ // If we get here, the stylesheet has loaded.
+ pendingCSS.splice(i, 1);
+ i -= 1;
+ self._progress(null, req);
+ } catch (ex) {
+ // An exception means the stylesheet is still loading.
+ }
+ }
+ }
+
+ if (pendingCSS.length) {
+ self._pollTimer = setTimeout(function () {
+ self._poll.call(self);
+ }, self.options.pollInterval);
+ }
+ },
+
+ _progress: function (err, req) {
+ var options = this.options;
+
+ if (err) {
+ req.error = err;
+
+ this.errors.push({
+ error : err,
+ request: req
+ });
+
+ }
+
+ req.node._yuiget_finished = req.finished = true;
+
+ if (options.onProgress) {
+ options.onProgress.call(options.context || this,
+ this._getEventData(req));
+ }
+
+ if (req.autopurge) {
+ // Pre-3.5.0 Get always excludes the most recent node from an
+ // autopurge. I find this odd, but I'm keeping that behavior for
+ // the sake of backcompat.
+ Get._autoPurge(this.options.purgethreshold);
+ Get._purgeNodes.push(req.node);
+ }
+
+ if (this._pending === req) {
+ this._pending = null;
+ }
+
+ this._waiting -= 1;
+ this._next();
+ }
+};
+
+
+}, '@VERSION@' ,{requires:['yui-base']});
+YUI.add('features', function(Y) {
+
+var feature_tests = {};
+
+/**
+Contains the core of YUI's feature test architecture.
+ at module features
+*/
+
+/**
+* Feature detection
+* @class Features
+* @static
+*/
+
+Y.mix(Y.namespace('Features'), {
+
+ /**
+ * Object hash of all registered feature tests
+ * @property tests
+ * @type Object
+ */
+ tests: feature_tests,
+
+ /**
+ * Add a test to the system
+ *
+ * ```
+ * Y.Features.add("load", "1", {});
+ * ```
+ *
+ * @method add
+ * @param {String} cat The category, right now only 'load' is supported
+ * @param {String} name The number sequence of the test, how it's reported in the URL or config: 1, 2, 3
+ * @param {Object} o Object containing test properties
+ * @param {String} o.name The name of the test
+ * @param {Function} o.test The test function to execute, the only argument to the function is the `Y` instance
+ * @param {String} o.trigger The module that triggers this test.
+ */
+ add: function(cat, name, o) {
+ feature_tests[cat] = feature_tests[cat] || {};
+ feature_tests[cat][name] = o;
+ },
+ /**
+ * Execute all tests of a given category and return the serialized results
+ *
+ * ```
+ * caps=1:1;2:1;3:0
+ * ```
+ * @method all
+ * @param {String} cat The category to execute
+ * @param {Array} args The arguments to pass to the test function
+ * @return {String} A semi-colon separated string of tests and their success/failure: 1:1;2:1;3:0
+ */
+ all: function(cat, args) {
+ var cat_o = feature_tests[cat],
+ // results = {};
+ result = [];
+ if (cat_o) {
+ Y.Object.each(cat_o, function(v, k) {
+ result.push(k + ':' + (Y.Features.test(cat, k, args) ? 1 : 0));
+ });
+ }
+
+ return (result.length) ? result.join(';') : '';
+ },
+ /**
+ * Run a sepecific test and return a Boolean response.
+ *
+ * ```
+ * Y.Features.test("load", "1");
+ * ```
+ *
+ * @method test
+ * @param {String} cat The category of the test to run
+ * @param {String} name The name of the test to run
+ * @param {Array} args The arguments to pass to the test function
+ * @return {Boolean} True or false if the test passed/failed.
+ */
+ test: function(cat, name, args) {
+ args = args || [];
+ var result, ua, test,
+ cat_o = feature_tests[cat],
+ feature = cat_o && cat_o[name];
+
+ if (!feature) {
+ } else {
+
+ result = feature.result;
+
+ if (Y.Lang.isUndefined(result)) {
+
+ ua = feature.ua;
+ if (ua) {
+ result = (Y.UA[ua]);
+ }
+
+ test = feature.test;
+ if (test && ((!ua) || result)) {
+ result = test.apply(Y, args);
+ }
+
+ feature.result = result;
+ }
+ }
+
+ return result;
+ }
+});
+
+// Y.Features.add("load", "1", {});
+// Y.Features.test("load", "1");
+// caps=1:1;2:0;3:1;
+
+/* This file is auto-generated by src/loader/scripts/meta_join.js */
+var add = Y.Features.add;
+// app-transitions-native
+add('load', '0', {
+ "name": "app-transitions-native",
+ "test": function (Y) {
+ var doc = Y.config.doc,
+ node = doc ? doc.documentElement : null;
+
+ if (node && node.style) {
+ return ('MozTransition' in node.style || 'WebkitTransition' in node.style);
+ }
+
+ return false;
+},
+ "trigger": "app-transitions"
+});
+// autocomplete-list-keys
+add('load', '1', {
+ "name": "autocomplete-list-keys",
+ "test": function (Y) {
+ // Only add keyboard support to autocomplete-list if this doesn't appear to
+ // be an iOS or Android-based mobile device.
+ //
+ // There's currently no feasible way to actually detect whether a device has
+ // a hardware keyboard, so this sniff will have to do. It can easily be
+ // overridden by manually loading the autocomplete-list-keys module.
+ //
+ // Worth noting: even though iOS supports bluetooth keyboards, Mobile Safari
+ // doesn't fire the keyboard events used by AutoCompleteList, so there's
+ // no point loading the -keys module even when a bluetooth keyboard may be
+ // available.
+ return !(Y.UA.ios || Y.UA.android);
+},
+ "trigger": "autocomplete-list"
+});
+// dd-gestures
+add('load', '2', {
+ "name": "dd-gestures",
+ "test": function(Y) {
+ return ((Y.config.win && ("ontouchstart" in Y.config.win)) && !(Y.UA.chrome && Y.UA.chrome < 6));
+},
+ "trigger": "dd-drag"
+});
+// dom-style-ie
+add('load', '3', {
+ "name": "dom-style-ie",
+ "test": function (Y) {
+
+ var testFeature = Y.Features.test,
+ addFeature = Y.Features.add,
+ WINDOW = Y.config.win,
+ DOCUMENT = Y.config.doc,
+ DOCUMENT_ELEMENT = 'documentElement',
+ ret = false;
+
+ addFeature('style', 'computedStyle', {
+ test: function() {
+ return WINDOW && 'getComputedStyle' in WINDOW;
+ }
+ });
+
+ addFeature('style', 'opacity', {
+ test: function() {
+ return DOCUMENT && 'opacity' in DOCUMENT[DOCUMENT_ELEMENT].style;
+ }
+ });
+
+ ret = (!testFeature('style', 'opacity') &&
+ !testFeature('style', 'computedStyle'));
+
+ return ret;
+},
+ "trigger": "dom-style"
+});
+// editor-para-ie
+add('load', '4', {
+ "name": "editor-para-ie",
+ "trigger": "editor-para",
+ "ua": "ie",
+ "when": "instead"
+});
+// event-base-ie
+add('load', '5', {
+ "name": "event-base-ie",
+ "test": function(Y) {
+ var imp = Y.config.doc && Y.config.doc.implementation;
+ return (imp && (!imp.hasFeature('Events', '2.0')));
+},
+ "trigger": "node-base"
+});
+// graphics-canvas
+add('load', '6', {
+ "name": "graphics-canvas",
+ "test": function(Y) {
+ var DOCUMENT = Y.config.doc,
+ useCanvas = Y.config.defaultGraphicEngine && Y.config.defaultGraphicEngine == "canvas",
+ canvas = DOCUMENT && DOCUMENT.createElement("canvas"),
+ svg = (DOCUMENT && DOCUMENT.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"));
+ return (!svg || useCanvas) && (canvas && canvas.getContext && canvas.getContext("2d"));
+},
+ "trigger": "graphics"
+});
+// graphics-canvas-default
+add('load', '7', {
+ "name": "graphics-canvas-default",
+ "test": function(Y) {
+ var DOCUMENT = Y.config.doc,
+ useCanvas = Y.config.defaultGraphicEngine && Y.config.defaultGraphicEngine == "canvas",
+ canvas = DOCUMENT && DOCUMENT.createElement("canvas"),
+ svg = (DOCUMENT && DOCUMENT.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"));
+ return (!svg || useCanvas) && (canvas && canvas.getContext && canvas.getContext("2d"));
+},
+ "trigger": "graphics"
+});
+// graphics-svg
+add('load', '8', {
+ "name": "graphics-svg",
+ "test": function(Y) {
+ var DOCUMENT = Y.config.doc,
+ useSVG = !Y.config.defaultGraphicEngine || Y.config.defaultGraphicEngine != "canvas",
+ canvas = DOCUMENT && DOCUMENT.createElement("canvas"),
+ svg = (DOCUMENT && DOCUMENT.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"));
+
+ return svg && (useSVG || !canvas);
+},
+ "trigger": "graphics"
+});
+// graphics-svg-default
+add('load', '9', {
+ "name": "graphics-svg-default",
+ "test": function(Y) {
+ var DOCUMENT = Y.config.doc,
+ useSVG = !Y.config.defaultGraphicEngine || Y.config.defaultGraphicEngine != "canvas",
+ canvas = DOCUMENT && DOCUMENT.createElement("canvas"),
+ svg = (DOCUMENT && DOCUMENT.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"));
+
+ return svg && (useSVG || !canvas);
+},
+ "trigger": "graphics"
+});
+// graphics-vml
+add('load', '10', {
+ "name": "graphics-vml",
+ "test": function(Y) {
+ var DOCUMENT = Y.config.doc,
+ canvas = DOCUMENT && DOCUMENT.createElement("canvas");
+ return (DOCUMENT && !DOCUMENT.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") && (!canvas || !canvas.getContext || !canvas.getContext("2d")));
+},
+ "trigger": "graphics"
+});
+// graphics-vml-default
+add('load', '11', {
+ "name": "graphics-vml-default",
+ "test": function(Y) {
+ var DOCUMENT = Y.config.doc,
+ canvas = DOCUMENT && DOCUMENT.createElement("canvas");
+ return (DOCUMENT && !DOCUMENT.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") && (!canvas || !canvas.getContext || !canvas.getContext("2d")));
+},
+ "trigger": "graphics"
+});
+// history-hash-ie
+add('load', '12', {
+ "name": "history-hash-ie",
+ "test": function (Y) {
+ var docMode = Y.config.doc && Y.config.doc.documentMode;
+
+ return Y.UA.ie && (!('onhashchange' in Y.config.win) ||
+ !docMode || docMode < 8);
+},
+ "trigger": "history-hash"
+});
+// io-nodejs
+add('load', '13', {
+ "name": "io-nodejs",
+ "trigger": "io-base",
+ "ua": "nodejs"
+});
+// scrollview-base-ie
+add('load', '14', {
+ "name": "scrollview-base-ie",
+ "trigger": "scrollview-base",
+ "ua": "ie"
+});
+// selector-css2
+add('load', '15', {
+ "name": "selector-css2",
+ "test": function (Y) {
+ var DOCUMENT = Y.config.doc,
+ ret = DOCUMENT && !('querySelectorAll' in DOCUMENT);
+
+ return ret;
+},
+ "trigger": "selector"
+});
+// transition-timer
+add('load', '16', {
+ "name": "transition-timer",
+ "test": function (Y) {
+ var DOCUMENT = Y.config.doc,
+ node = (DOCUMENT) ? DOCUMENT.documentElement: null,
+ ret = true;
+
+ if (node && node.style) {
+ ret = !('MozTransition' in node.style || 'WebkitTransition' in node.style);
+ }
+
+ return ret;
+},
+ "trigger": "transition"
+});
+// widget-base-ie
+add('load', '17', {
+ "name": "widget-base-ie",
+ "trigger": "widget-base",
+ "ua": "ie"
+});
+
+
+}, '@VERSION@' ,{requires:['yui-base']});
+YUI.add('intl-base', function(Y) {
+
+/**
+ * The Intl utility provides a central location for managing sets of
+ * localized resources (strings and formatting patterns).
+ *
+ * @class Intl
+ * @uses EventTarget
+ * @static
+ */
+
+var SPLIT_REGEX = /[, ]/;
+
+Y.mix(Y.namespace('Intl'), {
+
+ /**
+ * Returns the language among those available that
+ * best matches the preferred language list, using the Lookup
+ * algorithm of BCP 47.
+ * If none of the available languages meets the user's preferences,
+ * then "" is returned.
+ * Extended language ranges are not supported.
+ *
+ * @method lookupBestLang
+ * @param {String[] | String} preferredLanguages The list of preferred
+ * languages in descending preference order, represented as BCP 47
+ * language tags. A string array or a comma-separated list.
+ * @param {String[]} availableLanguages The list of languages
+ * that the application supports, represented as BCP 47 language
+ * tags.
+ *
+ * @return {String} The available language that best matches the
+ * preferred language list, or "".
+ * @since 3.1.0
+ */
+ lookupBestLang: function(preferredLanguages, availableLanguages) {
+
+ var i, language, result, index;
+
+ // check whether the list of available languages contains language;
+ // if so return it
+ function scan(language) {
+ var i;
+ for (i = 0; i < availableLanguages.length; i += 1) {
+ if (language.toLowerCase() ===
+ availableLanguages[i].toLowerCase()) {
+ return availableLanguages[i];
+ }
+ }
+ }
+
+ if (Y.Lang.isString(preferredLanguages)) {
+ preferredLanguages = preferredLanguages.split(SPLIT_REGEX);
+ }
+
+ for (i = 0; i < preferredLanguages.length; i += 1) {
+ language = preferredLanguages[i];
+ if (!language || language === '*') {
+ continue;
+ }
+ // check the fallback sequence for one language
+ while (language.length > 0) {
+ result = scan(language);
+ if (result) {
+ return result;
+ } else {
+ index = language.lastIndexOf('-');
+ if (index >= 0) {
+ language = language.substring(0, index);
+ // one-character subtags get cut along with the
+ // following subtag
+ if (index >= 2 && language.charAt(index - 2) === '-') {
+ language = language.substring(0, index - 2);
+ }
+ } else {
+ // nothing available for this language
+ break;
+ }
+ }
+ }
+ }
+
+ return '';
+ }
+});
+
+
+}, '@VERSION@' ,{requires:['yui-base']});
+YUI.add('yui-log', function(Y) {
+
+/**
+ * Provides console log capability and exposes a custom event for
+ * console implementations. This module is a `core` YUI module, <a href="../classes/YUI.html#method_log">it's documentation is located under the YUI class</a>.
+ *
+ * @module yui
+ * @submodule yui-log
+ */
+
+var INSTANCE = Y,
+ LOGEVENT = 'yui:log',
+ UNDEFINED = 'undefined',
+ LEVELS = { debug: 1,
+ info: 1,
+ warn: 1,
+ error: 1 };
+
+/**
+ * If the 'debug' config is true, a 'yui:log' event will be
+ * dispatched, which the Console widget and anything else
+ * can consume. If the 'useBrowserConsole' config is true, it will
+ * write to the browser console if available. YUI-specific log
+ * messages will only be present in the -debug versions of the
+ * JS files. The build system is supposed to remove log statements
+ * from the raw and minified versions of the files.
+ *
+ * @method log
+ * @for YUI
+ * @param {String} msg The message to log.
+ * @param {String} cat The log category for the message. Default
+ * categories are "info", "warn", "error", time".
+ * Custom categories can be used as well. (opt).
+ * @param {String} src The source of the the message (opt).
+ * @param {boolean} silent If true, the log event won't fire.
+ * @return {YUI} YUI instance.
+ */
+INSTANCE.log = function(msg, cat, src, silent) {
+ var bail, excl, incl, m, f,
+ Y = INSTANCE,
+ c = Y.config,
+ publisher = (Y.fire) ? Y : YUI.Env.globalEvents;
+ // suppress log message if the config is off or the event stack
+ // or the event call stack contains a consumer of the yui:log event
+ if (c.debug) {
+ // apply source filters
+ src = src || "";
+ if (typeof src !== "undefined") {
+ excl = c.logExclude;
+ incl = c.logInclude;
+ if (incl && !(src in incl)) {
+ bail = 1;
+ } else if (incl && (src in incl)) {
+ bail = !incl[src];
+ } else if (excl && (src in excl)) {
+ bail = excl[src];
+ }
+ }
+ if (!bail) {
+ if (c.useBrowserConsole) {
+ m = (src) ? src + ': ' + msg : msg;
+ if (Y.Lang.isFunction(c.logFn)) {
+ c.logFn.call(Y, msg, cat, src);
+ } else if (typeof console != UNDEFINED && console.log) {
+ f = (cat && console[cat] && (cat in LEVELS)) ? cat : 'log';
+ console[f](m);
+ } else if (typeof opera != UNDEFINED) {
+ opera.postError(m);
+ }
+ }
+
+ if (publisher && !silent) {
+
+ if (publisher == Y && (!publisher.getEvent(LOGEVENT))) {
+ publisher.publish(LOGEVENT, {
+ broadcast: 2
+ });
+ }
+
+ publisher.fire(LOGEVENT, {
+ msg: msg,
+ cat: cat,
+ src: src
+ });
+ }
+ }
+ }
+
+ return Y;
+};
+
+/**
+ * Write a system message. This message will be preserved in the
+ * minified and raw versions of the YUI files, unlike log statements.
+ * @method message
+ * @for YUI
+ * @param {String} msg The message to log.
+ * @param {String} cat The log category for the message. Default
+ * categories are "info", "warn", "error", time".
+ * Custom categories can be used as well. (opt).
+ * @param {String} src The source of the the message (opt).
+ * @param {boolean} silent If true, the log event won't fire.
+ * @return {YUI} YUI instance.
+ */
+INSTANCE.message = function() {
+ return INSTANCE.log.apply(INSTANCE, arguments);
+};
+
+
+}, '@VERSION@' ,{requires:['yui-base']});
+YUI.add('yui-later', function(Y) {
+
+/**
+ * Provides a setTimeout/setInterval wrapper. This module is a `core` YUI module, <a href="../classes/YUI.html#method_later">it's documentation is located under the YUI class</a>.
+ *
+ * @module yui
+ * @submodule yui-later
+ */
+
+var NO_ARGS = [];
+
+/**
+ * Executes the supplied function in the context of the supplied
+ * object 'when' milliseconds later. Executes the function a
+ * single time unless periodic is set to true.
+ * @for YUI
+ * @method later
+ * @param when {int} the number of milliseconds to wait until the fn
+ * is executed.
+ * @param o the context object.
+ * @param fn {Function|String} the function to execute or the name of
+ * the method in the 'o' object to execute.
+ * @param data [Array] data that is provided to the function. This
+ * accepts either a single item or an array. If an array is provided,
+ * the function is executed with one parameter for each array item.
+ * If you need to pass a single array parameter, it needs to be wrapped
+ * in an array [myarray].
+ *
+ * Note: native methods in IE may not have the call and apply methods.
+ * In this case, it will work, but you are limited to four arguments.
+ *
+ * @param periodic {boolean} if true, executes continuously at supplied
+ * interval until canceled.
+ * @return {object} a timer object. Call the cancel() method on this
+ * object to stop the timer.
+ */
+Y.later = function(when, o, fn, data, periodic) {
+ when = when || 0;
+ data = (!Y.Lang.isUndefined(data)) ? Y.Array(data) : NO_ARGS;
+ o = o || Y.config.win || Y;
+
+ var cancelled = false,
+ method = (o && Y.Lang.isString(fn)) ? o[fn] : fn,
+ wrapper = function() {
+ // IE 8- may execute a setInterval callback one last time
+ // after clearInterval was called, so in order to preserve
+ // the cancel() === no more runny-run, we have to jump through
+ // an extra hoop.
+ if (!cancelled) {
+ if (!method.apply) {
+ method(data[0], data[1], data[2], data[3]);
+ } else {
+ method.apply(o, data || NO_ARGS);
+ }
+ }
+ },
+ id = (periodic) ? setInterval(wrapper, when) : setTimeout(wrapper, when);
+
+ return {
+ id: id,
+ interval: periodic,
+ cancel: function() {
+ cancelled = true;
+ if (this.interval) {
+ clearInterval(id);
+ } else {
+ clearTimeout(id);
+ }
+ }
+ };
+};
+
+Y.Lang.later = Y.later;
+
+
+
+}, '@VERSION@' ,{requires:['yui-base']});
+YUI.add('loader-base', function(Y) {
+
+/**
+ * The YUI loader core
+ * @module loader
+ * @submodule loader-base
+ */
+
+if (!YUI.Env[Y.version]) {
+
+ (function() {
+ var VERSION = Y.version,
+ BUILD = '/build/',
+ ROOT = VERSION + BUILD,
+ CDN_BASE = Y.Env.base,
+ GALLERY_VERSION = 'gallery-2012.08.08-20-03',
+ TNT = '2in3',
+ TNT_VERSION = '4',
+ YUI2_VERSION = '2.9.0',
+ COMBO_BASE = CDN_BASE + 'combo?',
+ META = { version: VERSION,
+ root: ROOT,
+ base: Y.Env.base,
+ comboBase: COMBO_BASE,
+ skin: { defaultSkin: 'sam',
+ base: 'assets/skins/',
+ path: 'skin.css',
+ after: ['cssreset',
+ 'cssfonts',
+ 'cssgrids',
+ 'cssbase',
+ 'cssreset-context',
+ 'cssfonts-context']},
+ groups: {},
+ patterns: {} },
+ groups = META.groups,
+ yui2Update = function(tnt, yui2, config) {
+
+ var root = TNT + '.' +
+ (tnt || TNT_VERSION) + '/' +
+ (yui2 || YUI2_VERSION) + BUILD,
+ base = (config && config.base) ? config.base : CDN_BASE,
+ combo = (config && config.comboBase) ? config.comboBase : COMBO_BASE;
+
+ groups.yui2.base = base + root;
+ groups.yui2.root = root;
+ groups.yui2.comboBase = combo;
+ },
+ galleryUpdate = function(tag, config) {
+ var root = (tag || GALLERY_VERSION) + BUILD,
+ base = (config && config.base) ? config.base : CDN_BASE,
+ combo = (config && config.comboBase) ? config.comboBase : COMBO_BASE;
+
+ groups.gallery.base = base + root;
+ groups.gallery.root = root;
+ groups.gallery.comboBase = combo;
+ };
+
+
+ groups[VERSION] = {};
+
+ groups.gallery = {
+ ext: false,
+ combine: true,
+ comboBase: COMBO_BASE,
+ update: galleryUpdate,
+ patterns: { 'gallery-': { },
+ 'lang/gallery-': {},
+ 'gallerycss-': { type: 'css' } }
+ };
+
+ groups.yui2 = {
+ combine: true,
+ ext: false,
+ comboBase: COMBO_BASE,
+ update: yui2Update,
+ patterns: {
+ 'yui2-': {
+ configFn: function(me) {
+ if (/-skin|reset|fonts|grids|base/.test(me.name)) {
+ me.type = 'css';
+ me.path = me.path.replace(/\.js/, '.css');
+ // this makes skins in builds earlier than
+ // 2.6.0 work as long as combine is false
+ me.path = me.path.replace(/\/yui2-skin/,
+ '/assets/skins/sam/yui2-skin');
+ }
+ }
+ }
+ }
+ };
+
+ galleryUpdate();
+ yui2Update();
+
+ YUI.Env[VERSION] = META;
+ }());
+}
+
+
+/*jslint forin: true */
+
+/**
+ * Loader dynamically loads script and css files. It includes the dependency
+ * information for the version of the library in use, and will automatically pull in
+ * dependencies for the modules requested. It can also load the
+ * files from the Yahoo! CDN, and it can utilize the combo service provided on
+ * this network to reduce the number of http connections required to download
+ * YUI files.
+ *
+ * @module loader
+ * @main loader
+ * @submodule loader-base
+ */
+
+var NOT_FOUND = {},
+ NO_REQUIREMENTS = [],
+ MAX_URL_LENGTH = 1024,
+ GLOBAL_ENV = YUI.Env,
+ GLOBAL_LOADED = GLOBAL_ENV._loaded,
+ CSS = 'css',
+ JS = 'js',
+ INTL = 'intl',
+ DEFAULT_SKIN = 'sam',
+ VERSION = Y.version,
+ ROOT_LANG = '',
+ YObject = Y.Object,
+ oeach = YObject.each,
+ YArray = Y.Array,
+ _queue = GLOBAL_ENV._loaderQueue,
+ META = GLOBAL_ENV[VERSION],
+ SKIN_PREFIX = 'skin-',
+ L = Y.Lang,
+ ON_PAGE = GLOBAL_ENV.mods,
+ modulekey,
+ cache,
+ _path = function(dir, file, type, nomin) {
+ var path = dir + '/' + file;
+ if (!nomin) {
+ path += '-min';
+ }
+ path += '.' + (type || CSS);
+
+ return path;
+ };
+
+
+ if (!YUI.Env._cssLoaded) {
+ YUI.Env._cssLoaded = {};
+ }
+
+
+/**
+ * The component metadata is stored in Y.Env.meta.
+ * Part of the loader module.
+ * @property meta
+ * @for YUI
+ */
+Y.Env.meta = META;
+
+/**
+ * Loader dynamically loads script and css files. It includes the dependency
+ * info for the version of the library in use, and will automatically pull in
+ * dependencies for the modules requested. It can load the
+ * files from the Yahoo! CDN, and it can utilize the combo service provided on
+ * this network to reduce the number of http connections required to download
+ * YUI files. You can also specify an external, custom combo service to host
+ * your modules as well.
+
+ var Y = YUI();
+ var loader = new Y.Loader({
+ filter: 'debug',
+ base: '../../',
+ root: 'build/',
+ combine: true,
+ require: ['node', 'dd', 'console']
+ });
+ var out = loader.resolve(true);
+
+ * @constructor
+ * @class Loader
+ * @param {Object} config an optional set of configuration options.
+ * @param {String} config.base The base dir which to fetch this module from
+ * @param {String} config.comboBase The Combo service base path. Ex: `http://yui.yahooapis.com/combo?`
+ * @param {String} config.root The root path to prepend to module names for the combo service. Ex: `2.5.2/build/`
+ * @param {String|Object} config.filter A filter to apply to result urls. <a href="#property_filter">See filter property</a>
+ * @param {Object} config.filters Per-component filter specification. If specified for a given component, this overrides the filter config.
+ * @param {Boolean} config.combine Use a combo service to reduce the number of http connections required to load your dependencies
+ * @param {Boolean} [config.async=true] Fetch files in async
+ * @param {Array} config.ignore: A list of modules that should never be dynamically loaded
+ * @param {Array} config.force A list of modules that should always be loaded when required, even if already present on the page
+ * @param {HTMLElement|String} config.insertBefore Node or id for a node that should be used as the insertion point for new nodes
+ * @param {Object} config.jsAttributes Object literal containing attributes to add to script nodes
+ * @param {Object} config.cssAttributes Object literal containing attributes to add to link nodes
+ * @param {Number} config.timeout The number of milliseconds before a timeout occurs when dynamically loading nodes. If not set, there is no timeout
+ * @param {Object} config.context Execution context for all callbacks
+ * @param {Function} config.onSuccess Callback for the 'success' event
+ * @param {Function} config.onFailure Callback for the 'failure' event
+ * @param {Function} config.onCSS Callback for the 'CSSComplete' event. When loading YUI components with CSS the CSS is loaded first, then the script. This provides a moment you can tie into to improve the presentation of the page while the script is loading.
+ * @param {Function} config.onTimeout Callback for the 'timeout' event
+ * @param {Function} config.onProgress Callback executed each time a script or css file is loaded
+ * @param {Object} config.modules A list of module definitions. See <a href="#method_addModule">Loader.addModule</a> for the supported module metadata
+ * @param {Object} config.groups A list of group definitions. Each group can contain specific definitions for `base`, `comboBase`, `combine`, and accepts a list of `modules`.
+ * @param {String} config.2in3 The version of the YUI 2 in 3 wrapper to use. The intrinsic support for YUI 2 modules in YUI 3 relies on versions of the YUI 2 components inside YUI 3 module wrappers. These wrappers change over time to accomodate the issues that arise from running YUI 2 in a YUI 3 sandbox.
+ * @param {String} config.yui2 When using the 2in3 project, you can select the version of YUI 2 to use. Valid values are `2.2.2`, `2.3.1`, `2.4.1`, `2.5.2`, `2.6.0`, `2.7.0`, `2.8.0`, `2.8.1` and `2.9.0` [default] -- plus all versions of YUI 2 going forward.
+ */
+Y.Loader = function(o) {
+
+ var self = this;
+
+ //Catch no config passed.
+ o = o || {};
+
+ modulekey = META.md5;
+
+ /**
+ * Internal callback to handle multiple internal insert() calls
+ * so that css is inserted prior to js
+ * @property _internalCallback
+ * @private
+ */
+ // self._internalCallback = null;
+
+ /**
+ * Callback that will be executed when the loader is finished
+ * with an insert
+ * @method onSuccess
+ * @type function
+ */
+ // self.onSuccess = null;
+
+ /**
+ * Callback that will be executed if there is a failure
+ * @method onFailure
+ * @type function
+ */
+ // self.onFailure = null;
+
+ /**
+ * Callback for the 'CSSComplete' event. When loading YUI components
+ * with CSS the CSS is loaded first, then the script. This provides
+ * a moment you can tie into to improve the presentation of the page
+ * while the script is loading.
+ * @method onCSS
+ * @type function
+ */
+ // self.onCSS = null;
+
+ /**
+ * Callback executed each time a script or css file is loaded
+ * @method onProgress
+ * @type function
+ */
+ // self.onProgress = null;
+
+ /**
+ * Callback that will be executed if a timeout occurs
+ * @method onTimeout
+ * @type function
+ */
+ // self.onTimeout = null;
+
+ /**
+ * The execution context for all callbacks
+ * @property context
+ * @default {YUI} the YUI instance
+ */
+ self.context = Y;
+
+ /**
+ * Data that is passed to all callbacks
+ * @property data
+ */
+ // self.data = null;
+
+ /**
+ * Node reference or id where new nodes should be inserted before
+ * @property insertBefore
+ * @type string|HTMLElement
+ */
+ // self.insertBefore = null;
+
+ /**
+ * The charset attribute for inserted nodes
+ * @property charset
+ * @type string
+ * @deprecated , use cssAttributes or jsAttributes.
+ */
+ // self.charset = null;
+
+ /**
+ * An object literal containing attributes to add to link nodes
+ * @property cssAttributes
+ * @type object
+ */
+ // self.cssAttributes = null;
+
+ /**
+ * An object literal containing attributes to add to script nodes
+ * @property jsAttributes
+ * @type object
+ */
+ // self.jsAttributes = null;
+
+ /**
+ * The base directory.
+ * @property base
+ * @type string
+ * @default http://yui.yahooapis.com/[YUI VERSION]/build/
+ */
+ self.base = Y.Env.meta.base + Y.Env.meta.root;
+
+ /**
+ * Base path for the combo service
+ * @property comboBase
+ * @type string
+ * @default http://yui.yahooapis.com/combo?
+ */
+ self.comboBase = Y.Env.meta.comboBase;
+
+ /*
+ * Base path for language packs.
+ */
+ // self.langBase = Y.Env.meta.langBase;
+ // self.lang = "";
+
+ /**
+ * If configured, the loader will attempt to use the combo
+ * service for YUI resources and configured external resources.
+ * @property combine
+ * @type boolean
+ * @default true if a base dir isn't in the config
+ */
+ self.combine = o.base &&
+ (o.base.indexOf(self.comboBase.substr(0, 20)) > -1);
+
+ /**
+ * The default seperator to use between files in a combo URL
+ * @property comboSep
+ * @type {String}
+ * @default Ampersand
+ */
+ self.comboSep = '&';
+ /**
+ * Max url length for combo urls. The default is 1024. This is the URL
+ * limit for the Yahoo! hosted combo servers. If consuming
+ * a different combo service that has a different URL limit
+ * it is possible to override this default by supplying
+ * the maxURLLength config option. The config option will
+ * only take effect if lower than the default.
+ *
+ * @property maxURLLength
+ * @type int
+ */
+ self.maxURLLength = MAX_URL_LENGTH;
+
+ /**
+ * Ignore modules registered on the YUI global
+ * @property ignoreRegistered
+ * @default false
+ */
+ self.ignoreRegistered = o.ignoreRegistered;
+
+ /**
+ * Root path to prepend to module path for the combo
+ * service
+ * @property root
+ * @type string
+ * @default [YUI VERSION]/build/
+ */
+ self.root = Y.Env.meta.root;
+
+ /**
+ * Timeout value in milliseconds. If set, self value will be used by
+ * the get utility. the timeout event will fire if
+ * a timeout occurs.
+ * @property timeout
+ * @type int
+ */
+ self.timeout = 0;
+
+ /**
+ * A list of modules that should not be loaded, even if
+ * they turn up in the dependency tree
+ * @property ignore
+ * @type string[]
+ */
+ // self.ignore = null;
+
+ /**
+ * A list of modules that should always be loaded, even
+ * if they have already been inserted into the page.
+ * @property force
+ * @type string[]
+ */
+ // self.force = null;
+
+ self.forceMap = {};
+
+ /**
+ * Should we allow rollups
+ * @property allowRollup
+ * @type boolean
+ * @default false
+ */
+ self.allowRollup = false;
+
+ /**
+ * A filter to apply to result urls. This filter will modify the default
+ * path for all modules. The default path for the YUI library is the
+ * minified version of the files (e.g., event-min.js). The filter property
+ * can be a predefined filter or a custom filter. The valid predefined
+ * filters are:
+ * <dl>
+ * <dt>DEBUG</dt>
+ * <dd>Selects the debug versions of the library (e.g., event-debug.js).
+ * This option will automatically include the Logger widget</dd>
+ * <dt>RAW</dt>
+ * <dd>Selects the non-minified version of the library (e.g., event.js).
+ * </dd>
+ * </dl>
+ * You can also define a custom filter, which must be an object literal
+ * containing a search expression and a replace string:
+ *
+ * myFilter: {
+ * 'searchExp': "-min\\.js",
+ * 'replaceStr': "-debug.js"
+ * }
+ *
+ * @property filter
+ * @type string| {searchExp: string, replaceStr: string}
+ */
+ // self.filter = null;
+
+ /**
+ * per-component filter specification. If specified for a given
+ * component, this overrides the filter config.
+ * @property filters
+ * @type object
+ */
+ self.filters = {};
+
+ /**
+ * The list of requested modules
+ * @property required
+ * @type {string: boolean}
+ */
+ self.required = {};
+
+ /**
+ * If a module name is predefined when requested, it is checked againsts
+ * the patterns provided in this property. If there is a match, the
+ * module is added with the default configuration.
+ *
+ * At the moment only supporting module prefixes, but anticipate
+ * supporting at least regular expressions.
+ * @property patterns
+ * @type Object
+ */
+ // self.patterns = Y.merge(Y.Env.meta.patterns);
+ self.patterns = {};
+
+ /**
+ * The library metadata
+ * @property moduleInfo
+ */
+ // self.moduleInfo = Y.merge(Y.Env.meta.moduleInfo);
+ self.moduleInfo = {};
+
+ self.groups = Y.merge(Y.Env.meta.groups);
+
+ /**
+ * Provides the information used to skin the skinnable components.
+ * The following skin definition would result in 'skin1' and 'skin2'
+ * being loaded for calendar (if calendar was requested), and
+ * 'sam' for all other skinnable components:
+ *
+ * skin: {
+ * // The default skin, which is automatically applied if not
+ * // overriden by a component-specific skin definition.
+ * // Change this in to apply a different skin globally
+ * defaultSkin: 'sam',
+ *
+ * // This is combined with the loader base property to get
+ * // the default root directory for a skin. ex:
+ * // http://yui.yahooapis.com/2.3.0/build/assets/skins/sam/
+ * base: 'assets/skins/',
+ *
+ * // Any component-specific overrides can be specified here,
+ * // making it possible to load different skins for different
+ * // components. It is possible to load more than one skin
+ * // for a given component as well.
+ * overrides: {
+ * calendar: ['skin1', 'skin2']
+ * }
+ * }
+ * @property skin
+ * @type {Object}
+ */
+ self.skin = Y.merge(Y.Env.meta.skin);
+
+ /*
+ * Map of conditional modules
+ * @since 3.2.0
+ */
+ self.conditions = {};
+
+ // map of modules with a hash of modules that meet the requirement
+ // self.provides = {};
+
+ self.config = o;
+ self._internal = true;
+
+ self._populateCache();
+
+ /**
+ * Set when beginning to compute the dependency tree.
+ * Composed of what YUI reports to be loaded combined
+ * with what has been loaded by any instance on the page
+ * with the version number specified in the metadata.
+ * @property loaded
+ * @type {string: boolean}
+ */
+ self.loaded = GLOBAL_LOADED[VERSION];
+
+
+ /**
+ * Should Loader fetch scripts in `async`, defaults to `true`
+ * @property async
+ */
+
+ self.async = true;
+
+ self._inspectPage();
+
+ self._internal = false;
+
+ self._config(o);
+
+ self.forceMap = (self.force) ? Y.Array.hash(self.force) : {};
+
+ self.testresults = null;
+
+ if (Y.config.tests) {
+ self.testresults = Y.config.tests;
+ }
+
+ /**
+ * List of rollup files found in the library metadata
+ * @property rollups
+ */
+ // self.rollups = null;
+
+ /**
+ * Whether or not to load optional dependencies for
+ * the requested modules
+ * @property loadOptional
+ * @type boolean
+ * @default false
+ */
+ // self.loadOptional = false;
+
+ /**
+ * All of the derived dependencies in sorted order, which
+ * will be populated when either calculate() or insert()
+ * is called
+ * @property sorted
+ * @type string[]
+ */
+ self.sorted = [];
+
+ /*
+ * A list of modules to attach to the YUI instance when complete.
+ * If not supplied, the sorted list of dependencies are applied.
+ * @property attaching
+ */
+ // self.attaching = null;
+
+ /**
+ * Flag to indicate the dependency tree needs to be recomputed
+ * if insert is called again.
+ * @property dirty
+ * @type boolean
+ * @default true
+ */
+ self.dirty = true;
+
+ /**
+ * List of modules inserted by the utility
+ * @property inserted
+ * @type {string: boolean}
+ */
+ self.inserted = {};
+
+ /**
+ * List of skipped modules during insert() because the module
+ * was not defined
+ * @property skipped
+ */
+ self.skipped = {};
+
+ // Y.on('yui:load', self.loadNext, self);
+
+ self.tested = {};
+
+ /*
+ * Cached sorted calculate results
+ * @property results
+ * @since 3.2.0
+ */
+ //self.results = {};
+
+ if (self.ignoreRegistered) {
+ //Clear inpage already processed modules.
+ self._resetModules();
+ }
+
+};
+
+Y.Loader.prototype = {
+ /**
+ * Checks the cache for modules and conditions, if they do not exist
+ * process the default metadata and populate the local moduleInfo hash.
+ * @method _populateCache
+ * @private
+ */
+ _populateCache: function() {
+ var self = this,
+ defaults = META.modules,
+ cache = GLOBAL_ENV._renderedMods,
+ i;
+
+ if (cache && !self.ignoreRegistered) {
+ for (i in cache) {
+ if (cache.hasOwnProperty(i)) {
+ self.moduleInfo[i] = Y.merge(cache[i]);
+ }
+ }
+
+ cache = GLOBAL_ENV._conditions;
+ for (i in cache) {
+ if (cache.hasOwnProperty(i)) {
+ self.conditions[i] = Y.merge(cache[i]);
+ }
+ }
+
+ } else {
+ for (i in defaults) {
+ if (defaults.hasOwnProperty(i)) {
+ self.addModule(defaults[i], i);
+ }
+ }
+ }
+
+ },
+ /**
+ * Reset modules in the module cache to a pre-processed state so additional
+ * computations with a different skin or language will work as expected.
+ * @private _resetModules
+ */
+ _resetModules: function() {
+ var self = this, i, o;
+ for (i in self.moduleInfo) {
+ if (self.moduleInfo.hasOwnProperty(i)) {
+ var mod = self.moduleInfo[i],
+ name = mod.name,
+ details = (YUI.Env.mods[name] ? YUI.Env.mods[name].details : null);
+
+ if (details) {
+ self.moduleInfo[name]._reset = true;
+ self.moduleInfo[name].requires = details.requires || [];
+ self.moduleInfo[name].optional = details.optional || [];
+ self.moduleInfo[name].supersedes = details.supercedes || [];
+ }
+
+ if (mod.defaults) {
+ for (o in mod.defaults) {
+ if (mod.defaults.hasOwnProperty(o)) {
+ if (mod[o]) {
+ mod[o] = mod.defaults[o];
+ }
+ }
+ }
+ }
+ delete mod.langCache;
+ delete mod.skinCache;
+ if (mod.skinnable) {
+ self._addSkin(self.skin.defaultSkin, mod.name);
+ }
+ }
+ }
+ },
+ /**
+ Regex that matches a CSS URL. Used to guess the file type when it's not
+ specified.
+
+ @property REGEX_CSS
+ @type RegExp
+ @final
+ @protected
+ @since 3.5.0
+ **/
+ REGEX_CSS: /\.css(?:[?;].*)?$/i,
+
+ /**
+ * Default filters for raw and debug
+ * @property FILTER_DEFS
+ * @type Object
+ * @final
+ * @protected
+ */
+ FILTER_DEFS: {
+ RAW: {
+ 'searchExp': '-min\\.js',
+ 'replaceStr': '.js'
+ },
+ DEBUG: {
+ 'searchExp': '-min\\.js',
+ 'replaceStr': '-debug.js'
+ },
+ COVERAGE: {
+ 'searchExp': '-min\\.js',
+ 'replaceStr': '-coverage.js'
+ }
+ },
+ /*
+ * Check the pages meta-data and cache the result.
+ * @method _inspectPage
+ * @private
+ */
+ _inspectPage: function() {
+ var self = this, v, m, req, mr, i;
+
+ //Inspect the page for CSS only modules and mark them as loaded.
+ for (i in self.moduleInfo) {
+ if (self.moduleInfo.hasOwnProperty(i)) {
+ v = self.moduleInfo[i];
+ if (v.type && v.type === CSS) {
+ if (self.isCSSLoaded(v.name)) {
+ self.loaded[i] = true;
+ }
+ }
+ }
+ }
+ for (i in ON_PAGE) {
+ if (ON_PAGE.hasOwnProperty(i)) {
+ v = ON_PAGE[i];
+ if (v.details) {
+ m = self.moduleInfo[v.name];
+ req = v.details.requires;
+ mr = m && m.requires;
+
+ if (m) {
+ if (!m._inspected && req && mr.length != req.length) {
+ // console.log('deleting ' + m.name);
+ delete m.expanded;
+ }
+ } else {
+ m = self.addModule(v.details, i);
+ }
+ m._inspected = true;
+ }
+ }
+ }
+ },
+ /*
+ * returns true if b is not loaded, and is required directly or by means of modules it supersedes.
+ * @private
+ * @method _requires
+ * @param {String} mod1 The first module to compare
+ * @param {String} mod2 The second module to compare
+ */
+ _requires: function(mod1, mod2) {
+
+ var i, rm, after_map, s,
+ info = this.moduleInfo,
+ m = info[mod1],
+ other = info[mod2];
+
+ if (!m || !other) {
+ return false;
+ }
+
+ rm = m.expanded_map;
+ after_map = m.after_map;
+
+ // check if this module should be sorted after the other
+ // do this first to short circut circular deps
+ if (after_map && (mod2 in after_map)) {
+ return true;
+ }
+
+ after_map = other.after_map;
+
+ // and vis-versa
+ if (after_map && (mod1 in after_map)) {
+ return false;
+ }
+
+ // check if this module requires one the other supersedes
+ s = info[mod2] && info[mod2].supersedes;
+ if (s) {
+ for (i = 0; i < s.length; i++) {
+ if (this._requires(mod1, s[i])) {
+ return true;
+ }
+ }
+ }
+
+ s = info[mod1] && info[mod1].supersedes;
+ if (s) {
+ for (i = 0; i < s.length; i++) {
+ if (this._requires(mod2, s[i])) {
+ return false;
+ }
+ }
+ }
+
+ // check if this module requires the other directly
+ // if (r && YArray.indexOf(r, mod2) > -1) {
+ if (rm && (mod2 in rm)) {
+ return true;
+ }
+
+ // external css files should be sorted below yui css
+ if (m.ext && m.type == CSS && !other.ext && other.type == CSS) {
+ return true;
+ }
+
+ return false;
+ },
+ /**
+ * Apply a new config to the Loader instance
+ * @method _config
+ * @private
+ * @param {Object} o The new configuration
+ */
+ _config: function(o) {
+ var i, j, val, a, f, group, groupName, self = this;
+ // apply config values
+ if (o) {
+ for (i in o) {
+ if (o.hasOwnProperty(i)) {
+ val = o[i];
+ if (i == 'require') {
+ self.require(val);
+ } else if (i == 'skin') {
+ //If the config.skin is a string, format to the expected object
+ if (typeof val === 'string') {
+ self.skin.defaultSkin = o.skin;
+ val = {
+ defaultSkin: val
+ };
+ }
+
+ Y.mix(self.skin, val, true);
+ } else if (i == 'groups') {
+ for (j in val) {
+ if (val.hasOwnProperty(j)) {
+ groupName = j;
+ group = val[j];
+ self.addGroup(group, groupName);
+ if (group.aliases) {
+ for (a in group.aliases) {
+ if (group.aliases.hasOwnProperty(a)) {
+ self.addAlias(group.aliases[a], a);
+ }
+ }
+ }
+ }
+ }
+
+ } else if (i == 'modules') {
+ // add a hash of module definitions
+ for (j in val) {
+ if (val.hasOwnProperty(j)) {
+ self.addModule(val[j], j);
+ }
+ }
+ } else if (i === 'aliases') {
+ for (j in val) {
+ if (val.hasOwnProperty(j)) {
+ self.addAlias(val[j], j);
+ }
+ }
+ } else if (i == 'gallery') {
+ this.groups.gallery.update(val, o);
+ } else if (i == 'yui2' || i == '2in3') {
+ this.groups.yui2.update(o['2in3'], o.yui2, o);
+ } else {
+ self[i] = val;
+ }
+ }
+ }
+ }
+
+ // fix filter
+ f = self.filter;
+
+ if (L.isString(f)) {
+ f = f.toUpperCase();
+ self.filterName = f;
+ self.filter = self.FILTER_DEFS[f];
+ if (f == 'DEBUG') {
+ self.require('yui-log', 'dump');
+ }
+ }
+
+ if (self.filterName && self.coverage) {
+ if (self.filterName == 'COVERAGE' && L.isArray(self.coverage) && self.coverage.length) {
+ var mods = [];
+ for (i = 0; i < self.coverage.length; i++) {
+ var mod = self.coverage[i];
+ if (self.moduleInfo[mod] && self.moduleInfo[mod].use) {
+ mods = [].concat(mods, self.moduleInfo[mod].use);
+ } else {
+ mods.push(mod);
+ }
+ }
+ self.filters = self.filters || {};
+ Y.Array.each(mods, function(mod) {
+ self.filters[mod] = self.FILTER_DEFS.COVERAGE;
+ });
+ self.filterName = 'RAW';
+ self.filter = self.FILTER_DEFS[self.filterName];
+ }
+ }
+
+
+ if (self.lang) {
+ //Removed this so that when Loader is invoked
+ //it doesn't request what it doesn't need.
+ //self.require('intl-base', 'intl');
+ }
+
+ },
+
+ /**
+ * Returns the skin module name for the specified skin name. If a
+ * module name is supplied, the returned skin module name is
+ * specific to the module passed in.
+ * @method formatSkin
+ * @param {string} skin the name of the skin.
+ * @param {string} mod optional: the name of a module to skin.
+ * @return {string} the full skin module name.
+ */
+ formatSkin: function(skin, mod) {
+ var s = SKIN_PREFIX + skin;
+ if (mod) {
+ s = s + '-' + mod;
+ }
+
+ return s;
+ },
+
+ /**
+ * Adds the skin def to the module info
+ * @method _addSkin
+ * @param {string} skin the name of the skin.
+ * @param {string} mod the name of the module.
+ * @param {string} parent parent module if this is a skin of a
+ * submodule or plugin.
+ * @return {string} the module name for the skin.
+ * @private
+ */
+ _addSkin: function(skin, mod, parent) {
+ var mdef, pkg, name, nmod,
+ info = this.moduleInfo,
+ sinf = this.skin,
+ ext = info[mod] && info[mod].ext;
+
+ // Add a module definition for the module-specific skin css
+ if (mod) {
+ name = this.formatSkin(skin, mod);
+ if (!info[name]) {
+ mdef = info[mod];
+ pkg = mdef.pkg || mod;
+ nmod = {
+ skin: true,
+ name: name,
+ group: mdef.group,
+ type: 'css',
+ after: sinf.after,
+ path: (parent || pkg) + '/' + sinf.base + skin +
+ '/' + mod + '.css',
+ ext: ext
+ };
+ if (mdef.base) {
+ nmod.base = mdef.base;
+ }
+ if (mdef.configFn) {
+ nmod.configFn = mdef.configFn;
+ }
+ this.addModule(nmod, name);
+
+ }
+ }
+
+ return name;
+ },
+ /**
+ * Adds an alias module to the system
+ * @method addAlias
+ * @param {Array} use An array of modules that makes up this alias
+ * @param {String} name The name of the alias
+ * @example
+ * var loader = new Y.Loader({});
+ * loader.addAlias([ 'node', 'yql' ], 'davglass');
+ * loader.require(['davglass']);
+ * var out = loader.resolve(true);
+ *
+ * //out.js will contain Node and YQL modules
+ */
+ addAlias: function(use, name) {
+ YUI.Env.aliases[name] = use;
+ this.addModule({
+ name: name,
+ use: use
+ });
+ },
+ /**
+ * Add a new module group
+ * @method addGroup
+ * @param {Object} config An object containing the group configuration data
+ * @param {String} config.name required, the group name
+ * @param {String} config.base The base directory for this module group
+ * @param {String} config.root The root path to add to each combo resource path
+ * @param {Boolean} config.combine Should the request be combined
+ * @param {String} config.comboBase Combo service base path
+ * @param {Object} config.modules The group of modules
+ * @param {String} name the group name.
+ * @example
+ * var loader = new Y.Loader({});
+ * loader.addGroup({
+ * name: 'davglass',
+ * combine: true,
+ * comboBase: '/combo?',
+ * root: '',
+ * modules: {
+ * //Module List here
+ * }
+ * }, 'davglass');
+ */
+ addGroup: function(o, name) {
+ var mods = o.modules,
+ self = this, i, v;
+
+ name = name || o.name;
+ o.name = name;
+ self.groups[name] = o;
+
+ if (o.patterns) {
+ for (i in o.patterns) {
+ if (o.patterns.hasOwnProperty(i)) {
+ o.patterns[i].group = name;
+ self.patterns[i] = o.patterns[i];
+ }
+ }
+ }
+
+ if (mods) {
+ for (i in mods) {
+ if (mods.hasOwnProperty(i)) {
+ v = mods[i];
+ if (typeof v === 'string') {
+ v = { name: i, fullpath: v };
+ }
+ v.group = name;
+ self.addModule(v, i);
+ }
+ }
+ }
+ },
+
+ /**
+ * Add a new module to the component metadata.
+ * @method addModule
+ * @param {Object} config An object containing the module data.
+ * @param {String} config.name Required, the component name
+ * @param {String} config.type Required, the component type (js or css)
+ * @param {String} config.path Required, the path to the script from `base`
+ * @param {Array} config.requires Array of modules required by this component
+ * @param {Array} [config.optional] Array of optional modules for this component
+ * @param {Array} [config.supersedes] Array of the modules this component replaces
+ * @param {Array} [config.after] Array of modules the components which, if present, should be sorted above this one
+ * @param {Object} [config.after_map] Faster alternative to 'after' -- supply a hash instead of an array
+ * @param {Number} [config.rollup] The number of superseded modules required for automatic rollup
+ * @param {String} [config.fullpath] If `fullpath` is specified, this is used instead of the configured `base + path`
+ * @param {Boolean} [config.skinnable] Flag to determine if skin assets should automatically be pulled in
+ * @param {Object} [config.submodules] Hash of submodules
+ * @param {String} [config.group] The group the module belongs to -- this is set automatically when it is added as part of a group configuration.
+ * @param {Array} [config.lang] Array of BCP 47 language tags of languages for which this module has localized resource bundles, e.g., `["en-GB", "zh-Hans-CN"]`
+ * @param {Object} [config.condition] Specifies that the module should be loaded automatically if a condition is met. This is an object with up to three fields:
+ * @param {String} [config.condition.trigger] The name of a module that can trigger the auto-load
+ * @param {Function} [config.condition.test] A function that returns true when the module is to be loaded.
+ * @param {String} [config.condition.when] Specifies the load order of the conditional module
+ * with regard to the position of the trigger module.
+ * This should be one of three values: `before`, `after`, or `instead`. The default is `after`.
+ * @param {Object} [config.testresults] A hash of test results from `Y.Features.all()`
+ * @param {Function} [config.configFn] A function to exectute when configuring this module
+ * @param {Object} config.configFn.mod The module config, modifying this object will modify it's config. Returning false will delete the module's config.
+ * @param {String} [name] The module name, required if not in the module data.
+ * @return {Object} the module definition or null if the object passed in did not provide all required attributes.
+ */
+ addModule: function(o, name) {
+ name = name || o.name;
+
+ if (typeof o === 'string') {
+ o = { name: name, fullpath: o };
+ }
+
+ //Only merge this data if the temp flag is set
+ //from an earlier pass from a pattern or else
+ //an override module (YUI_config) can not be used to
+ //replace a default module.
+ if (this.moduleInfo[name] && this.moduleInfo[name].temp) {
+ //This catches temp modules loaded via a pattern
+ // The module will be added twice, once from the pattern and
+ // Once from the actual add call, this ensures that properties
+ // that were added to the module the first time around (group: gallery)
+ // are also added the second time around too.
+ o = Y.merge(this.moduleInfo[name], o);
+ }
+
+ o.name = name;
+
+ if (!o || !o.name) {
+ return null;
+ }
+
+ if (!o.type) {
+ //Always assume it's javascript unless the CSS pattern is matched.
+ o.type = JS;
+ var p = o.path || o.fullpath;
+ if (p && this.REGEX_CSS.test(p)) {
+ o.type = CSS;
+ }
+ }
+
+ if (!o.path && !o.fullpath) {
+ o.path = _path(name, name, o.type);
+ }
+ o.supersedes = o.supersedes || o.use;
+
+ o.ext = ('ext' in o) ? o.ext : (this._internal) ? false : true;
+
+ // Handle submodule logic
+ var subs = o.submodules, i, l, t, sup, s, smod, plugins, plug,
+ j, langs, packName, supName, flatSup, flatLang, lang, ret,
+ overrides, skinname, when, g,
+ conditions = this.conditions, trigger;
+ // , existing = this.moduleInfo[name], newr;
+
+ this.moduleInfo[name] = o;
+
+ o.requires = o.requires || [];
+
+ /*
+ Only allowing the cascade of requires information, since
+ optional and supersedes are far more fine grained than
+ a blanket requires is.
+ */
+ if (this.requires) {
+ for (i = 0; i < this.requires.length; i++) {
+ o.requires.push(this.requires[i]);
+ }
+ }
+ if (o.group && this.groups && this.groups[o.group]) {
+ g = this.groups[o.group];
+ if (g.requires) {
+ for (i = 0; i < g.requires.length; i++) {
+ o.requires.push(g.requires[i]);
+ }
+ }
+ }
+
+
+ if (!o.defaults) {
+ o.defaults = {
+ requires: o.requires ? [].concat(o.requires) : null,
+ supersedes: o.supersedes ? [].concat(o.supersedes) : null,
+ optional: o.optional ? [].concat(o.optional) : null
+ };
+ }
+
+ if (o.skinnable && o.ext && o.temp) {
+ skinname = this._addSkin(this.skin.defaultSkin, name);
+ o.requires.unshift(skinname);
+ }
+
+ if (o.requires.length) {
+ o.requires = this.filterRequires(o.requires) || [];
+ }
+
+ if (!o.langPack && o.lang) {
+ langs = YArray(o.lang);
+ for (j = 0; j < langs.length; j++) {
+ lang = langs[j];
+ packName = this.getLangPackName(lang, name);
+ smod = this.moduleInfo[packName];
+ if (!smod) {
+ smod = this._addLangPack(lang, o, packName);
+ }
+ }
+ }
+
+
+ if (subs) {
+ sup = o.supersedes || [];
+ l = 0;
+
+ for (i in subs) {
+ if (subs.hasOwnProperty(i)) {
+ s = subs[i];
+
+ s.path = s.path || _path(name, i, o.type);
+ s.pkg = name;
+ s.group = o.group;
+
+ if (s.supersedes) {
+ sup = sup.concat(s.supersedes);
+ }
+
+ smod = this.addModule(s, i);
+ sup.push(i);
+
+ if (smod.skinnable) {
+ o.skinnable = true;
+ overrides = this.skin.overrides;
+ if (overrides && overrides[i]) {
+ for (j = 0; j < overrides[i].length; j++) {
+ skinname = this._addSkin(overrides[i][j],
+ i, name);
+ sup.push(skinname);
+ }
+ }
+ skinname = this._addSkin(this.skin.defaultSkin,
+ i, name);
+ sup.push(skinname);
+ }
+
+ // looks like we are expected to work out the metadata
+ // for the parent module language packs from what is
+ // specified in the child modules.
+ if (s.lang && s.lang.length) {
+
+ langs = YArray(s.lang);
+ for (j = 0; j < langs.length; j++) {
+ lang = langs[j];
+ packName = this.getLangPackName(lang, name);
+ supName = this.getLangPackName(lang, i);
+ smod = this.moduleInfo[packName];
+
+ if (!smod) {
+ smod = this._addLangPack(lang, o, packName);
+ }
+
+ flatSup = flatSup || YArray.hash(smod.supersedes);
+
+ if (!(supName in flatSup)) {
+ smod.supersedes.push(supName);
+ }
+
+ o.lang = o.lang || [];
+
+ flatLang = flatLang || YArray.hash(o.lang);
+
+ if (!(lang in flatLang)) {
+ o.lang.push(lang);
+ }
+
+// Add rollup file, need to add to supersedes list too
+
+ // default packages
+ packName = this.getLangPackName(ROOT_LANG, name);
+ supName = this.getLangPackName(ROOT_LANG, i);
+
+ smod = this.moduleInfo[packName];
+
+ if (!smod) {
+ smod = this._addLangPack(lang, o, packName);
+ }
+
+ if (!(supName in flatSup)) {
+ smod.supersedes.push(supName);
+ }
+
+// Add rollup file, need to add to supersedes list too
+
+ }
+ }
+
+ l++;
+ }
+ }
+ //o.supersedes = YObject.keys(YArray.hash(sup));
+ o.supersedes = YArray.dedupe(sup);
+ if (this.allowRollup) {
+ o.rollup = (l < 4) ? l : Math.min(l - 1, 4);
+ }
+ }
+
+ plugins = o.plugins;
+ if (plugins) {
+ for (i in plugins) {
+ if (plugins.hasOwnProperty(i)) {
+ plug = plugins[i];
+ plug.pkg = name;
+ plug.path = plug.path || _path(name, i, o.type);
+ plug.requires = plug.requires || [];
+ plug.group = o.group;
+ this.addModule(plug, i);
+ if (o.skinnable) {
+ this._addSkin(this.skin.defaultSkin, i, name);
+ }
+
+ }
+ }
+ }
+
+ if (o.condition) {
+ t = o.condition.trigger;
+ if (YUI.Env.aliases[t]) {
+ t = YUI.Env.aliases[t];
+ }
+ if (!Y.Lang.isArray(t)) {
+ t = [t];
+ }
+
+ for (i = 0; i < t.length; i++) {
+ trigger = t[i];
+ when = o.condition.when;
+ conditions[trigger] = conditions[trigger] || {};
+ conditions[trigger][name] = o.condition;
+ // the 'when' attribute can be 'before', 'after', or 'instead'
+ // the default is after.
+ if (when && when != 'after') {
+ if (when == 'instead') { // replace the trigger
+ o.supersedes = o.supersedes || [];
+ o.supersedes.push(trigger);
+ } else { // before the trigger
+ // the trigger requires the conditional mod,
+ // so it should appear before the conditional
+ // mod if we do not intersede.
+ }
+ } else { // after the trigger
+ o.after = o.after || [];
+ o.after.push(trigger);
+ }
+ }
+ }
+
+ if (o.supersedes) {
+ o.supersedes = this.filterRequires(o.supersedes);
+ }
+
+ if (o.after) {
+ o.after = this.filterRequires(o.after);
+ o.after_map = YArray.hash(o.after);
+ }
+
+ // this.dirty = true;
+
+ if (o.configFn) {
+ ret = o.configFn(o);
+ if (ret === false) {
+ delete this.moduleInfo[name];
+ delete GLOBAL_ENV._renderedMods[name];
+ o = null;
+ }
+ }
+ //Add to global cache
+ if (o) {
+ if (!GLOBAL_ENV._renderedMods) {
+ GLOBAL_ENV._renderedMods = {};
+ }
+ GLOBAL_ENV._renderedMods[name] = Y.mix(GLOBAL_ENV._renderedMods[name] || {}, o);
+ GLOBAL_ENV._conditions = conditions;
+ }
+
+ return o;
+ },
+
+ /**
+ * Add a requirement for one or more module
+ * @method require
+ * @param {string[] | string*} what the modules to load.
+ */
+ require: function(what) {
+ var a = (typeof what === 'string') ? YArray(arguments) : what;
+ this.dirty = true;
+ this.required = Y.merge(this.required, YArray.hash(this.filterRequires(a)));
+
+ this._explodeRollups();
+ },
+ /**
+ * Grab all the items that were asked for, check to see if the Loader
+ * meta-data contains a "use" array. If it doesm remove the asked item and replace it with
+ * the content of the "use".
+ * This will make asking for: "dd"
+ * Actually ask for: "dd-ddm-base,dd-ddm,dd-ddm-drop,dd-drag,dd-proxy,dd-constrain,dd-drop,dd-scroll,dd-drop-plugin"
+ * @private
+ * @method _explodeRollups
+ */
+ _explodeRollups: function() {
+ var self = this, m, i, a, v, len, len2,
+ r = self.required;
+
+ if (!self.allowRollup) {
+ for (i in r) {
+ if (r.hasOwnProperty(i)) {
+ m = self.getModule(i);
+ if (m && m.use) {
+ len = m.use.length;
+ for (a = 0; a < len; a++) {
+ m = self.getModule(m.use[a]);
+ if (m && m.use) {
+ len2 = m.use.length;
+ for (v = 0; v < len2; v++) {
+ r[m.use[v]] = true;
+ }
+ } else {
+ r[m.use[a]] = true;
+ }
+ }
+ }
+ }
+ }
+ self.required = r;
+ }
+
+ },
+ /**
+ * Explodes the required array to remove aliases and replace them with real modules
+ * @method filterRequires
+ * @param {Array} r The original requires array
+ * @return {Array} The new array of exploded requirements
+ */
+ filterRequires: function(r) {
+ if (r) {
+ if (!Y.Lang.isArray(r)) {
+ r = [r];
+ }
+ r = Y.Array(r);
+ var c = [], i, mod, o, m;
+
+ for (i = 0; i < r.length; i++) {
+ mod = this.getModule(r[i]);
+ if (mod && mod.use) {
+ for (o = 0; o < mod.use.length; o++) {
+ //Must walk the other modules in case a module is a rollup of rollups (datatype)
+ m = this.getModule(mod.use[o]);
+ if (m && m.use) {
+ c = Y.Array.dedupe([].concat(c, this.filterRequires(m.use)));
+ } else {
+ c.push(mod.use[o]);
+ }
+ }
+ } else {
+ c.push(r[i]);
+ }
+ }
+ r = c;
+ }
+ return r;
+ },
+ /**
+ * Returns an object containing properties for all modules required
+ * in order to load the requested module
+ * @method getRequires
+ * @param {object} mod The module definition from moduleInfo.
+ * @return {array} the expanded requirement list.
+ */
+ getRequires: function(mod) {
+
+ if (!mod) {
+ //console.log('returning no reqs for ' + mod.name);
+ return NO_REQUIREMENTS;
+ }
+
+ if (mod._parsed) {
+ //console.log('returning requires for ' + mod.name, mod.requires);
+ return mod.expanded || NO_REQUIREMENTS;
+ }
+
+ //TODO add modue cache here out of scope..
+
+ var i, m, j, add, packName, lang, testresults = this.testresults,
+ name = mod.name, cond,
+ adddef = ON_PAGE[name] && ON_PAGE[name].details,
+ d, k, m1, go, def,
+ r, old_mod,
+ o, skinmod, skindef, skinpar, skinname,
+ intl = mod.lang || mod.intl,
+ info = this.moduleInfo,
+ ftests = Y.Features && Y.Features.tests.load,
+ hash, reparse;
+
+ // console.log(name);
+
+ // pattern match leaves module stub that needs to be filled out
+ if (mod.temp && adddef) {
+ old_mod = mod;
+ mod = this.addModule(adddef, name);
+ mod.group = old_mod.group;
+ mod.pkg = old_mod.pkg;
+ delete mod.expanded;
+ }
+
+ // console.log('cache: ' + mod.langCache + ' == ' + this.lang);
+
+ //If a skin or a lang is different, reparse..
+ reparse = !((!this.lang || mod.langCache === this.lang) && (mod.skinCache === this.skin.defaultSkin));
+
+ if (mod.expanded && !reparse) {
+ return mod.expanded;
+ }
+
+
+ d = [];
+ hash = {};
+ r = this.filterRequires(mod.requires);
+ if (mod.lang) {
+ //If a module has a lang attribute, auto add the intl requirement.
+ d.unshift('intl');
+ r.unshift('intl');
+ intl = true;
+ }
+ o = this.filterRequires(mod.optional);
+
+
+ mod._parsed = true;
+ mod.langCache = this.lang;
+ mod.skinCache = this.skin.defaultSkin;
+
+ for (i = 0; i < r.length; i++) {
+ if (!hash[r[i]]) {
+ d.push(r[i]);
+ hash[r[i]] = true;
+ m = this.getModule(r[i]);
+ if (m) {
+ add = this.getRequires(m);
+ intl = intl || (m.expanded_map &&
+ (INTL in m.expanded_map));
+ for (j = 0; j < add.length; j++) {
+ d.push(add[j]);
+ }
+ }
+ }
+ }
+
+ // get the requirements from superseded modules, if any
+ r = this.filterRequires(mod.supersedes);
+ if (r) {
+ for (i = 0; i < r.length; i++) {
+ if (!hash[r[i]]) {
+ // if this module has submodules, the requirements list is
+ // expanded to include the submodules. This is so we can
+ // prevent dups when a submodule is already loaded and the
+ // parent is requested.
+ if (mod.submodules) {
+ d.push(r[i]);
+ }
+
+ hash[r[i]] = true;
+ m = this.getModule(r[i]);
+
+ if (m) {
+ add = this.getRequires(m);
+ intl = intl || (m.expanded_map &&
+ (INTL in m.expanded_map));
+ for (j = 0; j < add.length; j++) {
+ d.push(add[j]);
+ }
+ }
+ }
+ }
+ }
+
+ if (o && this.loadOptional) {
+ for (i = 0; i < o.length; i++) {
+ if (!hash[o[i]]) {
+ d.push(o[i]);
+ hash[o[i]] = true;
+ m = info[o[i]];
+ if (m) {
+ add = this.getRequires(m);
+ intl = intl || (m.expanded_map &&
+ (INTL in m.expanded_map));
+ for (j = 0; j < add.length; j++) {
+ d.push(add[j]);
+ }
+ }
+ }
+ }
+ }
+
+ cond = this.conditions[name];
+
+ if (cond) {
+ //Set the module to not parsed since we have conditionals and this could change the dependency tree.
+ mod._parsed = false;
+ if (testresults && ftests) {
+ oeach(testresults, function(result, id) {
+ var condmod = ftests[id].name;
+ if (!hash[condmod] && ftests[id].trigger == name) {
+ if (result && ftests[id]) {
+ hash[condmod] = true;
+ d.push(condmod);
+ }
+ }
+ });
+ } else {
+ for (i in cond) {
+ if (cond.hasOwnProperty(i)) {
+ if (!hash[i]) {
+ def = cond[i];
+ //first see if they've specfied a ua check
+ //then see if they've got a test fn & if it returns true
+ //otherwise just having a condition block is enough
+ go = def && ((!def.ua && !def.test) || (def.ua && Y.UA[def.ua]) ||
+ (def.test && def.test(Y, r)));
+
+ if (go) {
+ hash[i] = true;
+ d.push(i);
+ m = this.getModule(i);
+ if (m) {
+ add = this.getRequires(m);
+ for (j = 0; j < add.length; j++) {
+ d.push(add[j]);
+ }
+
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Create skin modules
+ if (mod.skinnable) {
+ skindef = this.skin.overrides;
+ for (i in YUI.Env.aliases) {
+ if (YUI.Env.aliases.hasOwnProperty(i)) {
+ if (Y.Array.indexOf(YUI.Env.aliases[i], name) > -1) {
+ skinpar = i;
+ }
+ }
+ }
+ if (skindef && (skindef[name] || (skinpar && skindef[skinpar]))) {
+ skinname = name;
+ if (skindef[skinpar]) {
+ skinname = skinpar;
+ }
+ for (i = 0; i < skindef[skinname].length; i++) {
+ skinmod = this._addSkin(skindef[skinname][i], name);
+ if (!this.isCSSLoaded(skinmod, this._boot)) {
+ d.push(skinmod);
+ }
+ }
+ } else {
+ skinmod = this._addSkin(this.skin.defaultSkin, name);
+ if (!this.isCSSLoaded(skinmod, this._boot)) {
+ d.push(skinmod);
+ }
+ }
+ }
+
+ mod._parsed = false;
+
+ if (intl) {
+
+ if (mod.lang && !mod.langPack && Y.Intl) {
+ lang = Y.Intl.lookupBestLang(this.lang || ROOT_LANG, mod.lang);
+ packName = this.getLangPackName(lang, name);
+ if (packName) {
+ d.unshift(packName);
+ }
+ }
+ d.unshift(INTL);
+ }
+
+ mod.expanded_map = YArray.hash(d);
+
+ mod.expanded = YObject.keys(mod.expanded_map);
+
+ return mod.expanded;
+ },
+ /**
+ * Check to see if named css module is already loaded on the page
+ * @method isCSSLoaded
+ * @param {String} name The name of the css file
+ * @return Boolean
+ */
+ isCSSLoaded: function(name, skip) {
+ //TODO - Make this call a batching call with name being an array
+ if (!name || !YUI.Env.cssStampEl || (!skip && this.ignoreRegistered)) {
+ return false;
+ }
+ var el = YUI.Env.cssStampEl,
+ ret = false,
+ mod = YUI.Env._cssLoaded[name],
+ style = el.currentStyle; //IE
+
+
+ if (mod !== undefined) {
+ return mod;
+ }
+
+ //Add the classname to the element
+ el.className = name;
+
+ if (!style) {
+ style = Y.config.doc.defaultView.getComputedStyle(el, null);
+ }
+
+ if (style && style.display === 'none') {
+ ret = true;
+ }
+
+
+ el.className = ''; //Reset the classname to ''
+
+ YUI.Env._cssLoaded[name] = ret;
+
+ return ret;
+ },
+
+ /**
+ * Returns a hash of module names the supplied module satisfies.
+ * @method getProvides
+ * @param {string} name The name of the module.
+ * @return {object} what this module provides.
+ */
+ getProvides: function(name) {
+ var m = this.getModule(name), o, s;
+ // supmap = this.provides;
+
+ if (!m) {
+ return NOT_FOUND;
+ }
+
+ if (m && !m.provides) {
+ o = {};
+ s = m.supersedes;
+
+ if (s) {
+ YArray.each(s, function(v) {
+ Y.mix(o, this.getProvides(v));
+ }, this);
+ }
+
+ o[name] = true;
+ m.provides = o;
+
+ }
+
+ return m.provides;
+ },
+
+ /**
+ * Calculates the dependency tree, the result is stored in the sorted
+ * property.
+ * @method calculate
+ * @param {object} o optional options object.
+ * @param {string} type optional argument to prune modules.
+ */
+ calculate: function(o, type) {
+ if (o || type || this.dirty) {
+
+ if (o) {
+ this._config(o);
+ }
+
+ if (!this._init) {
+ this._setup();
+ }
+
+ this._explode();
+
+ if (this.allowRollup) {
+ this._rollup();
+ } else {
+ this._explodeRollups();
+ }
+ this._reduce();
+ this._sort();
+ }
+ },
+ /**
+ * Creates a "psuedo" package for languages provided in the lang array
+ * @method _addLangPack
+ * @private
+ * @param {String} lang The language to create
+ * @param {Object} m The module definition to create the language pack around
+ * @param {String} packName The name of the package (e.g: lang/datatype-date-en-US)
+ * @return {Object} The module definition
+ */
+ _addLangPack: function(lang, m, packName) {
+ var name = m.name,
+ packPath, conf,
+ existing = this.moduleInfo[packName];
+
+ if (!existing) {
+
+ packPath = _path((m.pkg || name), packName, JS, true);
+
+ conf = {
+ path: packPath,
+ intl: true,
+ langPack: true,
+ ext: m.ext,
+ group: m.group,
+ supersedes: []
+ };
+ if (m.root) {
+ conf.root = m.root;
+ }
+ if (m.base) {
+ conf.base = m.base;
+ }
+
+ if (m.configFn) {
+ conf.configFn = m.configFn;
+ }
+
+ this.addModule(conf, packName);
+
+ if (lang) {
+ Y.Env.lang = Y.Env.lang || {};
+ Y.Env.lang[lang] = Y.Env.lang[lang] || {};
+ Y.Env.lang[lang][name] = true;
+ }
+ }
+
+ return this.moduleInfo[packName];
+ },
+
+ /**
+ * Investigates the current YUI configuration on the page. By default,
+ * modules already detected will not be loaded again unless a force
+ * option is encountered. Called by calculate()
+ * @method _setup
+ * @private
+ */
+ _setup: function() {
+ var info = this.moduleInfo, name, i, j, m, l,
+ packName;
+
+ for (name in info) {
+ if (info.hasOwnProperty(name)) {
+ m = info[name];
+ if (m) {
+
+ // remove dups
+ //m.requires = YObject.keys(YArray.hash(m.requires));
+ m.requires = YArray.dedupe(m.requires);
+
+ // Create lang pack modules
+ //if (m.lang && m.lang.length) {
+ if (m.lang) {
+ // Setup root package if the module has lang defined,
+ // it needs to provide a root language pack
+ packName = this.getLangPackName(ROOT_LANG, name);
+ this._addLangPack(null, m, packName);
+ }
+
+ }
+ }
+ }
+
+
+ //l = Y.merge(this.inserted);
+ l = {};
+
+ // available modules
+ if (!this.ignoreRegistered) {
+ Y.mix(l, GLOBAL_ENV.mods);
+ }
+
+ // add the ignore list to the list of loaded packages
+ if (this.ignore) {
+ Y.mix(l, YArray.hash(this.ignore));
+ }
+
+ // expand the list to include superseded modules
+ for (j in l) {
+ if (l.hasOwnProperty(j)) {
+ Y.mix(l, this.getProvides(j));
+ }
+ }
+
+ // remove modules on the force list from the loaded list
+ if (this.force) {
+ for (i = 0; i < this.force.length; i++) {
+ if (this.force[i] in l) {
+ delete l[this.force[i]];
+ }
+ }
+ }
+
+ Y.mix(this.loaded, l);
+
+ this._init = true;
+ },
+
+ /**
+ * Builds a module name for a language pack
+ * @method getLangPackName
+ * @param {string} lang the language code.
+ * @param {string} mname the module to build it for.
+ * @return {string} the language pack module name.
+ */
+ getLangPackName: function(lang, mname) {
+ return ('lang/' + mname + ((lang) ? '_' + lang : ''));
+ },
+ /**
+ * Inspects the required modules list looking for additional
+ * dependencies. Expands the required list to include all
+ * required modules. Called by calculate()
+ * @method _explode
+ * @private
+ */
+ _explode: function() {
+ //TODO Move done out of scope
+ var r = this.required, m, reqs, done = {},
+ self = this, name;
+
+ // the setup phase is over, all modules have been created
+ self.dirty = false;
+
+ self._explodeRollups();
+ r = self.required;
+
+ for (name in r) {
+ if (r.hasOwnProperty(name)) {
+ if (!done[name]) {
+ done[name] = true;
+ m = self.getModule(name);
+ if (m) {
+ var expound = m.expound;
+
+ if (expound) {
+ r[expound] = self.getModule(expound);
+ reqs = self.getRequires(r[expound]);
+ Y.mix(r, YArray.hash(reqs));
+ }
+
+ reqs = self.getRequires(m);
+ Y.mix(r, YArray.hash(reqs));
+ }
+ }
+ }
+ }
+
+ },
+ /**
+ * The default method used to test a module against a pattern
+ * @method _patternTest
+ * @private
+ * @param {String} mname The module being tested
+ * @param {String} pname The pattern to match
+ */
+ _patternTest: function(mname, pname) {
+ return (mname.indexOf(pname) > -1);
+ },
+ /**
+ * Get's the loader meta data for the requested module
+ * @method getModule
+ * @param {String} mname The module name to get
+ * @return {Object} The module metadata
+ */
+ getModule: function(mname) {
+ //TODO: Remove name check - it's a quick hack to fix pattern WIP
+ if (!mname) {
+ return null;
+ }
+
+ var p, found, pname,
+ m = this.moduleInfo[mname],
+ patterns = this.patterns;
+
+ // check the patterns library to see if we should automatically add
+ // the module with defaults
+ if (!m || (m && m.ext)) {
+ for (pname in patterns) {
+ if (patterns.hasOwnProperty(pname)) {
+ p = patterns[pname];
+
+ //There is no test method, create a default one that tests
+ // the pattern against the mod name
+ if (!p.test) {
+ p.test = this._patternTest;
+ }
+
+ if (p.test(mname, pname)) {
+ // use the metadata supplied for the pattern
+ // as the module definition.
+ found = p;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!m) {
+ if (found) {
+ if (p.action) {
+ p.action.call(this, mname, pname);
+ } else {
+ // ext true or false?
+ m = this.addModule(Y.merge(found), mname);
+ if (found.configFn) {
+ m.configFn = found.configFn;
+ }
+ m.temp = true;
+ }
+ }
+ } else {
+ if (found && m && found.configFn && !m.configFn) {
+ m.configFn = found.configFn;
+ m.configFn(m);
+ }
+ }
+
+ return m;
+ },
+
+ // impl in rollup submodule
+ _rollup: function() { },
+
+ /**
+ * Remove superceded modules and loaded modules. Called by
+ * calculate() after we have the mega list of all dependencies
+ * @method _reduce
+ * @return {object} the reduced dependency hash.
+ * @private
+ */
+ _reduce: function(r) {
+
+ r = r || this.required;
+
+ var i, j, s, m, type = this.loadType,
+ ignore = this.ignore ? YArray.hash(this.ignore) : false;
+
+ for (i in r) {
+ if (r.hasOwnProperty(i)) {
+ m = this.getModule(i);
+ // remove if already loaded
+ if (((this.loaded[i] || ON_PAGE[i]) &&
+ !this.forceMap[i] && !this.ignoreRegistered) ||
+ (type && m && m.type != type)) {
+ delete r[i];
+ }
+ if (ignore && ignore[i]) {
+ delete r[i];
+ }
+ // remove anything this module supersedes
+ s = m && m.supersedes;
+ if (s) {
+ for (j = 0; j < s.length; j++) {
+ if (s[j] in r) {
+ delete r[s[j]];
+ }
+ }
+ }
+ }
+ }
+
+ return r;
+ },
+ /**
+ * Handles the queue when a module has been loaded for all cases
+ * @method _finish
+ * @private
+ * @param {String} msg The message from Loader
+ * @param {Boolean} success A boolean denoting success or failure
+ */
+ _finish: function(msg, success) {
+
+ _queue.running = false;
+
+ var onEnd = this.onEnd;
+ if (onEnd) {
+ onEnd.call(this.context, {
+ msg: msg,
+ data: this.data,
+ success: success
+ });
+ }
+ this._continue();
+ },
+ /**
+ * The default Loader onSuccess handler, calls this.onSuccess with a payload
+ * @method _onSuccess
+ * @private
+ */
+ _onSuccess: function() {
+ var self = this, skipped = Y.merge(self.skipped), fn,
+ failed = [], rreg = self.requireRegistration,
+ success, msg, i, mod;
+
+ for (i in skipped) {
+ if (skipped.hasOwnProperty(i)) {
+ delete self.inserted[i];
+ }
+ }
+
+ self.skipped = {};
+
+ for (i in self.inserted) {
+ if (self.inserted.hasOwnProperty(i)) {
+ mod = self.getModule(i);
+ if (mod && rreg && mod.type == JS && !(i in YUI.Env.mods)) {
+ failed.push(i);
+ } else {
+ Y.mix(self.loaded, self.getProvides(i));
+ }
+ }
+ }
+
+ fn = self.onSuccess;
+ msg = (failed.length) ? 'notregistered' : 'success';
+ success = !(failed.length);
+ if (fn) {
+ fn.call(self.context, {
+ msg: msg,
+ data: self.data,
+ success: success,
+ failed: failed,
+ skipped: skipped
+ });
+ }
+ self._finish(msg, success);
+ },
+ /**
+ * The default Loader onProgress handler, calls this.onProgress with a payload
+ * @method _onProgress
+ * @private
+ */
+ _onProgress: function(e) {
+ var self = this;
+ if (self.onProgress) {
+ self.onProgress.call(self.context, {
+ name: e.url,
+ data: e.data
+ });
+ }
+ },
+ /**
+ * The default Loader onFailure handler, calls this.onFailure with a payload
+ * @method _onFailure
+ * @private
+ */
+ _onFailure: function(o) {
+ var f = this.onFailure, msg = [], i = 0, len = o.errors.length;
+
+ for (i; i < len; i++) {
+ msg.push(o.errors[i].error);
+ }
+
+ msg = msg.join(',');
+
+
+ if (f) {
+ f.call(this.context, {
+ msg: msg,
+ data: this.data,
+ success: false
+ });
+ }
+
+ this._finish(msg, false);
+
+ },
+
+ /**
+ * The default Loader onTimeout handler, calls this.onTimeout with a payload
+ * @method _onTimeout
+ * @private
+ */
+ _onTimeout: function() {
+ var f = this.onTimeout;
+ if (f) {
+ f.call(this.context, {
+ msg: 'timeout',
+ data: this.data,
+ success: false
+ });
+ }
+ },
+
+ /**
+ * Sorts the dependency tree. The last step of calculate()
+ * @method _sort
+ * @private
+ */
+ _sort: function() {
+
+ // create an indexed list
+ var s = YObject.keys(this.required),
+ // loaded = this.loaded,
+ //TODO Move this out of scope
+ done = {},
+ p = 0, l, a, b, j, k, moved, doneKey;
+
+ // keep going until we make a pass without moving anything
+ for (;;) {
+
+ l = s.length;
+ moved = false;
+
+ // start the loop after items that are already sorted
+ for (j = p; j < l; j++) {
+
+ // check the next module on the list to see if its
+ // dependencies have been met
+ a = s[j];
+
+ // check everything below current item and move if we
+ // find a requirement for the current item
+ for (k = j + 1; k < l; k++) {
+ doneKey = a + s[k];
+
+ if (!done[doneKey] && this._requires(a, s[k])) {
+
+ // extract the dependency so we can move it up
+ b = s.splice(k, 1);
+
+ // insert the dependency above the item that
+ // requires it
+ s.splice(j, 0, b[0]);
+
+ // only swap two dependencies once to short circut
+ // circular dependencies
+ done[doneKey] = true;
+
+ // keep working
+ moved = true;
+
+ break;
+ }
+ }
+
+ // jump out of loop if we moved something
+ if (moved) {
+ break;
+ // this item is sorted, move our pointer and keep going
+ } else {
+ p++;
+ }
+ }
+
+ // when we make it here and moved is false, we are
+ // finished sorting
+ if (!moved) {
+ break;
+ }
+
+ }
+
+ this.sorted = s;
+ },
+
+ /**
+ * Handles the actual insertion of script/link tags
+ * @method _insert
+ * @private
+ * @param {Object} source The YUI instance the request came from
+ * @param {Object} o The metadata to include
+ * @param {String} type JS or CSS
+ * @param {Boolean} [skipcalc=false] Do a Loader.calculate on the meta
+ */
+ _insert: function(source, o, type, skipcalc) {
+
+
+ // restore the state at the time of the request
+ if (source) {
+ this._config(source);
+ }
+
+ // build the dependency list
+ // don't include type so we can process CSS and script in
+ // one pass when the type is not specified.
+ if (!skipcalc) {
+ //this.calculate(o);
+ }
+
+ var modules = this.resolve(!skipcalc),
+ self = this, comp = 0, actions = 0;
+
+ if (type) {
+ //Filter out the opposite type and reset the array so the checks later work
+ modules[((type === JS) ? CSS : JS)] = [];
+ }
+ if (modules.js.length) {
+ comp++;
+ }
+ if (modules.css.length) {
+ comp++;
+ }
+
+ //console.log('Resolved Modules: ', modules);
+
+ var complete = function(d) {
+ actions++;
+ var errs = {}, i = 0, u = '', fn;
+
+ if (d && d.errors) {
+ for (i = 0; i < d.errors.length; i++) {
+ if (d.errors[i].request) {
+ u = d.errors[i].request.url;
+ } else {
+ u = d.errors[i];
+ }
+ errs[u] = u;
+ }
+ }
+
+ if (d && d.data && d.data.length && (d.type === 'success')) {
+ for (i = 0; i < d.data.length; i++) {
+ self.inserted[d.data[i].name] = true;
+ }
+ }
+
+ if (actions === comp) {
+ self._loading = null;
+ if (d && d.fn) {
+ fn = d.fn;
+ delete d.fn;
+ fn.call(self, d);
+ }
+ }
+ };
+
+ this._loading = true;
+
+ if (!modules.js.length && !modules.css.length) {
+ actions = -1;
+ complete({
+ fn: self._onSuccess
+ });
+ return;
+ }
+
+
+ if (modules.css.length) { //Load CSS first
+ Y.Get.css(modules.css, {
+ data: modules.cssMods,
+ attributes: self.cssAttributes,
+ insertBefore: self.insertBefore,
+ charset: self.charset,
+ timeout: self.timeout,
+ context: self,
+ onProgress: function(e) {
+ self._onProgress.call(self, e);
+ },
+ onTimeout: function(d) {
+ self._onTimeout.call(self, d);
+ },
+ onSuccess: function(d) {
+ d.type = 'success';
+ d.fn = self._onSuccess;
+ complete.call(self, d);
+ },
+ onFailure: function(d) {
+ d.type = 'failure';
+ d.fn = self._onFailure;
+ complete.call(self, d);
+ }
+ });
+ }
+
+ if (modules.js.length) {
+ Y.Get.js(modules.js, {
+ data: modules.jsMods,
+ insertBefore: self.insertBefore,
+ attributes: self.jsAttributes,
+ charset: self.charset,
+ timeout: self.timeout,
+ autopurge: false,
+ context: self,
+ async: self.async,
+ onProgress: function(e) {
+ self._onProgress.call(self, e);
+ },
+ onTimeout: function(d) {
+ self._onTimeout.call(self, d);
+ },
+ onSuccess: function(d) {
+ d.type = 'success';
+ d.fn = self._onSuccess;
+ complete.call(self, d);
+ },
+ onFailure: function(d) {
+ d.type = 'failure';
+ d.fn = self._onFailure;
+ complete.call(self, d);
+ }
+ });
+ }
+ },
+ /**
+ * Once a loader operation is completely finished, process any additional queued items.
+ * @method _continue
+ * @private
+ */
+ _continue: function() {
+ if (!(_queue.running) && _queue.size() > 0) {
+ _queue.running = true;
+ _queue.next()();
+ }
+ },
+
+ /**
+ * inserts the requested modules and their dependencies.
+ * <code>type</code> can be "js" or "css". Both script and
+ * css are inserted if type is not provided.
+ * @method insert
+ * @param {object} o optional options object.
+ * @param {string} type the type of dependency to insert.
+ */
+ insert: function(o, type, skipsort) {
+ var self = this, copy = Y.merge(this);
+ delete copy.require;
+ delete copy.dirty;
+ _queue.add(function() {
+ self._insert(copy, o, type, skipsort);
+ });
+ this._continue();
+ },
+
+ /**
+ * Executed every time a module is loaded, and if we are in a load
+ * cycle, we attempt to load the next script. Public so that it
+ * is possible to call this if using a method other than
+ * Y.register to determine when scripts are fully loaded
+ * @method loadNext
+ * @deprecated
+ * @param {string} mname optional the name of the module that has
+ * been loaded (which is usually why it is time to load the next
+ * one).
+ */
+ loadNext: function(mname) {
+ return;
+ },
+
+ /**
+ * Apply filter defined for this instance to a url/path
+ * @method _filter
+ * @param {string} u the string to filter.
+ * @param {string} name the name of the module, if we are processing
+ * a single module as opposed to a combined url.
+ * @return {string} the filtered string.
+ * @private
+ */
+ _filter: function(u, name, group) {
+ var f = this.filter,
+ hasFilter = name && (name in this.filters),
+ modFilter = hasFilter && this.filters[name],
+ groupName = group || (this.moduleInfo[name] ? this.moduleInfo[name].group : null);
+
+ if (groupName && this.groups[groupName] && this.groups[groupName].filter) {
+ modFilter = this.groups[groupName].filter;
+ hasFilter = true;
+ }
+
+ if (u) {
+ if (hasFilter) {
+ f = (L.isString(modFilter)) ? this.FILTER_DEFS[modFilter.toUpperCase()] || null : modFilter;
+ }
+ if (f) {
+ u = u.replace(new RegExp(f.searchExp, 'g'), f.replaceStr);
+ }
+ }
+ return u;
+ },
+
+ /**
+ * Generates the full url for a module
+ * @method _url
+ * @param {string} path the path fragment.
+ * @param {String} name The name of the module
+ * @param {String} [base=self.base] The base url to use
+ * @return {string} the full url.
+ * @private
+ */
+ _url: function(path, name, base) {
+ return this._filter((base || this.base || '') + path, name);
+ },
+ /**
+ * Returns an Object hash of file arrays built from `loader.sorted` or from an arbitrary list of sorted modules.
+ * @method resolve
+ * @param {Boolean} [calc=false] Perform a loader.calculate() before anything else
+ * @param {Array} [s=loader.sorted] An override for the loader.sorted array
+ * @return {Object} Object hash (js and css) of two arrays of file lists
+ * @example This method can be used as an off-line dep calculator
+ *
+ * var Y = YUI();
+ * var loader = new Y.Loader({
+ * filter: 'debug',
+ * base: '../../',
+ * root: 'build/',
+ * combine: true,
+ * require: ['node', 'dd', 'console']
+ * });
+ * var out = loader.resolve(true);
+ *
+ */
+ resolve: function(calc, s) {
+
+ var len, i, m, url, fn, msg, attr, group, groupName, j, frag,
+ comboSource, comboSources, mods, comboBase,
+ base, urls, u = [], tmpBase, baseLen, resCombos = {},
+ self = this, comboSep, maxURLLength, singles = [],
+ inserted = (self.ignoreRegistered) ? {} : self.inserted,
+ resolved = { js: [], jsMods: [], css: [], cssMods: [] },
+ type = self.loadType || 'js';
+
+ if (self.skin.overrides || self.skin.defaultSkin !== DEFAULT_SKIN || self.ignoreRegistered) {
+ self._resetModules();
+ }
+
+ if (calc) {
+ self.calculate();
+ }
+ s = s || self.sorted;
+
+ var addSingle = function(m) {
+
+ if (m) {
+ group = (m.group && self.groups[m.group]) || NOT_FOUND;
+
+ //Always assume it's async
+ if (group.async === false) {
+ m.async = group.async;
+ }
+
+ url = (m.fullpath) ? self._filter(m.fullpath, s[i]) :
+ self._url(m.path, s[i], group.base || m.base);
+
+ if (m.attributes || m.async === false) {
+ url = {
+ url: url,
+ async: m.async
+ };
+ if (m.attributes) {
+ url.attributes = m.attributes;
+ }
+ }
+ resolved[m.type].push(url);
+ resolved[m.type + 'Mods'].push(m);
+ } else {
+ }
+
+ };
+
+ len = s.length;
+
+ // the default combo base
+ comboBase = self.comboBase;
+
+ url = comboBase;
+
+ comboSources = {};
+
+ for (i = 0; i < len; i++) {
+ comboSource = comboBase;
+ m = self.getModule(s[i]);
+ groupName = m && m.group;
+ group = self.groups[groupName];
+ if (groupName && group) {
+
+ if (!group.combine || m.fullpath) {
+ //This is not a combo module, skip it and load it singly later.
+ //singles.push(s[i]);
+ addSingle(m);
+ continue;
+ }
+ m.combine = true;
+ if (group.comboBase) {
+ comboSource = group.comboBase;
+ }
+
+ if ("root" in group && L.isValue(group.root)) {
+ m.root = group.root;
+ }
+ m.comboSep = group.comboSep || self.comboSep;
+ m.maxURLLength = group.maxURLLength || self.maxURLLength;
+ } else {
+ if (!self.combine) {
+ //This is not a combo module, skip it and load it singly later.
+ //singles.push(s[i]);
+ addSingle(m);
+ continue;
+ }
+ }
+
+ comboSources[comboSource] = comboSources[comboSource] || [];
+ comboSources[comboSource].push(m);
+ }
+
+ for (j in comboSources) {
+ if (comboSources.hasOwnProperty(j)) {
+ resCombos[j] = resCombos[j] || { js: [], jsMods: [], css: [], cssMods: [] };
+ url = j;
+ mods = comboSources[j];
+ len = mods.length;
+
+ if (len) {
+ for (i = 0; i < len; i++) {
+ if (inserted[mods[i]]) {
+ continue;
+ }
+ m = mods[i];
+ // Do not try to combine non-yui JS unless combo def
+ // is found
+ if (m && (m.combine || !m.ext)) {
+ resCombos[j].comboSep = m.comboSep;
+ resCombos[j].group = m.group;
+ resCombos[j].maxURLLength = m.maxURLLength;
+ frag = ((L.isValue(m.root)) ? m.root : self.root) + (m.path || m.fullpath);
+ frag = self._filter(frag, m.name);
+ resCombos[j][m.type].push(frag);
+ resCombos[j][m.type + 'Mods'].push(m);
+ } else {
+ //Add them to the next process..
+ if (mods[i]) {
+ //singles.push(mods[i].name);
+ addSingle(mods[i]);
+ }
+ }
+
+ }
+ }
+ }
+ }
+
+
+ for (j in resCombos) {
+ base = j;
+ comboSep = resCombos[base].comboSep || self.comboSep;
+ maxURLLength = resCombos[base].maxURLLength || self.maxURLLength;
+ for (type in resCombos[base]) {
+ if (type === JS || type === CSS) {
+ urls = resCombos[base][type];
+ mods = resCombos[base][type + 'Mods'];
+ len = urls.length;
+ tmpBase = base + urls.join(comboSep);
+ baseLen = tmpBase.length;
+ if (maxURLLength <= base.length) {
+ maxURLLength = MAX_URL_LENGTH;
+ }
+
+ if (len) {
+ if (baseLen > maxURLLength) {
+ u = [];
+ for (s = 0; s < len; s++) {
+ u.push(urls[s]);
+ tmpBase = base + u.join(comboSep);
+
+ if (tmpBase.length > maxURLLength) {
+ m = u.pop();
+ tmpBase = base + u.join(comboSep);
+ resolved[type].push(self._filter(tmpBase, null, resCombos[base].group));
+ u = [];
+ if (m) {
+ u.push(m);
+ }
+ }
+ }
+ if (u.length) {
+ tmpBase = base + u.join(comboSep);
+ resolved[type].push(self._filter(tmpBase, null, resCombos[base].group));
+ }
+ } else {
+ resolved[type].push(self._filter(tmpBase, null, resCombos[base].group));
+ }
+ }
+ resolved[type + 'Mods'] = resolved[type + 'Mods'].concat(mods);
+ }
+ }
+ }
+
+ resCombos = null;
+
+ return resolved;
+ },
+ /**
+ Shortcut to calculate, resolve and load all modules.
+
+ var loader = new Y.Loader({
+ ignoreRegistered: true,
+ modules: {
+ mod: {
+ path: 'mod.js'
+ }
+ },
+ requires: [ 'mod' ]
+ });
+ loader.load(function() {
+ console.log('All modules have loaded..');
+ });
+
+
+ @method load
+ @param {Callback} cb Executed after all load operations are complete
+ */
+ load: function(cb) {
+ if (!cb) {
+ return;
+ }
+ var self = this,
+ out = self.resolve(true);
+
+ self.data = out;
+
+ self.onEnd = function() {
+ cb.apply(self.context || self, arguments);
+ };
+
+ self.insert();
+ }
+};
+
+
+
+}, '@VERSION@' ,{requires:['get', 'features']});
+YUI.add('loader-rollup', function(Y) {
+
+/**
+ * Optional automatic rollup logic for reducing http connections
+ * when not using a combo service.
+ * @module loader
+ * @submodule rollup
+ */
+
+/**
+ * Look for rollup packages to determine if all of the modules a
+ * rollup supersedes are required. If so, include the rollup to
+ * help reduce the total number of connections required. Called
+ * by calculate(). This is an optional feature, and requires the
+ * appropriate submodule to function.
+ * @method _rollup
+ * @for Loader
+ * @private
+ */
+Y.Loader.prototype._rollup = function() {
+ var i, j, m, s, r = this.required, roll,
+ info = this.moduleInfo, rolled, c, smod;
+
+ // find and cache rollup modules
+ if (this.dirty || !this.rollups) {
+ this.rollups = {};
+ for (i in info) {
+ if (info.hasOwnProperty(i)) {
+ m = this.getModule(i);
+ // if (m && m.rollup && m.supersedes) {
+ if (m && m.rollup) {
+ this.rollups[i] = m;
+ }
+ }
+ }
+ }
+
+ // make as many passes as needed to pick up rollup rollups
+ for (;;) {
+ rolled = false;
+
+ // go through the rollup candidates
+ for (i in this.rollups) {
+ if (this.rollups.hasOwnProperty(i)) {
+ // there can be only one, unless forced
+ if (!r[i] && ((!this.loaded[i]) || this.forceMap[i])) {
+ m = this.getModule(i);
+ s = m.supersedes || [];
+ roll = false;
+
+ // @TODO remove continue
+ if (!m.rollup) {
+ continue;
+ }
+
+ c = 0;
+
+ // check the threshold
+ for (j = 0; j < s.length; j++) {
+ smod = info[s[j]];
+
+ // if the superseded module is loaded, we can't
+ // load the rollup unless it has been forced.
+ if (this.loaded[s[j]] && !this.forceMap[s[j]]) {
+ roll = false;
+ break;
+ // increment the counter if this module is required.
+ // if we are beyond the rollup threshold, we will
+ // use the rollup module
+ } else if (r[s[j]] && m.type == smod.type) {
+ c++;
+ roll = (c >= m.rollup);
+ if (roll) {
+ break;
+ }
+ }
+ }
+
+ if (roll) {
+ // add the rollup
+ r[i] = true;
+ rolled = true;
+
+ // expand the rollup's dependencies
+ this.getRequires(m);
+ }
+ }
+ }
+ }
+
+ // if we made it here w/o rolling up something, we are done
+ if (!rolled) {
+ break;
+ }
+ }
+};
+
+
+}, '@VERSION@' ,{requires:['loader-base']});
+YUI.add('loader-yui3', function(Y) {
+
+/* This file is auto-generated by src/loader/scripts/meta_join.js */
+
+/**
+ * YUI 3 module metadata
+ * @module loader
+ * @submodule yui3
+ */
+YUI.Env[Y.version].modules = YUI.Env[Y.version].modules || {
+ "align-plugin": {
+ "requires": [
+ "node-screen",
+ "node-pluginhost"
+ ]
+ },
+ "anim": {
+ "use": [
+ "anim-base",
+ "anim-color",
+ "anim-curve",
+ "anim-easing",
+ "anim-node-plugin",
+ "anim-scroll",
+ "anim-xy"
+ ]
+ },
+ "anim-base": {
+ "requires": [
+ "base-base",
+ "node-style"
+ ]
+ },
+ "anim-color": {
+ "requires": [
+ "anim-base"
+ ]
+ },
+ "anim-curve": {
+ "requires": [
+ "anim-xy"
+ ]
+ },
+ "anim-easing": {
+ "requires": [
+ "anim-base"
+ ]
+ },
+ "anim-node-plugin": {
+ "requires": [
+ "node-pluginhost",
+ "anim-base"
+ ]
+ },
+ "anim-scroll": {
+ "requires": [
+ "anim-base"
+ ]
+ },
+ "anim-shape-transform": {
+ "requires": [
+ "anim-base",
+ "anim-easing",
+ "matrix"
+ ]
+ },
+ "anim-xy": {
+ "requires": [
+ "anim-base",
+ "node-screen"
+ ]
+ },
+ "app": {
+ "use": [
+ "app-base",
+ "app-transitions",
+ "lazy-model-list",
+ "model",
+ "model-list",
+ "model-sync-rest",
+ "router",
+ "view",
+ "view-node-map"
+ ]
+ },
+ "app-base": {
+ "requires": [
+ "classnamemanager",
+ "pjax-base",
+ "router",
+ "view"
+ ]
+ },
+ "app-transitions": {
+ "requires": [
+ "app-base"
+ ]
+ },
+ "app-transitions-css": {
+ "type": "css"
+ },
+ "app-transitions-native": {
+ "condition": {
+ "name": "app-transitions-native",
+ "test": function (Y) {
+ var doc = Y.config.doc,
+ node = doc ? doc.documentElement : null;
+
+ if (node && node.style) {
+ return ('MozTransition' in node.style || 'WebkitTransition' in node.style);
+ }
+
+ return false;
+},
+ "trigger": "app-transitions"
+ },
+ "requires": [
+ "app-transitions",
+ "app-transitions-css",
+ "parallel",
+ "transition"
+ ]
+ },
+ "array-extras": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "array-invoke": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "arraylist": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "arraylist-add": {
+ "requires": [
+ "arraylist"
+ ]
+ },
+ "arraylist-filter": {
+ "requires": [
+ "arraylist"
+ ]
+ },
+ "arraysort": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "async-queue": {
+ "requires": [
+ "event-custom"
+ ]
+ },
+ "attribute": {
+ "use": [
+ "attribute-base",
+ "attribute-complex"
+ ]
+ },
+ "attribute-base": {
+ "requires": [
+ "attribute-core",
+ "attribute-events",
+ "attribute-extras"
+ ]
+ },
+ "attribute-complex": {
+ "requires": [
+ "attribute-base"
+ ]
+ },
+ "attribute-core": {
+ "requires": [
+ "oop"
+ ]
+ },
+ "attribute-events": {
+ "requires": [
+ "event-custom"
+ ]
+ },
+ "attribute-extras": {
+ "requires": [
+ "oop"
+ ]
+ },
+ "autocomplete": {
+ "use": [
+ "autocomplete-base",
+ "autocomplete-sources",
+ "autocomplete-list",
+ "autocomplete-plugin"
+ ]
+ },
+ "autocomplete-base": {
+ "optional": [
+ "autocomplete-sources"
+ ],
+ "requires": [
+ "array-extras",
+ "base-build",
+ "escape",
+ "event-valuechange",
+ "node-base"
+ ]
+ },
+ "autocomplete-filters": {
+ "requires": [
+ "array-extras",
+ "text-wordbreak"
+ ]
+ },
+ "autocomplete-filters-accentfold": {
+ "requires": [
+ "array-extras",
+ "text-accentfold",
+ "text-wordbreak"
+ ]
+ },
+ "autocomplete-highlighters": {
+ "requires": [
+ "array-extras",
+ "highlight-base"
+ ]
+ },
+ "autocomplete-highlighters-accentfold": {
+ "requires": [
+ "array-extras",
+ "highlight-accentfold"
+ ]
+ },
+ "autocomplete-list": {
+ "after": [
+ "autocomplete-sources"
+ ],
+ "lang": [
+ "en"
+ ],
+ "requires": [
+ "autocomplete-base",
+ "event-resize",
+ "node-screen",
+ "selector-css3",
+ "shim-plugin",
+ "widget",
+ "widget-position",
+ "widget-position-align"
+ ],
+ "skinnable": true
+ },
+ "autocomplete-list-keys": {
+ "condition": {
+ "name": "autocomplete-list-keys",
+ "test": function (Y) {
+ // Only add keyboard support to autocomplete-list if this doesn't appear to
+ // be an iOS or Android-based mobile device.
+ //
+ // There's currently no feasible way to actually detect whether a device has
+ // a hardware keyboard, so this sniff will have to do. It can easily be
+ // overridden by manually loading the autocomplete-list-keys module.
+ //
+ // Worth noting: even though iOS supports bluetooth keyboards, Mobile Safari
+ // doesn't fire the keyboard events used by AutoCompleteList, so there's
+ // no point loading the -keys module even when a bluetooth keyboard may be
+ // available.
+ return !(Y.UA.ios || Y.UA.android);
+},
+ "trigger": "autocomplete-list"
+ },
+ "requires": [
+ "autocomplete-list",
+ "base-build"
+ ]
+ },
+ "autocomplete-plugin": {
+ "requires": [
+ "autocomplete-list",
+ "node-pluginhost"
+ ]
+ },
+ "autocomplete-sources": {
+ "optional": [
+ "io-base",
+ "json-parse",
+ "jsonp",
+ "yql"
+ ],
+ "requires": [
+ "autocomplete-base"
+ ]
+ },
+ "base": {
+ "use": [
+ "base-base",
+ "base-pluginhost",
+ "base-build"
+ ]
+ },
+ "base-base": {
+ "after": [
+ "attribute-complex"
+ ],
+ "requires": [
+ "base-core",
+ "attribute-base"
+ ]
+ },
+ "base-build": {
+ "requires": [
+ "base-base"
+ ]
+ },
+ "base-core": {
+ "requires": [
+ "attribute-core"
+ ]
+ },
+ "base-pluginhost": {
+ "requires": [
+ "base-base",
+ "pluginhost"
+ ]
+ },
+ "button": {
+ "requires": [
+ "button-core",
+ "cssbutton",
+ "widget"
+ ]
+ },
+ "button-core": {
+ "requires": [
+ "attribute-core",
+ "classnamemanager",
+ "node-base"
+ ]
+ },
+ "button-group": {
+ "requires": [
+ "button-plugin",
+ "cssbutton",
+ "widget"
+ ]
+ },
+ "button-plugin": {
+ "requires": [
+ "button-core",
+ "cssbutton",
+ "node-pluginhost"
+ ]
+ },
+ "cache": {
+ "use": [
+ "cache-base",
+ "cache-offline",
+ "cache-plugin"
+ ]
+ },
+ "cache-base": {
+ "requires": [
+ "base"
+ ]
+ },
+ "cache-offline": {
+ "requires": [
+ "cache-base",
+ "json"
+ ]
+ },
+ "cache-plugin": {
+ "requires": [
+ "plugin",
+ "cache-base"
+ ]
+ },
+ "calendar": {
+ "lang": [
+ "de",
+ "en",
+ "fr",
+ "ja",
+ "nb-NO",
+ "pt-BR",
+ "ru",
+ "zh-HANT-TW"
+ ],
+ "requires": [
+ "calendar-base",
+ "calendarnavigator"
+ ],
+ "skinnable": true
+ },
+ "calendar-base": {
+ "lang": [
+ "de",
+ "en",
+ "fr",
+ "ja",
+ "nb-NO",
+ "pt-BR",
+ "ru",
+ "zh-HANT-TW"
+ ],
+ "requires": [
+ "widget",
+ "substitute",
+ "datatype-date",
+ "datatype-date-math",
+ "cssgrids"
+ ],
+ "skinnable": true
+ },
+ "calendarnavigator": {
+ "requires": [
+ "plugin",
+ "classnamemanager",
+ "datatype-date",
+ "node",
+ "substitute"
+ ],
+ "skinnable": true
+ },
+ "charts": {
+ "requires": [
+ "charts-base"
+ ]
+ },
+ "charts-base": {
+ "requires": [
+ "dom",
+ "datatype-number",
+ "datatype-date",
+ "event-custom",
+ "event-mouseenter",
+ "event-touch",
+ "widget",
+ "widget-position",
+ "widget-stack",
+ "graphics"
+ ]
+ },
+ "charts-legend": {
+ "requires": [
+ "charts-base"
+ ]
+ },
+ "classnamemanager": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "clickable-rail": {
+ "requires": [
+ "slider-base"
+ ]
+ },
+ "collection": {
+ "use": [
+ "array-extras",
+ "arraylist",
+ "arraylist-add",
+ "arraylist-filter",
+ "array-invoke"
+ ]
+ },
+ "console": {
+ "lang": [
+ "en",
+ "es",
+ "ja"
+ ],
+ "requires": [
+ "yui-log",
+ "widget",
+ "substitute"
+ ],
+ "skinnable": true
+ },
+ "console-filters": {
+ "requires": [
+ "plugin",
+ "console"
+ ],
+ "skinnable": true
+ },
+ "controller": {
+ "use": [
+ "router"
+ ]
+ },
+ "cookie": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "createlink-base": {
+ "requires": [
+ "editor-base"
+ ]
+ },
+ "cssbase": {
+ "after": [
+ "cssreset",
+ "cssfonts",
+ "cssgrids",
+ "cssreset-context",
+ "cssfonts-context",
+ "cssgrids-context"
+ ],
+ "type": "css"
+ },
+ "cssbase-context": {
+ "after": [
+ "cssreset",
+ "cssfonts",
+ "cssgrids",
+ "cssreset-context",
+ "cssfonts-context",
+ "cssgrids-context"
+ ],
+ "type": "css"
+ },
+ "cssbutton": {
+ "type": "css"
+ },
+ "cssfonts": {
+ "type": "css"
+ },
+ "cssfonts-context": {
+ "type": "css"
+ },
+ "cssgrids": {
+ "optional": [
+ "cssreset",
+ "cssfonts"
+ ],
+ "type": "css"
+ },
+ "cssgrids-base": {
+ "optional": [
+ "cssreset",
+ "cssfonts"
+ ],
+ "type": "css"
+ },
+ "cssgrids-units": {
+ "optional": [
+ "cssreset",
+ "cssfonts"
+ ],
+ "requires": [
+ "cssgrids-base"
+ ],
+ "type": "css"
+ },
+ "cssreset": {
+ "type": "css"
+ },
+ "cssreset-context": {
+ "type": "css"
+ },
+ "dataschema": {
+ "use": [
+ "dataschema-base",
+ "dataschema-json",
+ "dataschema-xml",
+ "dataschema-array",
+ "dataschema-text"
+ ]
+ },
+ "dataschema-array": {
+ "requires": [
+ "dataschema-base"
+ ]
+ },
+ "dataschema-base": {
+ "requires": [
+ "base"
+ ]
+ },
+ "dataschema-json": {
+ "requires": [
+ "dataschema-base",
+ "json"
+ ]
+ },
+ "dataschema-text": {
+ "requires": [
+ "dataschema-base"
+ ]
+ },
+ "dataschema-xml": {
+ "requires": [
+ "dataschema-base"
+ ]
+ },
+ "datasource": {
+ "use": [
+ "datasource-local",
+ "datasource-io",
+ "datasource-get",
+ "datasource-function",
+ "datasource-cache",
+ "datasource-jsonschema",
+ "datasource-xmlschema",
+ "datasource-arrayschema",
+ "datasource-textschema",
+ "datasource-polling"
+ ]
+ },
+ "datasource-arrayschema": {
+ "requires": [
+ "datasource-local",
+ "plugin",
+ "dataschema-array"
+ ]
+ },
+ "datasource-cache": {
+ "requires": [
+ "datasource-local",
+ "plugin",
+ "cache-base"
+ ]
+ },
+ "datasource-function": {
+ "requires": [
+ "datasource-local"
+ ]
+ },
+ "datasource-get": {
+ "requires": [
+ "datasource-local",
+ "get"
+ ]
+ },
+ "datasource-io": {
+ "requires": [
+ "datasource-local",
+ "io-base"
+ ]
+ },
+ "datasource-jsonschema": {
+ "requires": [
+ "datasource-local",
+ "plugin",
+ "dataschema-json"
+ ]
+ },
+ "datasource-local": {
+ "requires": [
+ "base"
+ ]
+ },
+ "datasource-polling": {
+ "requires": [
+ "datasource-local"
+ ]
+ },
+ "datasource-textschema": {
+ "requires": [
+ "datasource-local",
+ "plugin",
+ "dataschema-text"
+ ]
+ },
+ "datasource-xmlschema": {
+ "requires": [
+ "datasource-local",
+ "plugin",
+ "dataschema-xml"
+ ]
+ },
+ "datatable": {
+ "use": [
+ "datatable-core",
+ "datatable-table",
+ "datatable-head",
+ "datatable-body",
+ "datatable-base",
+ "datatable-column-widths",
+ "datatable-message",
+ "datatable-mutable",
+ "datatable-sort",
+ "datatable-datasource"
+ ]
+ },
+ "datatable-base": {
+ "requires": [
+ "datatable-core",
+ "datatable-table",
+ "base-build",
+ "widget"
+ ],
+ "skinnable": true
+ },
+ "datatable-base-deprecated": {
+ "requires": [
+ "recordset-base",
+ "widget",
+ "substitute",
+ "event-mouseenter"
+ ],
+ "skinnable": true
+ },
+ "datatable-body": {
+ "requires": [
+ "datatable-core",
+ "view",
+ "classnamemanager"
+ ]
+ },
+ "datatable-column-widths": {
+ "requires": [
+ "datatable-base"
+ ]
+ },
+ "datatable-core": {
+ "requires": [
+ "escape",
+ "model-list",
+ "node-event-delegate"
+ ]
+ },
+ "datatable-datasource": {
+ "requires": [
+ "datatable-base",
+ "plugin",
+ "datasource-local"
+ ]
+ },
+ "datatable-datasource-deprecated": {
+ "requires": [
+ "datatable-base-deprecated",
+ "plugin",
+ "datasource-local"
+ ]
+ },
+ "datatable-deprecated": {
+ "use": [
+ "datatable-base-deprecated",
+ "datatable-datasource-deprecated",
+ "datatable-sort-deprecated",
+ "datatable-scroll-deprecated"
+ ]
+ },
+ "datatable-head": {
+ "requires": [
+ "datatable-core",
+ "view",
+ "classnamemanager"
+ ]
+ },
+ "datatable-message": {
+ "lang": [
+ "en"
+ ],
+ "requires": [
+ "datatable-base"
+ ],
+ "skinnable": true
+ },
+ "datatable-mutable": {
+ "requires": [
+ "datatable-base"
+ ]
+ },
+ "datatable-scroll": {
+ "requires": [
+ "datatable-base",
+ "datatable-column-widths",
+ "dom-screen"
+ ],
+ "skinnable": true
+ },
+ "datatable-scroll-deprecated": {
+ "requires": [
+ "datatable-base-deprecated",
+ "plugin"
+ ]
+ },
+ "datatable-sort": {
+ "lang": [
+ "en"
+ ],
+ "requires": [
+ "datatable-base"
+ ],
+ "skinnable": true
+ },
+ "datatable-sort-deprecated": {
+ "lang": [
+ "en"
+ ],
+ "requires": [
+ "datatable-base-deprecated",
+ "plugin",
+ "recordset-sort"
+ ]
+ },
+ "datatable-table": {
+ "requires": [
+ "datatable-core",
+ "datatable-head",
+ "datatable-body",
+ "view",
+ "classnamemanager"
+ ]
+ },
+ "datatype": {
+ "use": [
+ "datatype-number",
+ "datatype-date",
+ "datatype-xml"
+ ]
+ },
+ "datatype-date": {
+ "supersedes": [
+ "datatype-date-format"
+ ],
+ "use": [
+ "datatype-date-parse",
+ "datatype-date-format"
+ ]
+ },
+ "datatype-date-format": {
+ "lang": [
+ "ar",
+ "ar-JO",
+ "ca",
+ "ca-ES",
+ "da",
+ "da-DK",
+ "de",
+ "de-AT",
+ "de-DE",
+ "el",
+ "el-GR",
+ "en",
+ "en-AU",
+ "en-CA",
+ "en-GB",
+ "en-IE",
+ "en-IN",
+ "en-JO",
+ "en-MY",
+ "en-NZ",
+ "en-PH",
+ "en-SG",
+ "en-US",
+ "es",
+ "es-AR",
+ "es-BO",
+ "es-CL",
+ "es-CO",
+ "es-EC",
+ "es-ES",
+ "es-MX",
+ "es-PE",
+ "es-PY",
+ "es-US",
+ "es-UY",
+ "es-VE",
+ "fi",
+ "fi-FI",
+ "fr",
+ "fr-BE",
+ "fr-CA",
+ "fr-FR",
+ "hi",
+ "hi-IN",
+ "id",
+ "id-ID",
+ "it",
+ "it-IT",
+ "ja",
+ "ja-JP",
+ "ko",
+ "ko-KR",
+ "ms",
+ "ms-MY",
+ "nb",
+ "nb-NO",
+ "nl",
+ "nl-BE",
+ "nl-NL",
+ "pl",
+ "pl-PL",
+ "pt",
+ "pt-BR",
+ "ro",
+ "ro-RO",
+ "ru",
+ "ru-RU",
+ "sv",
+ "sv-SE",
+ "th",
+ "th-TH",
+ "tr",
+ "tr-TR",
+ "vi",
+ "vi-VN",
+ "zh-Hans",
+ "zh-Hans-CN",
+ "zh-Hant",
+ "zh-Hant-HK",
+ "zh-Hant-TW"
+ ]
+ },
+ "datatype-date-math": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "datatype-date-parse": {},
+ "datatype-number": {
+ "use": [
+ "datatype-number-parse",
+ "datatype-number-format"
+ ]
+ },
+ "datatype-number-format": {},
+ "datatype-number-parse": {},
+ "datatype-xml": {
+ "use": [
+ "datatype-xml-parse",
+ "datatype-xml-format"
+ ]
+ },
+ "datatype-xml-format": {},
+ "datatype-xml-parse": {},
+ "dd": {
+ "use": [
+ "dd-ddm-base",
+ "dd-ddm",
+ "dd-ddm-drop",
+ "dd-drag",
+ "dd-proxy",
+ "dd-constrain",
+ "dd-drop",
+ "dd-scroll",
+ "dd-delegate"
+ ]
+ },
+ "dd-constrain": {
+ "requires": [
+ "dd-drag"
+ ]
+ },
+ "dd-ddm": {
+ "requires": [
+ "dd-ddm-base",
+ "event-resize"
+ ]
+ },
+ "dd-ddm-base": {
+ "requires": [
+ "node",
+ "base",
+ "yui-throttle",
+ "classnamemanager"
+ ]
+ },
+ "dd-ddm-drop": {
+ "requires": [
+ "dd-ddm"
+ ]
+ },
+ "dd-delegate": {
+ "requires": [
+ "dd-drag",
+ "dd-drop-plugin",
+ "event-mouseenter"
+ ]
+ },
+ "dd-drag": {
+ "requires": [
+ "dd-ddm-base"
+ ]
+ },
+ "dd-drop": {
+ "requires": [
+ "dd-drag",
+ "dd-ddm-drop"
+ ]
+ },
+ "dd-drop-plugin": {
+ "requires": [
+ "dd-drop"
+ ]
+ },
+ "dd-gestures": {
+ "condition": {
+ "name": "dd-gestures",
+ "test": function(Y) {
+ return ((Y.config.win && ("ontouchstart" in Y.config.win)) && !(Y.UA.chrome && Y.UA.chrome < 6));
+},
+ "trigger": "dd-drag"
+ },
+ "requires": [
+ "dd-drag",
+ "event-synthetic",
+ "event-gestures"
+ ]
+ },
+ "dd-plugin": {
+ "optional": [
+ "dd-constrain",
+ "dd-proxy"
+ ],
+ "requires": [
+ "dd-drag"
+ ]
+ },
+ "dd-proxy": {
+ "requires": [
+ "dd-drag"
+ ]
+ },
+ "dd-scroll": {
+ "requires": [
+ "dd-drag"
+ ]
+ },
+ "dial": {
+ "lang": [
+ "en",
+ "es"
+ ],
+ "requires": [
+ "widget",
+ "dd-drag",
+ "substitute",
+ "event-mouseenter",
+ "event-move",
+ "event-key",
+ "transition",
+ "intl"
+ ],
+ "skinnable": true
+ },
+ "dom": {
+ "use": [
+ "dom-base",
+ "dom-screen",
+ "dom-style",
+ "selector-native",
+ "selector"
+ ]
+ },
+ "dom-base": {
+ "requires": [
+ "dom-core"
+ ]
+ },
+ "dom-core": {
+ "requires": [
+ "oop",
+ "features"
+ ]
+ },
+ "dom-deprecated": {
+ "requires": [
+ "dom-base"
+ ]
+ },
+ "dom-screen": {
+ "requires": [
+ "dom-base",
+ "dom-style"
+ ]
+ },
+ "dom-style": {
+ "requires": [
+ "dom-base"
+ ]
+ },
+ "dom-style-ie": {
+ "condition": {
+ "name": "dom-style-ie",
+ "test": function (Y) {
+
+ var testFeature = Y.Features.test,
+ addFeature = Y.Features.add,
+ WINDOW = Y.config.win,
+ DOCUMENT = Y.config.doc,
+ DOCUMENT_ELEMENT = 'documentElement',
+ ret = false;
+
+ addFeature('style', 'computedStyle', {
+ test: function() {
+ return WINDOW && 'getComputedStyle' in WINDOW;
+ }
+ });
+
+ addFeature('style', 'opacity', {
+ test: function() {
+ return DOCUMENT && 'opacity' in DOCUMENT[DOCUMENT_ELEMENT].style;
+ }
+ });
+
+ ret = (!testFeature('style', 'opacity') &&
+ !testFeature('style', 'computedStyle'));
+
+ return ret;
+},
+ "trigger": "dom-style"
+ },
+ "requires": [
+ "dom-style"
+ ]
+ },
+ "dump": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "editor": {
+ "use": [
+ "frame",
+ "editor-selection",
+ "exec-command",
+ "editor-base",
+ "editor-para",
+ "editor-br",
+ "editor-bidi",
+ "editor-tab",
+ "createlink-base"
+ ]
+ },
+ "editor-base": {
+ "requires": [
+ "base",
+ "frame",
+ "node",
+ "exec-command",
+ "editor-selection"
+ ]
+ },
+ "editor-bidi": {
+ "requires": [
+ "editor-base"
+ ]
+ },
+ "editor-br": {
+ "requires": [
+ "editor-base"
+ ]
+ },
+ "editor-lists": {
+ "requires": [
+ "editor-base"
+ ]
+ },
+ "editor-para": {
+ "requires": [
+ "editor-para-base"
+ ]
+ },
+ "editor-para-base": {
+ "requires": [
+ "editor-base"
+ ]
+ },
+ "editor-para-ie": {
+ "condition": {
+ "name": "editor-para-ie",
+ "trigger": "editor-para",
+ "ua": "ie",
+ "when": "instead"
+ },
+ "requires": [
+ "editor-para-base"
+ ]
+ },
+ "editor-selection": {
+ "requires": [
+ "node"
+ ]
+ },
+ "editor-tab": {
+ "requires": [
+ "editor-base"
+ ]
+ },
+ "escape": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "event": {
+ "after": [
+ "node-base"
+ ],
+ "use": [
+ "event-base",
+ "event-delegate",
+ "event-synthetic",
+ "event-mousewheel",
+ "event-mouseenter",
+ "event-key",
+ "event-focus",
+ "event-resize",
+ "event-hover",
+ "event-outside",
+ "event-touch",
+ "event-move",
+ "event-flick",
+ "event-valuechange"
+ ]
+ },
+ "event-base": {
+ "after": [
+ "node-base"
+ ],
+ "requires": [
+ "event-custom-base"
+ ]
+ },
+ "event-base-ie": {
+ "after": [
+ "event-base"
+ ],
+ "condition": {
+ "name": "event-base-ie",
+ "test": function(Y) {
+ var imp = Y.config.doc && Y.config.doc.implementation;
+ return (imp && (!imp.hasFeature('Events', '2.0')));
+},
+ "trigger": "node-base"
+ },
+ "requires": [
+ "node-base"
+ ]
+ },
+ "event-contextmenu": {
+ "requires": [
+ "event-synthetic",
+ "dom-screen"
+ ]
+ },
+ "event-custom": {
+ "use": [
+ "event-custom-base",
+ "event-custom-complex"
+ ]
+ },
+ "event-custom-base": {
+ "requires": [
+ "oop"
+ ]
+ },
+ "event-custom-complex": {
+ "requires": [
+ "event-custom-base"
+ ]
+ },
+ "event-delegate": {
+ "requires": [
+ "node-base"
+ ]
+ },
+ "event-flick": {
+ "requires": [
+ "node-base",
+ "event-touch",
+ "event-synthetic"
+ ]
+ },
+ "event-focus": {
+ "requires": [
+ "event-synthetic"
+ ]
+ },
+ "event-gestures": {
+ "use": [
+ "event-flick",
+ "event-move"
+ ]
+ },
+ "event-hover": {
+ "requires": [
+ "event-mouseenter"
+ ]
+ },
+ "event-key": {
+ "requires": [
+ "event-synthetic"
+ ]
+ },
+ "event-mouseenter": {
+ "requires": [
+ "event-synthetic"
+ ]
+ },
+ "event-mousewheel": {
+ "requires": [
+ "node-base"
+ ]
+ },
+ "event-move": {
+ "requires": [
+ "node-base",
+ "event-touch",
+ "event-synthetic"
+ ]
+ },
+ "event-outside": {
+ "requires": [
+ "event-synthetic"
+ ]
+ },
+ "event-resize": {
+ "requires": [
+ "node-base",
+ "event-synthetic"
+ ]
+ },
+ "event-simulate": {
+ "requires": [
+ "event-base"
+ ]
+ },
+ "event-synthetic": {
+ "requires": [
+ "node-base",
+ "event-custom-complex"
+ ]
+ },
+ "event-touch": {
+ "requires": [
+ "node-base"
+ ]
+ },
+ "event-valuechange": {
+ "requires": [
+ "event-focus",
+ "event-synthetic"
+ ]
+ },
+ "exec-command": {
+ "requires": [
+ "frame"
+ ]
+ },
+ "features": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "file": {
+ "requires": [
+ "file-flash",
+ "file-html5"
+ ]
+ },
+ "file-flash": {
+ "requires": [
+ "base"
+ ]
+ },
+ "file-html5": {
+ "requires": [
+ "base"
+ ]
+ },
+ "frame": {
+ "requires": [
+ "base",
+ "node",
+ "selector-css3",
+ "substitute",
+ "yui-throttle"
+ ]
+ },
+ "gesture-simulate": {
+ "requires": [
+ "async-queue",
+ "event-simulate",
+ "node-screen"
+ ]
+ },
+ "get": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "graphics": {
+ "requires": [
+ "node",
+ "event-custom",
+ "pluginhost",
+ "matrix",
+ "classnamemanager"
+ ]
+ },
+ "graphics-canvas": {
+ "condition": {
+ "name": "graphics-canvas",
+ "test": function(Y) {
+ var DOCUMENT = Y.config.doc,
+ useCanvas = Y.config.defaultGraphicEngine && Y.config.defaultGraphicEngine == "canvas",
+ canvas = DOCUMENT && DOCUMENT.createElement("canvas"),
+ svg = (DOCUMENT && DOCUMENT.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"));
+ return (!svg || useCanvas) && (canvas && canvas.getContext && canvas.getContext("2d"));
+},
+ "trigger": "graphics"
+ },
+ "requires": [
+ "graphics"
+ ]
+ },
+ "graphics-canvas-default": {
+ "condition": {
+ "name": "graphics-canvas-default",
+ "test": function(Y) {
+ var DOCUMENT = Y.config.doc,
+ useCanvas = Y.config.defaultGraphicEngine && Y.config.defaultGraphicEngine == "canvas",
+ canvas = DOCUMENT && DOCUMENT.createElement("canvas"),
+ svg = (DOCUMENT && DOCUMENT.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"));
+ return (!svg || useCanvas) && (canvas && canvas.getContext && canvas.getContext("2d"));
+},
+ "trigger": "graphics"
+ }
+ },
+ "graphics-svg": {
+ "condition": {
+ "name": "graphics-svg",
+ "test": function(Y) {
+ var DOCUMENT = Y.config.doc,
+ useSVG = !Y.config.defaultGraphicEngine || Y.config.defaultGraphicEngine != "canvas",
+ canvas = DOCUMENT && DOCUMENT.createElement("canvas"),
+ svg = (DOCUMENT && DOCUMENT.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"));
+
+ return svg && (useSVG || !canvas);
+},
+ "trigger": "graphics"
+ },
+ "requires": [
+ "graphics"
+ ]
+ },
+ "graphics-svg-default": {
+ "condition": {
+ "name": "graphics-svg-default",
+ "test": function(Y) {
+ var DOCUMENT = Y.config.doc,
+ useSVG = !Y.config.defaultGraphicEngine || Y.config.defaultGraphicEngine != "canvas",
+ canvas = DOCUMENT && DOCUMENT.createElement("canvas"),
+ svg = (DOCUMENT && DOCUMENT.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"));
+
+ return svg && (useSVG || !canvas);
+},
+ "trigger": "graphics"
+ }
+ },
+ "graphics-vml": {
+ "condition": {
+ "name": "graphics-vml",
+ "test": function(Y) {
+ var DOCUMENT = Y.config.doc,
+ canvas = DOCUMENT && DOCUMENT.createElement("canvas");
+ return (DOCUMENT && !DOCUMENT.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") && (!canvas || !canvas.getContext || !canvas.getContext("2d")));
+},
+ "trigger": "graphics"
+ },
+ "requires": [
+ "graphics"
+ ]
+ },
+ "graphics-vml-default": {
+ "condition": {
+ "name": "graphics-vml-default",
+ "test": function(Y) {
+ var DOCUMENT = Y.config.doc,
+ canvas = DOCUMENT && DOCUMENT.createElement("canvas");
+ return (DOCUMENT && !DOCUMENT.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") && (!canvas || !canvas.getContext || !canvas.getContext("2d")));
+},
+ "trigger": "graphics"
+ }
+ },
+ "handlebars": {
+ "use": [
+ "handlebars-compiler"
+ ]
+ },
+ "handlebars-base": {
+ "requires": [
+ "escape"
+ ]
+ },
+ "handlebars-compiler": {
+ "requires": [
+ "handlebars-base"
+ ]
+ },
+ "highlight": {
+ "use": [
+ "highlight-base",
+ "highlight-accentfold"
+ ]
+ },
+ "highlight-accentfold": {
+ "requires": [
+ "highlight-base",
+ "text-accentfold"
+ ]
+ },
+ "highlight-base": {
+ "requires": [
+ "array-extras",
+ "classnamemanager",
+ "escape",
+ "text-wordbreak"
+ ]
+ },
+ "history": {
+ "use": [
+ "history-base",
+ "history-hash",
+ "history-hash-ie",
+ "history-html5"
+ ]
+ },
+ "history-base": {
+ "requires": [
+ "event-custom-complex"
+ ]
+ },
+ "history-hash": {
+ "after": [
+ "history-html5"
+ ],
+ "requires": [
+ "event-synthetic",
+ "history-base",
+ "yui-later"
+ ]
+ },
+ "history-hash-ie": {
+ "condition": {
+ "name": "history-hash-ie",
+ "test": function (Y) {
+ var docMode = Y.config.doc && Y.config.doc.documentMode;
+
+ return Y.UA.ie && (!('onhashchange' in Y.config.win) ||
+ !docMode || docMode < 8);
+},
+ "trigger": "history-hash"
+ },
+ "requires": [
+ "history-hash",
+ "node-base"
+ ]
+ },
+ "history-html5": {
+ "optional": [
+ "json"
+ ],
+ "requires": [
+ "event-base",
+ "history-base",
+ "node-base"
+ ]
+ },
+ "imageloader": {
+ "requires": [
+ "base-base",
+ "node-style",
+ "node-screen"
+ ]
+ },
+ "intl": {
+ "requires": [
+ "intl-base",
+ "event-custom"
+ ]
+ },
+ "intl-base": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "io": {
+ "use": [
+ "io-base",
+ "io-xdr",
+ "io-form",
+ "io-upload-iframe",
+ "io-queue"
+ ]
+ },
+ "io-base": {
+ "requires": [
+ "event-custom-base",
+ "querystring-stringify-simple"
+ ]
+ },
+ "io-form": {
+ "requires": [
+ "io-base",
+ "node-base"
+ ]
+ },
+ "io-nodejs": {
+ "condition": {
+ "name": "io-nodejs",
+ "trigger": "io-base",
+ "ua": "nodejs"
+ },
+ "requires": [
+ "io-base"
+ ]
+ },
+ "io-queue": {
+ "requires": [
+ "io-base",
+ "queue-promote"
+ ]
+ },
+ "io-upload-iframe": {
+ "requires": [
+ "io-base",
+ "node-base"
+ ]
+ },
+ "io-xdr": {
+ "requires": [
+ "io-base",
+ "datatype-xml-parse"
+ ]
+ },
+ "json": {
+ "use": [
+ "json-parse",
+ "json-stringify"
+ ]
+ },
+ "json-parse": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "json-stringify": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "jsonp": {
+ "requires": [
+ "get",
+ "oop"
+ ]
+ },
+ "jsonp-url": {
+ "requires": [
+ "jsonp"
+ ]
+ },
+ "lazy-model-list": {
+ "requires": [
+ "model-list"
+ ]
+ },
+ "loader": {
+ "use": [
+ "loader-base",
+ "loader-rollup",
+ "loader-yui3"
+ ]
+ },
+ "loader-base": {
+ "requires": [
+ "get",
+ "features"
+ ]
+ },
+ "loader-rollup": {
+ "requires": [
+ "loader-base"
+ ]
+ },
+ "loader-yui3": {
+ "requires": [
+ "loader-base"
+ ]
+ },
+ "matrix": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "model": {
+ "requires": [
+ "base-build",
+ "escape",
+ "json-parse"
+ ]
+ },
+ "model-list": {
+ "requires": [
+ "array-extras",
+ "array-invoke",
+ "arraylist",
+ "base-build",
+ "escape",
+ "json-parse",
+ "model"
+ ]
+ },
+ "model-sync-rest": {
+ "requires": [
+ "model",
+ "io-base",
+ "json-stringify"
+ ]
+ },
+ "node": {
+ "use": [
+ "node-base",
+ "node-event-delegate",
+ "node-pluginhost",
+ "node-screen",
+ "node-style"
+ ]
+ },
+ "node-base": {
+ "requires": [
+ "event-base",
+ "node-core",
+ "dom-base"
+ ]
+ },
+ "node-core": {
+ "requires": [
+ "dom-core",
+ "selector"
+ ]
+ },
+ "node-deprecated": {
+ "requires": [
+ "node-base"
+ ]
+ },
+ "node-event-delegate": {
+ "requires": [
+ "node-base",
+ "event-delegate"
+ ]
+ },
+ "node-event-html5": {
+ "requires": [
+ "node-base"
+ ]
+ },
+ "node-event-simulate": {
+ "requires": [
+ "node-base",
+ "event-simulate",
+ "gesture-simulate"
+ ]
+ },
+ "node-flick": {
+ "requires": [
+ "classnamemanager",
+ "transition",
+ "event-flick",
+ "plugin"
+ ],
+ "skinnable": true
+ },
+ "node-focusmanager": {
+ "requires": [
+ "attribute",
+ "node",
+ "plugin",
+ "node-event-simulate",
+ "event-key",
+ "event-focus"
+ ]
+ },
+ "node-load": {
+ "requires": [
+ "node-base",
+ "io-base"
+ ]
+ },
+ "node-menunav": {
+ "requires": [
+ "node",
+ "classnamemanager",
+ "plugin",
+ "node-focusmanager"
+ ],
+ "skinnable": true
+ },
+ "node-pluginhost": {
+ "requires": [
+ "node-base",
+ "pluginhost"
+ ]
+ },
+ "node-screen": {
+ "requires": [
+ "dom-screen",
+ "node-base"
+ ]
+ },
+ "node-style": {
+ "requires": [
+ "dom-style",
+ "node-base"
+ ]
+ },
+ "oop": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "overlay": {
+ "requires": [
+ "widget",
+ "widget-stdmod",
+ "widget-position",
+ "widget-position-align",
+ "widget-stack",
+ "widget-position-constrain"
+ ],
+ "skinnable": true
+ },
+ "panel": {
+ "requires": [
+ "widget",
+ "widget-autohide",
+ "widget-buttons",
+ "widget-modality",
+ "widget-position",
+ "widget-position-align",
+ "widget-position-constrain",
+ "widget-stack",
+ "widget-stdmod"
+ ],
+ "skinnable": true
+ },
+ "parallel": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "pjax": {
+ "requires": [
+ "pjax-base",
+ "io-base"
+ ]
+ },
+ "pjax-base": {
+ "requires": [
+ "classnamemanager",
+ "node-event-delegate",
+ "router"
+ ]
+ },
+ "pjax-plugin": {
+ "requires": [
+ "node-pluginhost",
+ "pjax",
+ "plugin"
+ ]
+ },
+ "plugin": {
+ "requires": [
+ "base-base"
+ ]
+ },
+ "pluginhost": {
+ "use": [
+ "pluginhost-base",
+ "pluginhost-config"
+ ]
+ },
+ "pluginhost-base": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "pluginhost-config": {
+ "requires": [
+ "pluginhost-base"
+ ]
+ },
+ "profiler": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "querystring": {
+ "use": [
+ "querystring-parse",
+ "querystring-stringify"
+ ]
+ },
+ "querystring-parse": {
+ "requires": [
+ "yui-base",
+ "array-extras"
+ ]
+ },
+ "querystring-parse-simple": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "querystring-stringify": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "querystring-stringify-simple": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "queue-promote": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "range-slider": {
+ "requires": [
+ "slider-base",
+ "slider-value-range",
+ "clickable-rail"
+ ]
+ },
+ "recordset": {
+ "use": [
+ "recordset-base",
+ "recordset-sort",
+ "recordset-filter",
+ "recordset-indexer"
+ ]
+ },
+ "recordset-base": {
+ "requires": [
+ "base",
+ "arraylist"
+ ]
+ },
+ "recordset-filter": {
+ "requires": [
+ "recordset-base",
+ "array-extras",
+ "plugin"
+ ]
+ },
+ "recordset-indexer": {
+ "requires": [
+ "recordset-base",
+ "plugin"
+ ]
+ },
+ "recordset-sort": {
+ "requires": [
+ "arraysort",
+ "recordset-base",
+ "plugin"
+ ]
+ },
+ "resize": {
+ "use": [
+ "resize-base",
+ "resize-proxy",
+ "resize-constrain"
+ ]
+ },
+ "resize-base": {
+ "requires": [
+ "base",
+ "widget",
+ "substitute",
+ "event",
+ "oop",
+ "dd-drag",
+ "dd-delegate",
+ "dd-drop"
+ ],
+ "skinnable": true
+ },
+ "resize-constrain": {
+ "requires": [
+ "plugin",
+ "resize-base"
+ ]
+ },
+ "resize-plugin": {
+ "optional": [
+ "resize-constrain"
+ ],
+ "requires": [
+ "resize-base",
+ "plugin"
+ ]
+ },
+ "resize-proxy": {
+ "requires": [
+ "plugin",
+ "resize-base"
+ ]
+ },
+ "router": {
+ "optional": [
+ "querystring-parse"
+ ],
+ "requires": [
+ "array-extras",
+ "base-build",
+ "history"
+ ]
+ },
+ "scrollview": {
+ "requires": [
+ "scrollview-base",
+ "scrollview-scrollbars"
+ ]
+ },
+ "scrollview-base": {
+ "requires": [
+ "widget",
+ "event-gestures",
+ "event-mousewheel",
+ "transition"
+ ],
+ "skinnable": true
+ },
+ "scrollview-base-ie": {
+ "condition": {
+ "name": "scrollview-base-ie",
+ "trigger": "scrollview-base",
+ "ua": "ie"
+ },
+ "requires": [
+ "scrollview-base"
+ ]
+ },
+ "scrollview-list": {
+ "requires": [
+ "plugin",
+ "classnamemanager"
+ ],
+ "skinnable": true
+ },
+ "scrollview-paginator": {
+ "requires": [
+ "plugin",
+ "classnamemanager"
+ ]
+ },
+ "scrollview-scrollbars": {
+ "requires": [
+ "classnamemanager",
+ "transition",
+ "plugin"
+ ],
+ "skinnable": true
+ },
+ "selector": {
+ "requires": [
+ "selector-native"
+ ]
+ },
+ "selector-css2": {
+ "condition": {
+ "name": "selector-css2",
+ "test": function (Y) {
+ var DOCUMENT = Y.config.doc,
+ ret = DOCUMENT && !('querySelectorAll' in DOCUMENT);
+
+ return ret;
+},
+ "trigger": "selector"
+ },
+ "requires": [
+ "selector-native"
+ ]
+ },
+ "selector-css3": {
+ "requires": [
+ "selector-native",
+ "selector-css2"
+ ]
+ },
+ "selector-native": {
+ "requires": [
+ "dom-base"
+ ]
+ },
+ "shim-plugin": {
+ "requires": [
+ "node-style",
+ "node-pluginhost"
+ ]
+ },
+ "slider": {
+ "use": [
+ "slider-base",
+ "slider-value-range",
+ "clickable-rail",
+ "range-slider"
+ ]
+ },
+ "slider-base": {
+ "requires": [
+ "widget",
+ "dd-constrain",
+ "substitute",
+ "event-key"
+ ],
+ "skinnable": true
+ },
+ "slider-value-range": {
+ "requires": [
+ "slider-base"
+ ]
+ },
+ "sortable": {
+ "requires": [
+ "dd-delegate",
+ "dd-drop-plugin",
+ "dd-proxy"
+ ]
+ },
+ "sortable-scroll": {
+ "requires": [
+ "dd-scroll",
+ "sortable"
+ ]
+ },
+ "stylesheet": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "substitute": {
+ "optional": [
+ "dump"
+ ],
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "swf": {
+ "requires": [
+ "event-custom",
+ "node",
+ "swfdetect",
+ "escape"
+ ]
+ },
+ "swfdetect": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "tabview": {
+ "requires": [
+ "widget",
+ "widget-parent",
+ "widget-child",
+ "tabview-base",
+ "node-pluginhost",
+ "node-focusmanager"
+ ],
+ "skinnable": true
+ },
+ "tabview-base": {
+ "requires": [
+ "node-event-delegate",
+ "classnamemanager",
+ "skin-sam-tabview"
+ ]
+ },
+ "tabview-plugin": {
+ "requires": [
+ "tabview-base"
+ ]
+ },
+ "test": {
+ "requires": [
+ "event-simulate",
+ "event-custom",
+ "substitute",
+ "json-stringify"
+ ],
+ "skinnable": true
+ },
+ "test-console": {
+ "requires": [
+ "console-filters",
+ "test"
+ ],
+ "skinnable": true
+ },
+ "text": {
+ "use": [
+ "text-accentfold",
+ "text-wordbreak"
+ ]
+ },
+ "text-accentfold": {
+ "requires": [
+ "array-extras",
+ "text-data-accentfold"
+ ]
+ },
+ "text-data-accentfold": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "text-data-wordbreak": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "text-wordbreak": {
+ "requires": [
+ "array-extras",
+ "text-data-wordbreak"
+ ]
+ },
+ "transition": {
+ "requires": [
+ "node-style"
+ ]
+ },
+ "transition-timer": {
+ "condition": {
+ "name": "transition-timer",
+ "test": function (Y) {
+ var DOCUMENT = Y.config.doc,
+ node = (DOCUMENT) ? DOCUMENT.documentElement: null,
+ ret = true;
+
+ if (node && node.style) {
+ ret = !('MozTransition' in node.style || 'WebkitTransition' in node.style);
+ }
+
+ return ret;
+},
+ "trigger": "transition"
+ },
+ "requires": [
+ "transition"
+ ]
+ },
+ "uploader": {
+ "requires": [
+ "uploader-html5",
+ "uploader-flash"
+ ]
+ },
+ "uploader-deprecated": {
+ "requires": [
+ "event-custom",
+ "node",
+ "base",
+ "swf"
+ ]
+ },
+ "uploader-flash": {
+ "requires": [
+ "swf",
+ "widget",
+ "substitute",
+ "base",
+ "cssbutton",
+ "node",
+ "event-custom",
+ "file-flash",
+ "uploader-queue"
+ ]
+ },
+ "uploader-html5": {
+ "requires": [
+ "widget",
+ "node-event-simulate",
+ "substitute",
+ "file-html5",
+ "uploader-queue"
+ ]
+ },
+ "uploader-queue": {
+ "requires": [
+ "base"
+ ]
+ },
+ "view": {
+ "requires": [
+ "base-build",
+ "node-event-delegate"
+ ]
+ },
+ "view-node-map": {
+ "requires": [
+ "view"
+ ]
+ },
+ "widget": {
+ "use": [
+ "widget-base",
+ "widget-htmlparser",
+ "widget-skin",
+ "widget-uievents"
+ ]
+ },
+ "widget-anim": {
+ "requires": [
+ "anim-base",
+ "plugin",
+ "widget"
+ ]
+ },
+ "widget-autohide": {
+ "requires": [
+ "base-build",
+ "event-key",
+ "event-outside",
+ "widget"
+ ]
+ },
+ "widget-base": {
+ "requires": [
+ "attribute",
+ "base-base",
+ "base-pluginhost",
+ "classnamemanager",
+ "event-focus",
+ "node-base",
+ "node-style"
+ ],
+ "skinnable": true
+ },
+ "widget-base-ie": {
+ "condition": {
+ "name": "widget-base-ie",
+ "trigger": "widget-base",
+ "ua": "ie"
+ },
+ "requires": [
+ "widget-base"
+ ]
+ },
+ "widget-buttons": {
+ "requires": [
+ "button-plugin",
+ "cssbutton",
+ "widget-stdmod"
+ ]
+ },
+ "widget-child": {
+ "requires": [
+ "base-build",
+ "widget"
+ ]
+ },
+ "widget-htmlparser": {
+ "requires": [
+ "widget-base"
+ ]
+ },
+ "widget-locale": {
+ "requires": [
+ "widget-base"
+ ]
+ },
+ "widget-modality": {
+ "requires": [
+ "base-build",
+ "event-outside",
+ "widget"
+ ],
+ "skinnable": true
+ },
+ "widget-parent": {
+ "requires": [
+ "arraylist",
+ "base-build",
+ "widget"
+ ]
+ },
+ "widget-position": {
+ "requires": [
+ "base-build",
+ "node-screen",
+ "widget"
+ ]
+ },
+ "widget-position-align": {
+ "requires": [
+ "widget-position"
+ ]
+ },
+ "widget-position-constrain": {
+ "requires": [
+ "widget-position"
+ ]
+ },
+ "widget-skin": {
+ "requires": [
+ "widget-base"
+ ]
+ },
+ "widget-stack": {
+ "requires": [
+ "base-build",
+ "widget"
+ ],
+ "skinnable": true
+ },
+ "widget-stdmod": {
+ "requires": [
+ "base-build",
+ "widget"
+ ]
+ },
+ "widget-uievents": {
+ "requires": [
+ "node-event-delegate",
+ "widget-base"
+ ]
+ },
+ "yql": {
+ "requires": [
+ "jsonp",
+ "jsonp-url"
+ ]
+ },
+ "yui": {},
+ "yui-base": {},
+ "yui-later": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "yui-log": {
+ "requires": [
+ "yui-base"
+ ]
+ },
+ "yui-throttle": {
+ "requires": [
+ "yui-base"
+ ]
+ }
+};
+YUI.Env[Y.version].md5 = '8167a05694cccfa8b829b85c2caae54e';
+
+
+}, '@VERSION@' ,{requires:['loader-base']});
+
+
+YUI.add('yui', function(Y){}, '@VERSION@' ,{use:['yui-base','get','features','intl-base','yui-log','yui-later','loader-base', 'loader-rollup', 'loader-yui3']});
+
diff --git a/nodejs_tests/files/yui.js.min b/nodejs_tests/files/yui.js.min
new file mode 100644
index 0000000..3907e98
--- /dev/null
+++ b/nodejs_tests/files/yui.js.min
@@ -0,0 +1 @@
+if(typeof YUI!="undefined"){YUI._YUI=YUI}var YUI=function(){var c=0,f=this,b=arguments,a=b.length,e=function(h,g){return(h&&h.hasOwnProperty&&(h instanceof g))},d=(typeof YUI_config!=="undefined")&&YUI_config;if(!(e(f,YUI))){f=new YUI()}else{f._init();if(YUI.GlobalConfig){f.applyConfig(YUI.GlobalConfig)}if(d){f.applyConfig(d)}if(!a){f._setup()}}if(a){for(;c<a;c++){f.applyConfig(b[c])}f._setup()}f.instanceOf=e;return f};(function(){var q,b,r="@VERSION@",i=".",o="http://yui.yahooapis.com/" [...]
\ No newline at end of file
diff --git a/nodejs_tests/tests.js b/nodejs_tests/tests.js
new file mode 100644
index 0000000..c8c4474
--- /dev/null
+++ b/nodejs_tests/tests.js
@@ -0,0 +1,102 @@
+var YUITest = require('yuitest'),
+ Assert = YUITest.Assert,
+ suite = new YUITest.TestSuite('YUICompressor Tests'),
+ path = require('path'),
+ fs = require('fs'),
+ compressor = require('../nodejs/index'),
+ exists = fs.existsSync || path.existsSync;
+
+
+var base = path.join(__dirname, '../tests');
+var files = fs.readdirSync(base);
+var testFiles = [];
+
+files.forEach(function(file) {
+ var ext = path.extname(file);
+ if (ext === '.js' || ext === '.css') {
+ var comp = path.join(base, file + '.min');
+ if (exists(comp)) {
+ testFiles.push({
+ type: ext.replace('.', ''),
+ test: file,
+ result: (fs.readFileSync(path.join(base, file + '.min'), 'utf8')).trim()
+ });
+ }
+ }
+});
+
+testFiles.forEach(function(item) {
+ suite.add(new YUITest.TestCase({
+ name: item.test,
+ 'test: compress': function() {
+ var test = this;
+ compressor.compress(path.join(base, item.test), {
+ type: item.type,
+ charset: 'utf8'
+ }, function(err, out) {
+ test.resume(function() {
+ Assert.isNull(err, 'error object should be null');
+ Assert.areEqual(out, item.result, 'Failed to properly compress');
+ });
+ });
+ test.wait();
+ }
+ }));
+
+});
+
+suite.add(new YUITest.TestCase({
+ name: 'Others',
+ 'test: error no file': function() {
+ var test = this;
+ compressor.compress('/path/to/no/file', function(err, data) {
+ test.resume(function() {
+ Assert.areEqual(data, '', 'should not return data');
+ Assert.isTrue(err.indexOf('[ERROR]') > -1, 'should have [ERROR] in string');
+ });
+ });
+ test.wait();
+ },
+ 'test: string to compress': function() {
+ var test = this,
+ given = 'var x = (function() { var foo = 1, bar = 2; return (foo + bar) }())',
+ expected = 'var x=(function(){var b=1,a=2;return(b+a)}());';
+ compressor.compress(given, function(err, data) {
+ test.resume(function() {
+ Assert.isNull(err, 'error object should be null');
+ Assert.areEqual(data, expected, 'failed to compress string');
+ });
+ });
+ test.wait();
+ }
+}));
+
+var expectedYUI = fs.readFileSync(path.join(__dirname, 'files', 'yui.js.min'), 'utf8');
+
+suite.add(new YUITest.TestCase({
+ name: 'Large file support',
+ 'test compress yui.js as file': function() {
+ var test = this;
+ compressor.compress(path.join(__dirname, 'files', 'yui.js'), function(err, data) {
+ test.resume(function() {
+ Assert.isNull(err, 'error object should be null');
+ Assert.areEqual(data, expectedYUI, 'failed to minify a large file');
+ });
+ });
+ test.wait();
+ },
+ 'test compress yui.js as string': function() {
+ var test = this,
+ given = fs.readFileSync(path.join(__dirname, 'files', 'yui.js'), 'utf8');
+
+ compressor.compress(given, function(err, data) {
+ test.resume(function() {
+ Assert.isNull(err, 'error object should be null');
+ Assert.areEqual(data, expectedYUI, 'failed to minify a large file');
+ });
+ });
+ test.wait();
+ }
+}));
+
+YUITest.TestRunner.add(suite);
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..253ed3a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "yuicompressor",
+ "description": "YUICompressor CLI and Node.js require",
+ "version": "2.4.8",
+ "author": "Dav Glass <davglass at gmail.com>",
+ "bugs": { "url" : "http://yuilibrary.com/projects/yuicompressor/newticket" },
+ "devDependencies": {
+ "yuitest": "*"
+ },
+ "keywords": [
+ "yui", "compressor", "munger", "cssmin", "minify", "minification"
+ ],
+ "main": "./nodejs/index.js",
+ "bin": {
+ "yuicompressor": "./nodejs/cli.js"
+ },
+ "files": [
+ "build/*.jar",
+ "nodejs"
+ ],
+ "scripts": {
+ "test": "yuitest ./nodejs_tests/tests.js"
+ },
+ "licenses":[
+ {
+ "type" : "BSD",
+ "url" : "http://yuilibrary.com/license/"
+ }
+ ],
+ "repository": {
+ "type":"git",
+ "url":"https://github.com/yui/yuicompressor"
+ }
+}
diff --git a/ports/js/cssmin.js b/ports/js/cssmin.js
new file mode 100644
index 0000000..30a376f
--- /dev/null
+++ b/ports/js/cssmin.js
@@ -0,0 +1,391 @@
+/**
+ * cssmin.js
+ * Author: Stoyan Stefanov - http://phpied.com/
+ * Contributor: Dan Beam - http://danbeam.org/
+ * This is a JavaScript port of the CSS minification tool
+ * distributed with YUICompressor, itself a port
+ * of the cssmin utility by Isaac Schlueter - http://foohack.com/
+ * Permission is hereby granted to use the JavaScript version under the same
+ * conditions as the YUICompressor (original YUICompressor note below).
+ */
+
+/*
+* YUI Compressor
+* http://developer.yahoo.com/yui/compressor/
+* Author: Julien Lecomte - http://www.julienlecomte.net/
+* Copyright (c) 2013 Yahoo! Inc. All rights reserved.
+* The copyrights embodied in the content of this file are licensed
+* by Yahoo! Inc. under the BSD (revised) open source license.
+*/
+var YAHOO = YAHOO || {};
+YAHOO.compressor = YAHOO.compressor || {};
+
+/**
+ * Utility method to replace all data urls with tokens before we start
+ * compressing, to avoid performance issues running some of the subsequent
+ * regexes against large strings chunks.
+ *
+ * @private
+ * @method _extractDataUrls
+ * @param {String} css The input css
+ * @param {Array} The global array of tokens to preserve
+ * @returns String The processed css
+ */
+YAHOO.compressor._extractDataUrls = function (css, preservedTokens) {
+
+ // Leave data urls alone to increase parse performance.
+ var maxIndex = css.length - 1,
+ appendIndex = 0,
+ startIndex,
+ endIndex,
+ terminator,
+ foundTerminator,
+ sb = [],
+ m,
+ preserver,
+ token,
+ pattern = /url\(\s*(["']?)data\:/gi;
+
+ // Since we need to account for non-base64 data urls, we need to handle
+ // ' and ) being part of the data string. Hence switching to indexOf,
+ // to determine whether or not we have matching string terminators and
+ // handling sb appends directly, instead of using matcher.append* methods.
+
+ while ((m = pattern.exec(css)) !== null) {
+
+ startIndex = m.index + 4; // "url(".length()
+ terminator = m[1]; // ', " or empty (not quoted)
+
+ if (terminator.length === 0) {
+ terminator = ")";
+ }
+
+ foundTerminator = false;
+
+ endIndex = pattern.lastIndex - 1;
+
+ while(foundTerminator === false && endIndex+1 <= maxIndex) {
+ endIndex = css.indexOf(terminator, endIndex + 1);
+
+ // endIndex == 0 doesn't really apply here
+ if ((endIndex > 0) && (css.charAt(endIndex - 1) !== '\\')) {
+ foundTerminator = true;
+ if (")" != terminator) {
+ endIndex = css.indexOf(")", endIndex);
+ }
+ }
+ }
+
+ // Enough searching, start moving stuff over to the buffer
+ sb.push(css.substring(appendIndex, m.index));
+
+ if (foundTerminator) {
+ token = css.substring(startIndex, endIndex);
+ token = token.replace(/\s+/g, "");
+ preservedTokens.push(token);
+
+ preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___)";
+ sb.push(preserver);
+
+ appendIndex = endIndex + 1;
+ } else {
+ // No end terminator found, re-add the whole match. Should we throw/warn here?
+ sb.push(css.substring(m.index, pattern.lastIndex));
+ appendIndex = pattern.lastIndex;
+ }
+ }
+
+ sb.push(css.substring(appendIndex));
+
+ return sb.join("");
+};
+
+/**
+ * Utility method to compress hex color values of the form #AABBCC to #ABC.
+ *
+ * DOES NOT compress CSS ID selectors which match the above pattern (which would break things).
+ * e.g. #AddressForm { ... }
+ *
+ * DOES NOT compress IE filters, which have hex color values (which would break things).
+ * e.g. filter: chroma(color="#FFFFFF");
+ *
+ * DOES NOT compress invalid hex values.
+ * e.g. background-color: #aabbccdd
+ *
+ * @private
+ * @method _compressHexColors
+ * @param {String} css The input css
+ * @returns String The processed css
+ */
+YAHOO.compressor._compressHexColors = function(css) {
+
+ // Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters)
+ var pattern = /(\=\s*?["']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/gi,
+ m,
+ index = 0,
+ isFilter,
+ sb = [];
+
+ while ((m = pattern.exec(css)) !== null) {
+
+ sb.push(css.substring(index, m.index));
+
+ isFilter = m[1];
+
+ if (isFilter) {
+ // Restore, maintain case, otherwise filter will break
+ sb.push(m[1] + "#" + (m[2] + m[3] + m[4] + m[5] + m[6] + m[7]));
+ } else {
+ if (m[2].toLowerCase() == m[3].toLowerCase() &&
+ m[4].toLowerCase() == m[5].toLowerCase() &&
+ m[6].toLowerCase() == m[7].toLowerCase()) {
+
+ // Compress.
+ sb.push("#" + (m[3] + m[5] + m[7]).toLowerCase());
+ } else {
+ // Non compressible color, restore but lower case.
+ sb.push("#" + (m[2] + m[3] + m[4] + m[5] + m[6] + m[7]).toLowerCase());
+ }
+ }
+
+ index = pattern.lastIndex = pattern.lastIndex - m[8].length;
+ }
+
+ sb.push(css.substring(index));
+
+ return sb.join("");
+};
+
+YAHOO.compressor.cssmin = function (css, linebreakpos) {
+
+ var startIndex = 0,
+ endIndex = 0,
+ i = 0, max = 0,
+ preservedTokens = [],
+ comments = [],
+ token = '',
+ totallen = css.length,
+ placeholder = '';
+
+ css = this._extractDataUrls(css, preservedTokens);
+
+ // collect all comment blocks...
+ while ((startIndex = css.indexOf("/*", startIndex)) >= 0) {
+ endIndex = css.indexOf("*/", startIndex + 2);
+ if (endIndex < 0) {
+ endIndex = totallen;
+ }
+ token = css.slice(startIndex + 2, endIndex);
+ comments.push(token);
+ css = css.slice(0, startIndex + 2) + "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.length - 1) + "___" + css.slice(endIndex);
+ startIndex += 2;
+ }
+
+ // preserve strings so their content doesn't get accidentally minified
+ css = css.replace(/("([^\\"]|\\.|\\)*")|('([^\\']|\\.|\\)*')/g, function (match) {
+ var i, max, quote = match.substring(0, 1);
+
+ match = match.slice(1, -1);
+
+ // maybe the string contains a comment-like substring?
+ // one, maybe more? put'em back then
+ if (match.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) {
+ for (i = 0, max = comments.length; i < max; i = i + 1) {
+ match = match.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments[i]);
+ }
+ }
+
+ // minify alpha opacity in filter strings
+ match = match.replace(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi, "alpha(opacity=");
+
+ preservedTokens.push(match);
+ return quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___" + quote;
+ });
+
+ // strings are safe, now wrestle the comments
+ for (i = 0, max = comments.length; i < max; i = i + 1) {
+
+ token = comments[i];
+ placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
+
+ // ! in the first position of the comment means preserve
+ // so push to the preserved tokens keeping the !
+ if (token.charAt(0) === "!") {
+ preservedTokens.push(token);
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___");
+ continue;
+ }
+
+ // \ in the last position looks like hack for Mac/IE5
+ // shorten that to /*\*/ and the next one to /**/
+ if (token.charAt(token.length - 1) === "\\") {
+ preservedTokens.push("\\");
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___");
+ i = i + 1; // attn: advancing the loop
+ preservedTokens.push("");
+ css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___");
+ continue;
+ }
+
+ // keep empty comments after child selectors (IE7 hack)
+ // e.g. html >/**/ body
+ if (token.length === 0) {
+ startIndex = css.indexOf(placeholder);
+ if (startIndex > 2) {
+ if (css.charAt(startIndex - 3) === '>') {
+ preservedTokens.push("");
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___");
+ }
+ }
+ }
+
+ // in all other cases kill the comment
+ css = css.replace("/*" + placeholder + "*/", "");
+ }
+
+
+ // Normalize all whitespace strings to single spaces. Easier to work with that way.
+ css = css.replace(/\s+/g, " ");
+
+ // Remove the spaces before the things that should not have spaces before them.
+ // But, be careful not to turn "p :link {...}" into "p:link{...}"
+ // Swap out any pseudo-class colons with the token, and then swap back.
+ css = css.replace(/(^|\})(([^\{:])+:)+([^\{]*\{)/g, function (m) {
+ return m.replace(":", "___YUICSSMIN_PSEUDOCLASSCOLON___");
+ });
+ css = css.replace(/\s+([!{};:>+\(\)\],])/g, '$1');
+ css = css.replace(/___YUICSSMIN_PSEUDOCLASSCOLON___/g, ":");
+
+ // retain space for special IE6 cases and lowercase
+ css = css.replace(/:first-(line|letter)(\{|,)/gi, function(all, $1, $2) {
+ return ":first-" + $1.toLowerCase() + " " + $2;
+ });
+
+ // no space after the end of a preserved comment
+ css = css.replace(/\*\/ /g, '*/');
+
+ // If there is a @charset, then only allow one, and push to the top of the file (and make lowercase).
+ css = css.replace(/^(.*)(@charset)( "[^"]*";)/gi, function(all, $1, $2, $3) {
+ return $2.toLowerCase() + $3 + $1;
+ });
+ css = css.replace(/^((\s*)(@charset)( [^;]+;\s*))+/gi, function(all, $1, $2, $3, $4) {
+ return $2 + $3.toLowerCase() + $4;
+ });
+
+ // Put the space back in some cases, to support stuff like
+ // @media screen and (-webkit-min-device-pixel-ratio:0){
+ css = css.replace(/\band\(/gi, "and (");
+
+ // lowercase some popular @directives (@charset is done right above)
+ css = css.replace(/@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)/gi, function(all, $1) {
+ return '@' + $1.toLowerCase();
+ });
+
+ // lowercase some more common pseudo-elements
+ css = css.replace(/:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/gi, function(all, $1) {
+ return ':' + $1.toLowerCase();
+ });
+
+ // lowercase some more common functions
+ css = css.replace(/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\(/gi, function(all, $1) {
+ return ':' + $1.toLowerCase() + '(';
+ });
+
+ // lower case some common function that can be values
+ // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us
+ css = css.replace(/([:,\( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)/gi, function(all, $1, $2) {
+ return $1 + $2.toLowerCase();
+ });
+
+ // Remove the spaces after the things that should not have spaces after them.
+ css = css.replace(/([!{}:;>+\(\[,])\s+/g, '$1');
+
+ // remove unnecessary semicolons
+ css = css.replace(/;+\}/g, "}");
+
+ // Replace 0(px,em,%) with 0.
+ css = css.replace(/(^|[^0-9])(?:0?\.)?0(?:px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz)/gi, "$10");
+
+ // Replace 0 0 0 0; with 0.
+ css = css.replace(/:0 0 0 0(;|\})/g, ":0$1");
+ css = css.replace(/:0 0 0(;|\})/g, ":0$1");
+ css = css.replace(/:0 0(;|\})/g, ":0$1");
+
+ // Replace background-position:0; with background-position:0 0;
+ // same for transform-origin
+ css = css.replace(/(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|\})/gi, function(all, prop, tail) {
+ return prop.toLowerCase() + ":0 0" + tail;
+ });
+
+ // Replace 0.6 to .6, but only when preceded by : or a white-space
+ css = css.replace(/(:|\s)0+\.(\d+)/g, "$1.$2");
+
+ // Shorten colors from rgb(51,102,153) to #336699
+ // This makes it more likely that it'll get further compressed in the next step.
+ css = css.replace(/rgb\s*\(\s*([0-9,\s]+)\s*\)/gi, function () {
+ var i, rgbcolors = arguments[1].split(',');
+ for (i = 0; i < rgbcolors.length; i = i + 1) {
+ rgbcolors[i] = parseInt(rgbcolors[i], 10).toString(16);
+ if (rgbcolors[i].length === 1) {
+ rgbcolors[i] = '0' + rgbcolors[i];
+ }
+ }
+ return '#' + rgbcolors.join('');
+ });
+
+ // Shorten colors from #AABBCC to #ABC.
+ css = this._compressHexColors(css);
+
+ // Shorten color from #f00 to red
+ css = css.replace(/(:|\s)(#f00)(;|})/g, "$1red$3");
+ // Other colors
+ css = css.replace(/(:|\s)(#000080)(;|})/g, "$1navy$3");
+ css = css.replace(/(:|\s)(#808080)(;|})/g, "$1gray$3");
+ css = css.replace(/(:|\s)(#808000)(;|})/g, "$1olive$3");
+ css = css.replace(/(:|\s)(#800080)(;|})/g, "$1purple$3");
+ css = css.replace(/(:|\s)(#c0c0c0)(;|})/g, "$1silver$3");
+ css = css.replace(/(:|\s)(#008080)(;|})/g, "$1teal$3");
+ css = css.replace(/(:|\s)(#ffa500)(;|})/g, "$1orange$3");
+ css = css.replace(/(:|\s)(#800000)(;|})/g, "$1maroon$3");
+
+ // border: none -> border:0
+ css = css.replace(/(border|border-top|border-right|border-bottom|border-left|outline|background):none(;|\})/gi, function(all, prop, tail) {
+ return prop.toLowerCase() + ":0" + tail;
+ });
+
+ // shorter opacity IE filter
+ css = css.replace(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi, "alpha(opacity=");
+
+ // Remove empty rules.
+ css = css.replace(/[^\};\{\/]+\{\}/g, "");
+
+ if (linebreakpos >= 0) {
+ // Some source control tools don't like it when files containing lines longer
+ // than, say 8000 characters, are checked in. The linebreak option is used in
+ // that case to split long lines after a specific column.
+ startIndex = 0;
+ i = 0;
+ while (i < css.length) {
+ i = i + 1;
+ if (css[i - 1] === '}' && i - startIndex > linebreakpos) {
+ css = css.slice(0, i) + '\n' + css.slice(i);
+ startIndex = i;
+ }
+ }
+ }
+
+ // Replace multiple semi-colons in a row by a single one
+ // See SF bug #1980989
+ css = css.replace(/;;+/g, ";");
+
+ // restore preserved comments and strings
+ for (i = 0, max = preservedTokens.length; i < max; i = i + 1) {
+ css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens[i]);
+ }
+
+ // Trim the final string (for any leading or trailing white spaces)
+ css = css.replace(/^\s+|\s+$/g, "");
+
+ return css;
+
+};
diff --git a/src/com/yahoo/platform/yui/compressor/Bootstrap.java b/src/com/yahoo/platform/yui/compressor/Bootstrap.java
new file mode 100644
index 0000000..666bda3
--- /dev/null
+++ b/src/com/yahoo/platform/yui/compressor/Bootstrap.java
@@ -0,0 +1,23 @@
+/*
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+
+package com.yahoo.platform.yui.compressor;
+
+import java.lang.reflect.Method;
+
+public class Bootstrap {
+
+ public static void main(String args[]) throws Exception {
+ ClassLoader loader = new JarClassLoader();
+ Thread.currentThread().setContextClassLoader(loader);
+ Class c = loader.loadClass(YUICompressor.class.getName());
+ Method main = c.getMethod("main", new Class[]{String[].class});
+ main.invoke(null, new Object[]{args});
+ }
+}
diff --git a/src/com/yahoo/platform/yui/compressor/CssCompressor.java b/src/com/yahoo/platform/yui/compressor/CssCompressor.java
new file mode 100644
index 0000000..d3847af
--- /dev/null
+++ b/src/com/yahoo/platform/yui/compressor/CssCompressor.java
@@ -0,0 +1,468 @@
+/*
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Author: Isaac Schlueter - http://foohack.com/
+ * Author: Stoyan Stefanov - http://phpied.com/
+ * Contributor: Dan Beam - http://danbeam.org/
+ * Copyright (c) 2013 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+package com.yahoo.platform.yui.compressor;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import java.util.ArrayList;
+
+public class CssCompressor {
+
+ private StringBuffer srcsb = new StringBuffer();
+
+ public CssCompressor(Reader in) throws IOException {
+ // Read the stream...
+ int c;
+ while ((c = in.read()) != -1) {
+ srcsb.append((char) c);
+ }
+ }
+
+ // Leave data urls alone to increase parse performance.
+ protected String extractDataUrls(String css, ArrayList preservedTokens) {
+
+ int maxIndex = css.length() - 1;
+ int appendIndex = 0;
+
+ StringBuffer sb = new StringBuffer();
+
+ Pattern p = Pattern.compile("(?i)url\\(\\s*([\"']?)data\\:");
+ Matcher m = p.matcher(css);
+
+ /*
+ * Since we need to account for non-base64 data urls, we need to handle
+ * ' and ) being part of the data string. Hence switching to indexOf,
+ * to determine whether or not we have matching string terminators and
+ * handling sb appends directly, instead of using matcher.append* methods.
+ */
+
+ while (m.find()) {
+
+ int startIndex = m.start() + 4; // "url(".length()
+ String terminator = m.group(1); // ', " or empty (not quoted)
+
+ if (terminator.length() == 0) {
+ terminator = ")";
+ }
+
+ boolean foundTerminator = false;
+
+ int endIndex = m.end() - 1;
+ while(foundTerminator == false && endIndex+1 <= maxIndex) {
+ endIndex = css.indexOf(terminator, endIndex+1);
+
+ if ((endIndex > 0) && (css.charAt(endIndex-1) != '\\')) {
+ foundTerminator = true;
+ if (!")".equals(terminator)) {
+ endIndex = css.indexOf(")", endIndex);
+ }
+ }
+ }
+
+ // Enough searching, start moving stuff over to the buffer
+ sb.append(css.substring(appendIndex, m.start()));
+
+ if (foundTerminator) {
+ String token = css.substring(startIndex, endIndex);
+ token = token.replaceAll("\\s+", "");
+ preservedTokens.add(token);
+
+ String preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___)";
+ sb.append(preserver);
+
+ appendIndex = endIndex + 1;
+ } else {
+ // No end terminator found, re-add the whole match. Should we throw/warn here?
+ sb.append(css.substring(m.start(), m.end()));
+ appendIndex = m.end();
+ }
+ }
+
+ sb.append(css.substring(appendIndex));
+
+ return sb.toString();
+ }
+
+ public void compress(Writer out, int linebreakpos)
+ throws IOException {
+
+ Pattern p;
+ Matcher m;
+ String css = srcsb.toString();
+
+ int startIndex = 0;
+ int endIndex = 0;
+ int i = 0;
+ int max = 0;
+ ArrayList preservedTokens = new ArrayList(0);
+ ArrayList comments = new ArrayList(0);
+ String token;
+ int totallen = css.length();
+ String placeholder;
+
+ css = this.extractDataUrls(css, preservedTokens);
+
+ StringBuffer sb = new StringBuffer(css);
+
+ // collect all comment blocks...
+ while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) {
+ endIndex = sb.indexOf("*/", startIndex + 2);
+ if (endIndex < 0) {
+ endIndex = totallen;
+ }
+
+ token = sb.substring(startIndex + 2, endIndex);
+ comments.add(token);
+ sb.replace(startIndex + 2, endIndex, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___");
+ startIndex += 2;
+ }
+ css = sb.toString();
+
+ // preserve strings so their content doesn't get accidentally minified
+ sb = new StringBuffer();
+ p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')");
+ m = p.matcher(css);
+ while (m.find()) {
+ token = m.group();
+ char quote = token.charAt(0);
+ token = token.substring(1, token.length() - 1);
+
+ // maybe the string contains a comment-like substring?
+ // one, maybe more? put'em back then
+ if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) {
+ for (i = 0, max = comments.size(); i < max; i += 1) {
+ token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i).toString());
+ }
+ }
+
+ // minify alpha opacity in filter strings
+ token = token.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
+
+ preservedTokens.add(token);
+ String preserver = quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote;
+ m.appendReplacement(sb, preserver);
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+
+ // strings are safe, now wrestle the comments
+ for (i = 0, max = comments.size(); i < max; i += 1) {
+
+ token = comments.get(i).toString();
+ placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
+
+ // ! in the first position of the comment means preserve
+ // so push to the preserved tokens while stripping the !
+ if (token.startsWith("!")) {
+ preservedTokens.add(token);
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ continue;
+ }
+
+ // \ in the last position looks like hack for Mac/IE5
+ // shorten that to /*\*/ and the next one to /**/
+ if (token.endsWith("\\")) {
+ preservedTokens.add("\\");
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ i = i + 1; // attn: advancing the loop
+ preservedTokens.add("");
+ css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ continue;
+ }
+
+ // keep empty comments after child selectors (IE7 hack)
+ // e.g. html >/**/ body
+ if (token.length() == 0) {
+ startIndex = css.indexOf(placeholder);
+ if (startIndex > 2) {
+ if (css.charAt(startIndex - 3) == '>') {
+ preservedTokens.add("");
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ }
+ }
+ }
+
+ // in all other cases kill the comment
+ css = css.replace("/*" + placeholder + "*/", "");
+ }
+
+
+ // Normalize all whitespace strings to single spaces. Easier to work with that way.
+ css = css.replaceAll("\\s+", " ");
+
+ // Remove the spaces before the things that should not have spaces before them.
+ // But, be careful not to turn "p :link {...}" into "p:link{...}"
+ // Swap out any pseudo-class colons with the token, and then swap back.
+ sb = new StringBuffer();
+ p = Pattern.compile("(^|\\})(([^\\{:])+:)+([^\\{]*\\{)");
+ m = p.matcher(css);
+ while (m.find()) {
+ String s = m.group();
+ s = s.replaceAll(":", "___YUICSSMIN_PSEUDOCLASSCOLON___");
+ s = s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\\$", "\\\\\\$" );
+ m.appendReplacement(sb, s);
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+ // Remove spaces before the things that should not have spaces before them.
+ css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1");
+ // Restore spaces for !important
+ css = css.replaceAll("!important", " !important");
+ // bring back the colon
+ css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":");
+
+ // retain space for special IE6 cases
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i):first\\-(line|letter)(\\{|,)");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, ":first-" + m.group(1).toLowerCase() + " " + m.group(2));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // no space after the end of a preserved comment
+ css = css.replaceAll("\\*/ ", "*/");
+
+ // If there are multiple @charset directives, push them to the top of the file.
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)^(.*)(@charset)( \"[^\"]*\";)");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(2).toLowerCase() + m.group(3) + m.group(1));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // When all @charset are at the top, remove the second and after (as they are completely ignored).
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)^((\\s*)(@charset)( [^;]+;\\s*))+");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(2) + m.group(3).toLowerCase() + m.group(4));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // lowercase some popular @directives (@charset is done right above)
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, '@' + m.group(1).toLowerCase());
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // lowercase some more common pseudo-elements
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i):(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, ':' + m.group(1).toLowerCase());
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // lowercase some more common functions
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i):(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\\(");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, ':' + m.group(1).toLowerCase() + '(');
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // lower case some common function that can be values
+ // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us right after this
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)([:,\\( ]\\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(1) + m.group(2).toLowerCase());
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // Put the space back in some cases, to support stuff like
+ // @media screen and (-webkit-min-device-pixel-ratio:0){
+ css = css.replaceAll("(?i)\\band\\(", "and (");
+
+ // Remove the spaces after the things that should not have spaces after them.
+ css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");
+
+ // remove unnecessary semicolons
+ css = css.replaceAll(";+}", "}");
+
+ // Replace 0(px,em,%) with 0.
+ css = css.replaceAll("(?i)(^|[^0-9])(?:0?\\.)?0(?:px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz)", "$10");
+
+ // Replace 0 0 0 0; with 0.
+ css = css.replaceAll(":0 0 0 0(;|})", ":0$1");
+ css = css.replaceAll(":0 0 0(;|})", ":0$1");
+ css = css.replaceAll(":0 0(;|})", ":0$1");
+
+
+ // Replace background-position:0; with background-position:0 0;
+ // same for transform-origin
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)(background-position|webkit-mask-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|})");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(1).toLowerCase() + ":0 0" + m.group(2));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // Replace 0.6 to .6, but only when preceded by : or a white-space
+ css = css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2");
+
+ // Shorten colors from rgb(51,102,153) to #336699
+ // This makes it more likely that it'll get further compressed in the next step.
+ p = Pattern.compile("rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)");
+ m = p.matcher(css);
+ sb = new StringBuffer();
+ while (m.find()) {
+ String[] rgbcolors = m.group(1).split(",");
+ StringBuffer hexcolor = new StringBuffer("#");
+ for (i = 0; i < rgbcolors.length; i++) {
+ int val = Integer.parseInt(rgbcolors[i]);
+ if (val < 16) {
+ hexcolor.append("0");
+ }
+ hexcolor.append(Integer.toHexString(val));
+ }
+ m.appendReplacement(sb, hexcolor.toString());
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // Shorten colors from #AABBCC to #ABC. Note that we want to make sure
+ // the color is not preceded by either ", " or =. Indeed, the property
+ // filter: chroma(color="#FFFFFF");
+ // would become
+ // filter: chroma(color="#FFF");
+ // which makes the filter break in IE.
+ // We also want to make sure we're only compressing #AABBCC patterns inside { }, not id selectors ( #FAABAC {} )
+ // We also want to avoid compressing invalid values (e.g. #AABBCCD to #ABCD)
+ p = Pattern.compile("(\\=\\s*?[\"']?)?" + "#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])" + "(:?\\}|[^0-9a-fA-F{][^{]*?\\})");
+
+ m = p.matcher(css);
+ sb = new StringBuffer();
+ int index = 0;
+
+ while (m.find(index)) {
+
+ sb.append(css.substring(index, m.start()));
+
+ boolean isFilter = (m.group(1) != null && !"".equals(m.group(1)));
+
+ if (isFilter) {
+ // Restore, as is. Compression will break filters
+ sb.append(m.group(1) + "#" + m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7));
+ } else {
+ if( m.group(2).equalsIgnoreCase(m.group(3)) &&
+ m.group(4).equalsIgnoreCase(m.group(5)) &&
+ m.group(6).equalsIgnoreCase(m.group(7))) {
+
+ // #AABBCC pattern
+ sb.append("#" + (m.group(3) + m.group(5) + m.group(7)).toLowerCase());
+
+ } else {
+
+ // Non-compressible color, restore, but lower case.
+ sb.append("#" + (m.group(2) + m.group(3) + m.group(4) + m.group(5) + m.group(6) + m.group(7)).toLowerCase());
+ }
+ }
+
+ index = m.end(7);
+ }
+
+ sb.append(css.substring(index));
+ css = sb.toString();
+
+ // Replace #f00 -> red
+ css = css.replaceAll("(:|\\s)(#f00)(;|})", "$1red$3");
+ // Replace other short color keywords
+ css = css.replaceAll("(:|\\s)(#000080)(;|})", "$1navy$3");
+ css = css.replaceAll("(:|\\s)(#808080)(;|})", "$1gray$3");
+ css = css.replaceAll("(:|\\s)(#808000)(;|})", "$1olive$3");
+ css = css.replaceAll("(:|\\s)(#800080)(;|})", "$1purple$3");
+ css = css.replaceAll("(:|\\s)(#c0c0c0)(;|})", "$1silver$3");
+ css = css.replaceAll("(:|\\s)(#008080)(;|})", "$1teal$3");
+ css = css.replaceAll("(:|\\s)(#ffa500)(;|})", "$1orange$3");
+ css = css.replaceAll("(:|\\s)(#800000)(;|})", "$1maroon$3");
+
+ // border: none -> border:0
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-left|outline|background):none(;|})");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(1).toLowerCase() + ":0" + m.group(2));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // shorter opacity IE filter
+ css = css.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
+
+ // Find a fraction that is used for Opera's -o-device-pixel-ratio query
+ // Add token to add the "\" back in later
+ css = css.replaceAll("\\(([\\-A-Za-z]+):([0-9]+)\\/([0-9]+)\\)", "($1:$2___YUI_QUERY_FRACTION___$3)");
+
+ // Remove empty rules.
+ css = css.replaceAll("[^\\}\\{/;]+\\{\\}", "");
+
+ // Add "\" back to fix Opera -o-device-pixel-ratio query
+ css = css.replaceAll("___YUI_QUERY_FRACTION___", "/");
+
+ // TODO: Should this be after we re-insert tokens. These could alter the break points. However then
+ // we'd need to make sure we don't break in the middle of a string etc.
+ if (linebreakpos >= 0) {
+ // Some source control tools don't like it when files containing lines longer
+ // than, say 8000 characters, are checked in. The linebreak option is used in
+ // that case to split long lines after a specific column.
+ i = 0;
+ int linestartpos = 0;
+ sb = new StringBuffer(css);
+ while (i < sb.length()) {
+ char c = sb.charAt(i++);
+ if (c == '}' && i - linestartpos > linebreakpos) {
+ sb.insert(i, '\n');
+ linestartpos = i;
+ }
+ }
+
+ css = sb.toString();
+ }
+
+ // Replace multiple semi-colons in a row by a single one
+ // See SF bug #1980989
+ css = css.replaceAll(";;+", ";");
+
+ // restore preserved comments and strings
+ for(i = 0, max = preservedTokens.size(); i < max; i++) {
+ css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString());
+ }
+
+ // Trim the final string (for any leading or trailing white spaces)
+ css = css.trim();
+
+ // Write the output...
+ out.write(css);
+ }
+}
diff --git a/src/com/yahoo/platform/yui/compressor/JarClassLoader.java b/src/com/yahoo/platform/yui/compressor/JarClassLoader.java
new file mode 100644
index 0000000..7bd38c1
--- /dev/null
+++ b/src/com/yahoo/platform/yui/compressor/JarClassLoader.java
@@ -0,0 +1,158 @@
+/*
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+package com.yahoo.platform.yui.compressor;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+public class JarClassLoader extends ClassLoader {
+
+ private static String jarPath;
+
+ public Class loadClass(String name) throws ClassNotFoundException {
+
+ // First check if the class is already loaded
+ Class c = findLoadedClass(name);
+ if (c == null) {
+ c = findClass(name);
+ }
+
+ if (c == null) {
+ c = ClassLoader.getSystemClassLoader().loadClass(name);
+ }
+
+ return c;
+ }
+
+ private static String getJarPath() {
+
+ if (jarPath != null) {
+ return jarPath;
+ }
+
+ String classname = JarClassLoader.class.getName().replace('.', '/') + ".class";
+ String classpath = System.getProperty("java.class.path");
+ String classpaths[] = classpath.split(System.getProperty("path.separator"));
+
+ for (int i = 0; i < classpaths.length; i++) {
+
+ String path = classpaths[i];
+ JarFile jarFile = null;
+ JarEntry jarEntry = null;
+
+ try {
+ jarFile = new JarFile(path);
+ jarEntry = findJarEntry(jarFile, classname);
+ } catch (IOException ioe) {
+ /* ignore */
+ } finally {
+ if (jarFile != null) {
+ try {
+ jarFile.close();
+ } catch (IOException ioe) {
+ /* ignore */
+ }
+ }
+ }
+
+ if (jarEntry != null) {
+ jarPath = path;
+ break;
+ }
+ }
+
+ return jarPath;
+ }
+
+ private static JarEntry findJarEntry(JarFile jarFile, String entryName) {
+
+ Enumeration entries = jarFile.entries();
+
+ while (entries.hasMoreElements()) {
+ JarEntry entry = (JarEntry) entries.nextElement();
+ if (entry.getName().equals(entryName)) {
+ return entry;
+ }
+ }
+
+ return null;
+ }
+
+ protected Class findClass(String name) {
+
+ Class c = null;
+ String jarPath = getJarPath();
+
+ if (jarPath != null) {
+ JarFile jarFile = null;
+ try {
+ jarFile = new JarFile(jarPath);
+ c = loadClassData(jarFile, name);
+ } catch (IOException ioe) {
+ /* ignore */
+ } finally {
+ if (jarFile != null) {
+ try {
+ jarFile.close();
+ } catch (IOException ioe) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ return c;
+ }
+
+ private Class loadClassData(JarFile jarFile, String className) {
+
+ String entryName = className.replace('.', '/') + ".class";
+ JarEntry jarEntry = findJarEntry(jarFile, entryName);
+ if (jarEntry == null) {
+ return null;
+ }
+
+ // Create the necessary package if needed...
+ int index = className.lastIndexOf('.');
+ if (index >= 0) {
+ String packageName = className.substring(0, index);
+ if (getPackage(packageName) == null) {
+ definePackage(packageName, "", "", "", "", "", "", null);
+ }
+ }
+
+ // Read the Jar File entry and define the class...
+ Class c = null;
+ try {
+ InputStream is = jarFile.getInputStream(jarEntry);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ copy(is, os);
+ byte[] bytes = os.toByteArray();
+ c = defineClass(className, bytes, 0, bytes.length);
+ } catch (IOException ioe) {
+ /* ignore */
+ }
+
+ return c;
+ }
+
+ private void copy(InputStream in, OutputStream out) throws IOException {
+ byte[] buf = new byte[1024];
+ while (true) {
+ int len = in.read(buf);
+ if (len < 0) break;
+ out.write(buf, 0, len);
+ }
+ }
+}
diff --git a/src/com/yahoo/platform/yui/compressor/JavaScriptCompressor.java b/src/com/yahoo/platform/yui/compressor/JavaScriptCompressor.java
new file mode 100644
index 0000000..18b1c64
--- /dev/null
+++ b/src/com/yahoo/platform/yui/compressor/JavaScriptCompressor.java
@@ -0,0 +1,1337 @@
+/*
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+package com.yahoo.platform.yui.compressor;
+
+import org.mozilla.javascript.*;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JavaScriptCompressor {
+
+ static final ArrayList ones;
+ static final ArrayList twos;
+ static final ArrayList threes;
+
+ static final Set builtin = new HashSet();
+ static final Map literals = new Hashtable();
+ static final Set reserved = new HashSet();
+
+ static {
+
+ // This list contains all the 3 characters or less built-in global
+ // symbols available in a browser. Please add to this list if you
+ // see anything missing.
+ builtin.add("NaN");
+ builtin.add("top");
+
+ ones = new ArrayList();
+ for (char c = 'a'; c <= 'z'; c++)
+ ones.add(Character.toString(c));
+ for (char c = 'A'; c <= 'Z'; c++)
+ ones.add(Character.toString(c));
+
+ twos = new ArrayList();
+ for (int i = 0; i < ones.size(); i++) {
+ String one = (String) ones.get(i);
+ for (char c = 'a'; c <= 'z'; c++)
+ twos.add(one + Character.toString(c));
+ for (char c = 'A'; c <= 'Z'; c++)
+ twos.add(one + Character.toString(c));
+ for (char c = '0'; c <= '9'; c++)
+ twos.add(one + Character.toString(c));
+ }
+
+ // Remove two-letter JavaScript reserved words and built-in globals...
+ twos.remove("as");
+ twos.remove("is");
+ twos.remove("do");
+ twos.remove("if");
+ twos.remove("in");
+ twos.removeAll(builtin);
+
+ threes = new ArrayList();
+ for (int i = 0; i < twos.size(); i++) {
+ String two = (String) twos.get(i);
+ for (char c = 'a'; c <= 'z'; c++)
+ threes.add(two + Character.toString(c));
+ for (char c = 'A'; c <= 'Z'; c++)
+ threes.add(two + Character.toString(c));
+ for (char c = '0'; c <= '9'; c++)
+ threes.add(two + Character.toString(c));
+ }
+
+ // Remove three-letter JavaScript reserved words and built-in globals...
+ threes.remove("for");
+ threes.remove("int");
+ threes.remove("new");
+ threes.remove("try");
+ threes.remove("use");
+ threes.remove("var");
+ threes.removeAll(builtin);
+
+ // That's up to ((26+26)*(1+(26+26+10)))*(1+(26+26+10))-8
+ // (206,380 symbols per scope)
+
+ // The following list comes from org/mozilla/javascript/Decompiler.java...
+ literals.put(new Integer(Token.GET), "get ");
+ literals.put(new Integer(Token.SET), "set ");
+ literals.put(new Integer(Token.TRUE), "true");
+ literals.put(new Integer(Token.FALSE), "false");
+ literals.put(new Integer(Token.NULL), "null");
+ literals.put(new Integer(Token.THIS), "this");
+ literals.put(new Integer(Token.FUNCTION), "function");
+ literals.put(new Integer(Token.COMMA), ",");
+ literals.put(new Integer(Token.LC), "{");
+ literals.put(new Integer(Token.RC), "}");
+ literals.put(new Integer(Token.LP), "(");
+ literals.put(new Integer(Token.RP), ")");
+ literals.put(new Integer(Token.LB), "[");
+ literals.put(new Integer(Token.RB), "]");
+ literals.put(new Integer(Token.DOT), ".");
+ literals.put(new Integer(Token.NEW), "new ");
+ literals.put(new Integer(Token.DELPROP), "delete ");
+ literals.put(new Integer(Token.IF), "if");
+ literals.put(new Integer(Token.ELSE), "else");
+ literals.put(new Integer(Token.FOR), "for");
+ literals.put(new Integer(Token.IN), " in ");
+ literals.put(new Integer(Token.WITH), "with");
+ literals.put(new Integer(Token.WHILE), "while");
+ literals.put(new Integer(Token.DO), "do");
+ literals.put(new Integer(Token.TRY), "try");
+ literals.put(new Integer(Token.CATCH), "catch");
+ literals.put(new Integer(Token.FINALLY), "finally");
+ literals.put(new Integer(Token.THROW), "throw");
+ literals.put(new Integer(Token.SWITCH), "switch");
+ literals.put(new Integer(Token.BREAK), "break");
+ literals.put(new Integer(Token.CONTINUE), "continue");
+ literals.put(new Integer(Token.CASE), "case");
+ literals.put(new Integer(Token.DEFAULT), "default");
+ literals.put(new Integer(Token.RETURN), "return");
+ literals.put(new Integer(Token.VAR), "var ");
+ literals.put(new Integer(Token.SEMI), ";");
+ literals.put(new Integer(Token.ASSIGN), "=");
+ literals.put(new Integer(Token.ASSIGN_ADD), "+=");
+ literals.put(new Integer(Token.ASSIGN_SUB), "-=");
+ literals.put(new Integer(Token.ASSIGN_MUL), "*=");
+ literals.put(new Integer(Token.ASSIGN_DIV), "/=");
+ literals.put(new Integer(Token.ASSIGN_MOD), "%=");
+ literals.put(new Integer(Token.ASSIGN_BITOR), "|=");
+ literals.put(new Integer(Token.ASSIGN_BITXOR), "^=");
+ literals.put(new Integer(Token.ASSIGN_BITAND), "&=");
+ literals.put(new Integer(Token.ASSIGN_LSH), "<<=");
+ literals.put(new Integer(Token.ASSIGN_RSH), ">>=");
+ literals.put(new Integer(Token.ASSIGN_URSH), ">>>=");
+ literals.put(new Integer(Token.HOOK), "?");
+ literals.put(new Integer(Token.OBJECTLIT), ":");
+ literals.put(new Integer(Token.COLON), ":");
+ literals.put(new Integer(Token.OR), "||");
+ literals.put(new Integer(Token.AND), "&&");
+ literals.put(new Integer(Token.BITOR), "|");
+ literals.put(new Integer(Token.BITXOR), "^");
+ literals.put(new Integer(Token.BITAND), "&");
+ literals.put(new Integer(Token.SHEQ), "===");
+ literals.put(new Integer(Token.SHNE), "!==");
+ literals.put(new Integer(Token.EQ), "==");
+ literals.put(new Integer(Token.NE), "!=");
+ literals.put(new Integer(Token.LE), "<=");
+ literals.put(new Integer(Token.LT), "<");
+ literals.put(new Integer(Token.GE), ">=");
+ literals.put(new Integer(Token.GT), ">");
+ literals.put(new Integer(Token.INSTANCEOF), " instanceof ");
+ literals.put(new Integer(Token.LSH), "<<");
+ literals.put(new Integer(Token.RSH), ">>");
+ literals.put(new Integer(Token.URSH), ">>>");
+ literals.put(new Integer(Token.TYPEOF), "typeof");
+ literals.put(new Integer(Token.VOID), "void ");
+ literals.put(new Integer(Token.CONST), "const ");
+ literals.put(new Integer(Token.NOT), "!");
+ literals.put(new Integer(Token.BITNOT), "~");
+ literals.put(new Integer(Token.POS), "+");
+ literals.put(new Integer(Token.NEG), "-");
+ literals.put(new Integer(Token.INC), "++");
+ literals.put(new Integer(Token.DEC), "--");
+ literals.put(new Integer(Token.ADD), "+");
+ literals.put(new Integer(Token.SUB), "-");
+ literals.put(new Integer(Token.MUL), "*");
+ literals.put(new Integer(Token.DIV), "/");
+ literals.put(new Integer(Token.MOD), "%");
+ literals.put(new Integer(Token.COLONCOLON), "::");
+ literals.put(new Integer(Token.DOTDOT), "..");
+ literals.put(new Integer(Token.DOTQUERY), ".(");
+ literals.put(new Integer(Token.XMLATTR), "@");
+ literals.put(new Integer(Token.LET), "let ");
+ literals.put(new Integer(Token.YIELD), "yield ");
+
+ // See http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Reserved_Words
+
+ // JavaScript 1.5 reserved words
+ reserved.add("break");
+ reserved.add("case");
+ reserved.add("catch");
+ reserved.add("continue");
+ reserved.add("default");
+ reserved.add("delete");
+ reserved.add("do");
+ reserved.add("else");
+ reserved.add("finally");
+ reserved.add("for");
+ reserved.add("function");
+ reserved.add("if");
+ reserved.add("in");
+ reserved.add("instanceof");
+ reserved.add("new");
+ reserved.add("return");
+ reserved.add("switch");
+ reserved.add("this");
+ reserved.add("throw");
+ reserved.add("try");
+ reserved.add("typeof");
+ reserved.add("var");
+ reserved.add("void");
+ reserved.add("while");
+ reserved.add("with");
+ // Words reserved for future use
+ reserved.add("abstract");
+ reserved.add("boolean");
+ reserved.add("byte");
+ reserved.add("char");
+ reserved.add("class");
+ reserved.add("const");
+ reserved.add("debugger");
+ reserved.add("double");
+ reserved.add("enum");
+ reserved.add("export");
+ reserved.add("extends");
+ reserved.add("final");
+ reserved.add("float");
+ reserved.add("goto");
+ reserved.add("implements");
+ reserved.add("import");
+ reserved.add("int");
+ reserved.add("interface");
+ reserved.add("long");
+ reserved.add("native");
+ reserved.add("package");
+ reserved.add("private");
+ reserved.add("protected");
+ reserved.add("public");
+ reserved.add("short");
+ reserved.add("static");
+ reserved.add("super");
+ reserved.add("synchronized");
+ reserved.add("throws");
+ reserved.add("transient");
+ reserved.add("volatile");
+ // These are not reserved, but should be taken into account
+ // in isValidIdentifier (See jslint source code)
+ reserved.add("arguments");
+ reserved.add("eval");
+ reserved.add("true");
+ reserved.add("false");
+ reserved.add("Infinity");
+ reserved.add("NaN");
+ reserved.add("null");
+ reserved.add("undefined");
+ }
+
+ private static int countChar(String haystack, char needle) {
+ int idx = 0;
+ int count = 0;
+ int length = haystack.length();
+ while (idx < length) {
+ char c = haystack.charAt(idx++);
+ if (c == needle) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private static int printSourceString(String source, int offset, StringBuffer sb) {
+ int length = source.charAt(offset);
+ ++offset;
+ if ((0x8000 & length) != 0) {
+ length = ((0x7FFF & length) << 16) | source.charAt(offset);
+ ++offset;
+ }
+ if (sb != null) {
+ String str = source.substring(offset, offset + length);
+ sb.append(str);
+ }
+ return offset + length;
+ }
+
+ private static int printSourceNumber(String source,
+ int offset, StringBuffer sb) {
+ double number = 0.0;
+ char type = source.charAt(offset);
+ ++offset;
+ if (type == 'S') {
+ if (sb != null) {
+ number = source.charAt(offset);
+ }
+ ++offset;
+ } else if (type == 'J' || type == 'D') {
+ if (sb != null) {
+ long lbits;
+ lbits = (long) source.charAt(offset) << 48;
+ lbits |= (long) source.charAt(offset + 1) << 32;
+ lbits |= (long) source.charAt(offset + 2) << 16;
+ lbits |= (long) source.charAt(offset + 3);
+ if (type == 'J') {
+ number = lbits;
+ } else {
+ number = Double.longBitsToDouble(lbits);
+ }
+ }
+ offset += 4;
+ } else {
+ // Bad source
+ throw new RuntimeException();
+ }
+ if (sb != null) {
+ sb.append(ScriptRuntime.numberToString(number, 10));
+ }
+ return offset;
+ }
+
+ private static ArrayList parse(Reader in, ErrorReporter reporter)
+ throws IOException, EvaluatorException {
+
+ CompilerEnvirons env = new CompilerEnvirons();
+ env.setLanguageVersion(Context.VERSION_1_7);
+ Parser parser = new Parser(env, reporter);
+ parser.parse(in, null, 1);
+ String source = parser.getEncodedSource();
+
+ int offset = 0;
+ int length = source.length();
+ ArrayList tokens = new ArrayList();
+ StringBuffer sb = new StringBuffer();
+
+ while (offset < length) {
+ int tt = source.charAt(offset++);
+ switch (tt) {
+
+ case Token.CONDCOMMENT:
+ case Token.KEEPCOMMENT:
+ case Token.NAME:
+ case Token.REGEXP:
+ case Token.STRING:
+ sb.setLength(0);
+ offset = printSourceString(source, offset, sb);
+ tokens.add(new JavaScriptToken(tt, sb.toString()));
+ break;
+
+ case Token.NUMBER:
+ sb.setLength(0);
+ offset = printSourceNumber(source, offset, sb);
+ tokens.add(new JavaScriptToken(tt, sb.toString()));
+ break;
+
+ default:
+ String literal = (String) literals.get(new Integer(tt));
+ if (literal != null) {
+ tokens.add(new JavaScriptToken(tt, literal));
+ }
+ break;
+ }
+ }
+
+ return tokens;
+ }
+
+ private static void processStringLiterals(ArrayList tokens, boolean merge) {
+
+ String tv;
+ int i, length = tokens.size();
+ JavaScriptToken token, prevToken, nextToken;
+
+ if (merge) {
+
+ // Concatenate string literals that are being appended wherever
+ // it is safe to do so. Note that we take care of the case:
+ // "a" + "b".toUpperCase()
+
+ for (i = 0; i < length; i++) {
+ token = (JavaScriptToken) tokens.get(i);
+ switch (token.getType()) {
+
+ case Token.ADD:
+ if (i > 0 && i < length) {
+ prevToken = (JavaScriptToken) tokens.get(i - 1);
+ nextToken = (JavaScriptToken) tokens.get(i + 1);
+ if (prevToken.getType() == Token.STRING && nextToken.getType() == Token.STRING &&
+ (i == length - 1 || ((JavaScriptToken) tokens.get(i + 2)).getType() != Token.DOT)) {
+ tokens.set(i - 1, new JavaScriptToken(Token.STRING,
+ prevToken.getValue() + nextToken.getValue()));
+ tokens.remove(i + 1);
+ tokens.remove(i);
+ i = i - 1;
+ length = length - 2;
+ break;
+ }
+ }
+ }
+ }
+
+ }
+
+ // Second pass...
+
+ for (i = 0; i < length; i++) {
+ token = (JavaScriptToken) tokens.get(i);
+ if (token.getType() == Token.STRING) {
+ tv = token.getValue();
+
+ // Finally, add the quoting characters and escape the string. We use
+ // the quoting character that minimizes the amount of escaping to save
+ // a few additional bytes.
+
+ char quotechar;
+ int singleQuoteCount = countChar(tv, '\'');
+ int doubleQuoteCount = countChar(tv, '"');
+ if (doubleQuoteCount <= singleQuoteCount) {
+ quotechar = '"';
+ } else {
+ quotechar = '\'';
+ }
+
+ tv = quotechar + escapeString(tv, quotechar) + quotechar;
+
+ // String concatenation transforms the old script scheme:
+ // '<scr'+'ipt ...><'+'/script>'
+ // into the following:
+ // '<script ...></script>'
+ // which breaks if this code is embedded inside an HTML document.
+ // Since this is not the right way to do this, let's fix the code by
+ // transforming all "</script" into "<\/script"
+
+ if (tv.indexOf("</script") >= 0) {
+ tv = tv.replaceAll("<\\/script", "<\\\\/script");
+ }
+
+ tokens.set(i, new JavaScriptToken(Token.STRING, tv));
+ }
+ }
+ }
+
+ // Add necessary escaping that was removed in Rhino's tokenizer.
+ private static String escapeString(String s, char quotechar) {
+
+ assert quotechar == '"' || quotechar == '\'';
+
+ if (s == null) {
+ return null;
+ }
+
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0, L = s.length(); i < L; i++) {
+ int c = s.charAt(i);
+ if (c == quotechar) {
+ sb.append("\\");
+ }
+ sb.append((char) c);
+ }
+
+ return sb.toString();
+ }
+
+ /*
+ * Simple check to see whether a string is a valid identifier name.
+ * If a string matches this pattern, it means it IS a valid
+ * identifier name. If a string doesn't match it, it does not
+ * necessarily mean it is not a valid identifier name.
+ */
+ private static final Pattern SIMPLE_IDENTIFIER_NAME_PATTERN = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");
+
+ private static boolean isValidIdentifier(String s) {
+ Matcher m = SIMPLE_IDENTIFIER_NAME_PATTERN.matcher(s);
+ return (m.matches() && !reserved.contains(s));
+ }
+
+ /*
+ * Transforms obj["foo"] into obj.foo whenever possible, saving 3 bytes.
+ */
+ private static void optimizeObjectMemberAccess(ArrayList tokens) {
+
+ String tv;
+ int i, length;
+ JavaScriptToken token;
+
+ for (i = 0, length = tokens.size(); i < length; i++) {
+
+ if (((JavaScriptToken) tokens.get(i)).getType() == Token.LB &&
+ i > 0 && i < length - 2 &&
+ ((JavaScriptToken) tokens.get(i - 1)).getType() == Token.NAME &&
+ ((JavaScriptToken) tokens.get(i + 1)).getType() == Token.STRING &&
+ ((JavaScriptToken) tokens.get(i + 2)).getType() == Token.RB) {
+ token = (JavaScriptToken) tokens.get(i + 1);
+ tv = token.getValue();
+ tv = tv.substring(1, tv.length() - 1);
+ if (isValidIdentifier(tv)) {
+ tokens.set(i, new JavaScriptToken(Token.DOT, "."));
+ tokens.set(i + 1, new JavaScriptToken(Token.NAME, tv));
+ tokens.remove(i + 2);
+ i = i + 2;
+ length = length - 1;
+ }
+ }
+ }
+ }
+
+ /*
+ * Transforms 'foo': ... into foo: ... whenever possible, saving 2 bytes.
+ */
+ private static void optimizeObjLitMemberDecl(ArrayList tokens) {
+
+ String tv;
+ int i, length;
+ JavaScriptToken token;
+
+ for (i = 0, length = tokens.size(); i < length; i++) {
+ if (((JavaScriptToken) tokens.get(i)).getType() == Token.OBJECTLIT &&
+ i > 0 && ((JavaScriptToken) tokens.get(i - 1)).getType() == Token.STRING) {
+ token = (JavaScriptToken) tokens.get(i - 1);
+ tv = token.getValue();
+ tv = tv.substring(1, tv.length() - 1);
+ if (isValidIdentifier(tv)) {
+ tokens.set(i - 1, new JavaScriptToken(Token.NAME, tv));
+ }
+ }
+ }
+ }
+
+ private ErrorReporter logger;
+
+ private boolean munge;
+ private boolean verbose;
+
+ private static final int BUILDING_SYMBOL_TREE = 1;
+ private static final int CHECKING_SYMBOL_TREE = 2;
+
+ private int mode;
+ private int offset;
+ private int braceNesting;
+ private ArrayList tokens;
+ private Stack scopes = new Stack();
+ private ScriptOrFnScope globalScope = new ScriptOrFnScope(-1, null);
+ private Hashtable indexedScopes = new Hashtable();
+
+ public JavaScriptCompressor(Reader in, ErrorReporter reporter)
+ throws IOException, EvaluatorException {
+
+ this.logger = reporter;
+ this.tokens = parse(in, reporter);
+ }
+
+ public void compress(Writer out, int linebreak, boolean munge, boolean verbose,
+ boolean preserveAllSemiColons, boolean disableOptimizations)
+ throws IOException {
+
+ this.munge = munge;
+ this.verbose = verbose;
+
+ processStringLiterals(this.tokens, !disableOptimizations);
+
+ if (!disableOptimizations) {
+ optimizeObjectMemberAccess(this.tokens);
+ optimizeObjLitMemberDecl(this.tokens);
+ }
+
+ buildSymbolTree();
+ // DO NOT TOUCH this.tokens BETWEEN THESE TWO PHASES (BECAUSE OF this.indexedScopes)
+ mungeSymboltree();
+ StringBuffer sb = printSymbolTree(linebreak, preserveAllSemiColons);
+
+ out.write(sb.toString());
+ }
+
+ private ScriptOrFnScope getCurrentScope() {
+ return (ScriptOrFnScope) scopes.peek();
+ }
+
+ private void enterScope(ScriptOrFnScope scope) {
+ scopes.push(scope);
+ }
+
+ private void leaveCurrentScope() {
+ scopes.pop();
+ }
+
+ private JavaScriptToken consumeToken() {
+ return (JavaScriptToken) tokens.get(offset++);
+ }
+
+ private JavaScriptToken getToken(int delta) {
+ return (JavaScriptToken) tokens.get(offset + delta);
+ }
+
+ /*
+ * Returns the identifier for the specified symbol defined in
+ * the specified scope or in any scope above it. Returns null
+ * if this symbol does not have a corresponding identifier.
+ */
+ private JavaScriptIdentifier getIdentifier(String symbol, ScriptOrFnScope scope) {
+ JavaScriptIdentifier identifier;
+ while (scope != null) {
+ identifier = scope.getIdentifier(symbol);
+ if (identifier != null) {
+ return identifier;
+ }
+ scope = scope.getParentScope();
+ }
+ return null;
+ }
+
+ /*
+ * If either 'eval' or 'with' is used in a local scope, we must make
+ * sure that all containing local scopes don't get munged. Otherwise,
+ * the obfuscation would potentially introduce bugs.
+ */
+ private void protectScopeFromObfuscation(ScriptOrFnScope scope) {
+ assert scope != null;
+
+ if (scope == globalScope) {
+ // The global scope does not get obfuscated,
+ // so we don't need to worry about it...
+ return;
+ }
+
+ // Find the highest local scope containing the specified scope.
+ while (scope.getParentScope() != globalScope) {
+ scope = scope.getParentScope();
+ }
+
+ assert scope.getParentScope() == globalScope;
+ scope.preventMunging();
+ }
+
+ private String getDebugString(int max) {
+ assert max > 0;
+ StringBuffer result = new StringBuffer();
+ int start = Math.max(offset - max, 0);
+ int end = Math.min(offset + max, tokens.size());
+ for (int i = start; i < end; i++) {
+ JavaScriptToken token = (JavaScriptToken) tokens.get(i);
+ if (i == offset - 1) {
+ result.append(" ---> ");
+ }
+ result.append(token.getValue());
+ if (i == offset - 1) {
+ result.append(" <--- ");
+ }
+ }
+ return result.toString();
+ }
+
+ private void warn(String message, boolean showDebugString) {
+ if (verbose) {
+ if (showDebugString) {
+ message = message + "\n" + getDebugString(10);
+ }
+ logger.warning(message, null, -1, null, -1);
+ }
+ }
+
+ private void parseFunctionDeclaration() {
+
+ String symbol;
+ JavaScriptToken token;
+ ScriptOrFnScope currentScope, fnScope;
+ JavaScriptIdentifier identifier;
+
+ currentScope = getCurrentScope();
+
+ token = consumeToken();
+ if (token.getType() == Token.NAME) {
+ if (mode == BUILDING_SYMBOL_TREE) {
+ // Get the name of the function and declare it in the current scope.
+ symbol = token.getValue();
+ if (currentScope.getIdentifier(symbol) != null) {
+ warn("The function " + symbol + " has already been declared in the same scope...", true);
+ }
+ currentScope.declareIdentifier(symbol);
+ }
+ token = consumeToken();
+ }
+
+ assert token.getType() == Token.LP;
+ if (mode == BUILDING_SYMBOL_TREE) {
+ fnScope = new ScriptOrFnScope(braceNesting, currentScope);
+ indexedScopes.put(new Integer(offset), fnScope);
+ } else {
+ fnScope = (ScriptOrFnScope) indexedScopes.get(new Integer(offset));
+ }
+
+ // Parse function arguments.
+ int argpos = 0;
+ while ((token = consumeToken()).getType() != Token.RP) {
+ assert token.getType() == Token.NAME ||
+ token.getType() == Token.COMMA;
+ if (token.getType() == Token.NAME && mode == BUILDING_SYMBOL_TREE) {
+ symbol = token.getValue();
+ identifier = fnScope.declareIdentifier(symbol);
+ if (symbol.equals("$super") && argpos == 0) {
+ // Exception for Prototype 1.6...
+ identifier.preventMunging();
+ }
+ argpos++;
+ }
+ }
+
+ token = consumeToken();
+ assert token.getType() == Token.LC;
+ braceNesting++;
+
+ token = getToken(0);
+ if (token.getType() == Token.STRING &&
+ getToken(1).getType() == Token.SEMI) {
+ // This is a hint. Hints are empty statements that look like
+ // "localvar1:nomunge, localvar2:nomunge"; They allow developers
+ // to prevent specific symbols from getting obfuscated (some heretic
+ // implementations, such as Prototype 1.6, require specific variable
+ // names, such as $super for example, in order to work appropriately.
+ // Note: right now, only "nomunge" is supported in the right hand side
+ // of a hint. However, in the future, the right hand side may contain
+ // other values.
+ consumeToken();
+ String hints = token.getValue();
+ // Remove the leading and trailing quotes...
+ hints = hints.substring(1, hints.length() - 1).trim();
+ StringTokenizer st1 = new StringTokenizer(hints, ",");
+ while (st1.hasMoreTokens()) {
+ String hint = st1.nextToken();
+ int idx = hint.indexOf(':');
+ if (idx <= 0 || idx >= hint.length() - 1) {
+ if (mode == BUILDING_SYMBOL_TREE) {
+ // No need to report the error twice, hence the test...
+ warn("Invalid hint syntax: " + hint, true);
+ }
+ break;
+ }
+ String variableName = hint.substring(0, idx).trim();
+ String variableType = hint.substring(idx + 1).trim();
+ if (mode == BUILDING_SYMBOL_TREE) {
+ fnScope.addHint(variableName, variableType);
+ } else if (mode == CHECKING_SYMBOL_TREE) {
+ identifier = fnScope.getIdentifier(variableName);
+ if (identifier != null) {
+ if (variableType.equals("nomunge")) {
+ identifier.preventMunging();
+ } else {
+ warn("Unsupported hint value: " + hint, true);
+ }
+ } else {
+ warn("Hint refers to an unknown identifier: " + hint, true);
+ }
+ }
+ }
+ }
+
+ parseScope(fnScope);
+ }
+
+ private void parseCatch() {
+
+ String symbol;
+ JavaScriptToken token;
+ ScriptOrFnScope currentScope;
+ JavaScriptIdentifier identifier;
+
+ token = getToken(-1);
+ assert token.getType() == Token.CATCH;
+ token = consumeToken();
+ assert token.getType() == Token.LP;
+ token = consumeToken();
+ assert token.getType() == Token.NAME;
+
+ symbol = token.getValue();
+ currentScope = getCurrentScope();
+
+ if (mode == BUILDING_SYMBOL_TREE) {
+ // We must declare the exception identifier in the containing function
+ // scope to avoid errors related to the obfuscation process. No need to
+ // display a warning if the symbol was already declared here...
+ currentScope.declareIdentifier(symbol);
+ } else {
+ identifier = getIdentifier(symbol, currentScope);
+ identifier.incrementRefcount();
+ }
+
+ token = consumeToken();
+ assert token.getType() == Token.RP;
+ }
+
+ private void parseExpression() {
+
+ // Parse the expression until we encounter a comma or a semi-colon
+ // in the same brace nesting, bracket nesting and paren nesting.
+ // Parse functions if any...
+
+ String symbol;
+ JavaScriptToken token;
+ ScriptOrFnScope currentScope;
+ JavaScriptIdentifier identifier;
+
+ int expressionBraceNesting = braceNesting;
+ int bracketNesting = 0;
+ int parensNesting = 0;
+
+ int length = tokens.size();
+
+ while (offset < length) {
+
+ token = consumeToken();
+ currentScope = getCurrentScope();
+
+ switch (token.getType()) {
+
+ case Token.SEMI:
+ case Token.COMMA:
+ if (braceNesting == expressionBraceNesting &&
+ bracketNesting == 0 &&
+ parensNesting == 0) {
+ return;
+ }
+ break;
+
+ case Token.FUNCTION:
+ parseFunctionDeclaration();
+ break;
+
+ case Token.LC:
+ braceNesting++;
+ break;
+
+ case Token.RC:
+ braceNesting--;
+ assert braceNesting >= expressionBraceNesting;
+ break;
+
+ case Token.LB:
+ bracketNesting++;
+ break;
+
+ case Token.RB:
+ bracketNesting--;
+ break;
+
+ case Token.LP:
+ parensNesting++;
+ break;
+
+ case Token.RP:
+ parensNesting--;
+ break;
+
+ case Token.CONDCOMMENT:
+ if (mode == BUILDING_SYMBOL_TREE) {
+ protectScopeFromObfuscation(currentScope);
+ warn("Using JScript conditional comments is not recommended." + (munge ? " Moreover, using JScript conditional comments reduces the level of compression!" : ""), true);
+ }
+ break;
+
+ case Token.NAME:
+ symbol = token.getValue();
+
+ if (mode == BUILDING_SYMBOL_TREE) {
+
+ if (symbol.equals("eval")) {
+
+ protectScopeFromObfuscation(currentScope);
+ warn("Using 'eval' is not recommended." + (munge ? " Moreover, using 'eval' reduces the level of compression!" : ""), true);
+
+ }
+
+ } else if (mode == CHECKING_SYMBOL_TREE) {
+
+ if ((offset < 2 ||
+ (getToken(-2).getType() != Token.DOT &&
+ getToken(-2).getType() != Token.GET &&
+ getToken(-2).getType() != Token.SET)) &&
+ getToken(0).getType() != Token.OBJECTLIT) {
+
+ identifier = getIdentifier(symbol, currentScope);
+
+ if (identifier == null) {
+
+ if (symbol.length() <= 3 && !builtin.contains(symbol)) {
+ // Here, we found an undeclared and un-namespaced symbol that is
+ // 3 characters or less in length. Declare it in the global scope.
+ // We don't need to declare longer symbols since they won't cause
+ // any conflict with other munged symbols.
+ globalScope.declareIdentifier(symbol);
+
+ // I removed the warning since was only being done when
+ // for identifiers 3 chars or less, and was just causing
+ // noise for people who happen to rely on an externally
+ // declared variable that happen to be that short. We either
+ // should always warn or never warn -- the fact that we
+ // declare the short symbols in the global space doesn't
+ // change anything.
+ // warn("Found an undeclared symbol: " + symbol, true);
+ }
+
+ } else {
+
+ identifier.incrementRefcount();
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ private void parseScope(ScriptOrFnScope scope) {
+
+ String symbol;
+ JavaScriptToken token;
+ JavaScriptIdentifier identifier;
+
+ int length = tokens.size();
+
+ enterScope(scope);
+
+ while (offset < length) {
+
+ token = consumeToken();
+
+ switch (token.getType()) {
+
+ case Token.VAR:
+
+ if (mode == BUILDING_SYMBOL_TREE && scope.incrementVarCount() > 1) {
+ warn("Try to use a single 'var' statement per scope.", true);
+ }
+
+ /* FALLSTHROUGH */
+
+ case Token.CONST:
+
+ // The var keyword is followed by at least one symbol name.
+ // If several symbols follow, they are comma separated.
+ for (; ;) {
+ token = consumeToken();
+
+ assert token.getType() == Token.NAME;
+
+ if (mode == BUILDING_SYMBOL_TREE) {
+ symbol = token.getValue();
+ if (scope.getIdentifier(symbol) == null) {
+ scope.declareIdentifier(symbol);
+ } else {
+ warn("The variable " + symbol + " has already been declared in the same scope...", true);
+ }
+ }
+
+ token = getToken(0);
+
+ assert token.getType() == Token.SEMI ||
+ token.getType() == Token.ASSIGN ||
+ token.getType() == Token.COMMA ||
+ token.getType() == Token.IN;
+
+ if (token.getType() == Token.IN) {
+ break;
+ } else {
+ parseExpression();
+ token = getToken(-1);
+ if (token.getType() == Token.SEMI) {
+ break;
+ }
+ }
+ }
+ break;
+
+ case Token.FUNCTION:
+ parseFunctionDeclaration();
+ break;
+
+ case Token.LC:
+ braceNesting++;
+ break;
+
+ case Token.RC:
+ braceNesting--;
+ assert braceNesting >= scope.getBraceNesting();
+ if (braceNesting == scope.getBraceNesting()) {
+ leaveCurrentScope();
+ return;
+ }
+ break;
+
+ case Token.WITH:
+ if (mode == BUILDING_SYMBOL_TREE) {
+ // Inside a 'with' block, it is impossible to figure out
+ // statically whether a symbol is a local variable or an
+ // object member. As a consequence, the only thing we can
+ // do is turn the obfuscation off for the highest scope
+ // containing the 'with' block.
+ protectScopeFromObfuscation(scope);
+ warn("Using 'with' is not recommended." + (munge ? " Moreover, using 'with' reduces the level of compression!" : ""), true);
+ }
+ break;
+
+ case Token.CATCH:
+ parseCatch();
+ break;
+
+ case Token.CONDCOMMENT:
+ if (mode == BUILDING_SYMBOL_TREE) {
+ protectScopeFromObfuscation(scope);
+ warn("Using JScript conditional comments is not recommended." + (munge ? " Moreover, using JScript conditional comments reduces the level of compression." : ""), true);
+ }
+ break;
+
+ case Token.NAME:
+ symbol = token.getValue();
+
+ if (mode == BUILDING_SYMBOL_TREE) {
+
+ if (symbol.equals("eval")) {
+
+ protectScopeFromObfuscation(scope);
+ warn("Using 'eval' is not recommended." + (munge ? " Moreover, using 'eval' reduces the level of compression!" : ""), true);
+
+ }
+
+ } else if (mode == CHECKING_SYMBOL_TREE) {
+
+ if ((offset < 2 || getToken(-2).getType() != Token.DOT) &&
+ getToken(0).getType() != Token.OBJECTLIT) {
+
+ identifier = getIdentifier(symbol, scope);
+
+ if (identifier == null) {
+
+ if (symbol.length() <= 3 && !builtin.contains(symbol)) {
+ // Here, we found an undeclared and un-namespaced symbol that is
+ // 3 characters or less in length. Declare it in the global scope.
+ // We don't need to declare longer symbols since they won't cause
+ // any conflict with other munged symbols.
+ globalScope.declareIdentifier(symbol);
+ // warn("Found an undeclared symbol: " + symbol, true);
+ }
+
+ } else {
+
+ identifier.incrementRefcount();
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ private void buildSymbolTree() {
+ offset = 0;
+ braceNesting = 0;
+ scopes.clear();
+ indexedScopes.clear();
+ indexedScopes.put(new Integer(0), globalScope);
+ mode = BUILDING_SYMBOL_TREE;
+ parseScope(globalScope);
+ }
+
+ private void mungeSymboltree() {
+
+ if (!munge) {
+ return;
+ }
+
+ // One problem with obfuscation resides in the use of undeclared
+ // and un-namespaced global symbols that are 3 characters or less
+ // in length. Here is an example:
+ //
+ // var declaredGlobalVar;
+ //
+ // function declaredGlobalFn() {
+ // var localvar;
+ // localvar = abc; // abc is an undeclared global symbol
+ // }
+ //
+ // In the example above, there is a slim chance that localvar may be
+ // munged to 'abc', conflicting with the undeclared global symbol
+ // abc, creating a potential bug. The following code detects such
+ // global symbols. This must be done AFTER the entire file has been
+ // parsed, and BEFORE munging the symbol tree. Note that declaring
+ // extra symbols in the global scope won't hurt.
+ //
+ // Note: Since we go through all the tokens to do this, we also use
+ // the opportunity to count how many times each identifier is used.
+
+ offset = 0;
+ braceNesting = 0;
+ scopes.clear();
+ mode = CHECKING_SYMBOL_TREE;
+ parseScope(globalScope);
+ globalScope.munge();
+ }
+
+ private StringBuffer printSymbolTree(int linebreakpos, boolean preserveAllSemiColons)
+ throws IOException {
+
+ offset = 0;
+ braceNesting = 0;
+ scopes.clear();
+
+ String symbol;
+ JavaScriptToken token;
+ JavaScriptToken lastToken = getToken(0);
+ ScriptOrFnScope currentScope;
+ JavaScriptIdentifier identifier;
+
+ int length = tokens.size();
+ StringBuffer result = new StringBuffer();
+
+ int linestartpos = 0;
+
+ enterScope(globalScope);
+
+ while (offset < length) {
+
+ token = consumeToken();
+ symbol = token.getValue();
+ currentScope = getCurrentScope();
+
+ switch (token.getType()) {
+ case Token.GET:
+ case Token.SET:
+ lastToken = token;
+
+ case Token.NAME:
+
+ if (offset >= 2 && getToken(-2).getType() == Token.DOT ||
+ getToken(0).getType() == Token.OBJECTLIT) {
+
+ result.append(symbol);
+
+ } else {
+
+ identifier = getIdentifier(symbol, currentScope);
+ if (identifier != null) {
+ if (identifier.getMungedValue() != null) {
+ result.append(identifier.getMungedValue());
+ } else {
+ result.append(symbol);
+ }
+ if (currentScope != globalScope && identifier.getRefcount() == 0) {
+ warn("The symbol " + symbol + " is declared but is apparently never used.\nThis code can probably be written in a more compact way.", true);
+ }
+ } else {
+ result.append(symbol);
+ }
+ }
+ break;
+
+ case Token.REGEXP:
+ case Token.NUMBER:
+ case Token.STRING:
+ result.append(symbol);
+ break;
+
+ case Token.ADD:
+ case Token.SUB:
+ result.append((String) literals.get(new Integer(token.getType())));
+ if (offset < length) {
+ token = getToken(0);
+ if (token.getType() == Token.INC ||
+ token.getType() == Token.DEC ||
+ token.getType() == Token.ADD ||
+ token.getType() == Token.DEC) {
+ // Handle the case x +/- ++/-- y
+ // We must keep a white space here. Otherwise, x +++ y would be
+ // interpreted as x ++ + y by the compiler, which is a bug (due
+ // to the implicit assignment being done on the wrong variable)
+ result.append(' ');
+ } else if (token.getType() == Token.POS && getToken(-1).getType() == Token.ADD ||
+ token.getType() == Token.NEG && getToken(-1).getType() == Token.SUB) {
+ // Handle the case x + + y and x - - y
+ result.append(' ');
+ }
+ }
+ break;
+
+ case Token.FUNCTION:
+ if (lastToken.getType() != Token.GET && lastToken.getType() != Token.SET) {
+ result.append("function");
+ }
+ lastToken = token;
+ token = consumeToken();
+ if (token.getType() == Token.NAME) {
+ result.append(' ');
+ symbol = token.getValue();
+ identifier = getIdentifier(symbol, currentScope);
+ assert identifier != null;
+ if (identifier.getMungedValue() != null) {
+ result.append(identifier.getMungedValue());
+ } else {
+ result.append(symbol);
+ }
+ if (currentScope != globalScope && identifier.getRefcount() == 0) {
+ warn("The symbol " + symbol + " is declared but is apparently never used.\nThis code can probably be written in a more compact way.", true);
+ }
+ token = consumeToken();
+ }
+ assert token.getType() == Token.LP;
+ result.append('(');
+ currentScope = (ScriptOrFnScope) indexedScopes.get(new Integer(offset));
+ enterScope(currentScope);
+ while ((token = consumeToken()).getType() != Token.RP) {
+ assert token.getType() == Token.NAME || token.getType() == Token.COMMA;
+ if (token.getType() == Token.NAME) {
+ symbol = token.getValue();
+ identifier = getIdentifier(symbol, currentScope);
+ assert identifier != null;
+ if (identifier.getMungedValue() != null) {
+ result.append(identifier.getMungedValue());
+ } else {
+ result.append(symbol);
+ }
+ } else if (token.getType() == Token.COMMA) {
+ result.append(',');
+ }
+ }
+ result.append(')');
+ token = consumeToken();
+ assert token.getType() == Token.LC;
+ result.append('{');
+ braceNesting++;
+ token = getToken(0);
+ if (token.getType() == Token.STRING &&
+ getToken(1).getType() == Token.SEMI) {
+ // This is a hint. Skip it!
+ consumeToken();
+ consumeToken();
+ }
+ break;
+
+ case Token.RETURN:
+ case Token.TYPEOF:
+ result.append(literals.get(new Integer(token.getType())));
+ // No space needed after 'return' and 'typeof' when followed
+ // by '(', '[', '{', a string or a regexp.
+ if (offset < length) {
+ token = getToken(0);
+ if (token.getType() != Token.LP &&
+ token.getType() != Token.LB &&
+ token.getType() != Token.LC &&
+ token.getType() != Token.STRING &&
+ token.getType() != Token.REGEXP &&
+ token.getType() != Token.SEMI) {
+ result.append(' ');
+ }
+ }
+ break;
+
+ case Token.CASE:
+ case Token.THROW:
+ result.append(literals.get(new Integer(token.getType())));
+ // White-space needed after 'case' and 'throw' when not followed by a string.
+ if (offset < length && getToken(0).getType() != Token.STRING) {
+ result.append(' ');
+ }
+ break;
+
+ case Token.BREAK:
+ case Token.CONTINUE:
+ result.append(literals.get(new Integer(token.getType())));
+ if (offset < length && getToken(0).getType() != Token.SEMI) {
+ // If 'break' or 'continue' is not followed by a semi-colon, it must
+ // be followed by a label, hence the need for a white space.
+ result.append(' ');
+ }
+ break;
+
+ case Token.LC:
+ result.append('{');
+ braceNesting++;
+ break;
+
+ case Token.RC:
+ result.append('}');
+ braceNesting--;
+ assert braceNesting >= currentScope.getBraceNesting();
+ if (braceNesting == currentScope.getBraceNesting()) {
+ leaveCurrentScope();
+ }
+ break;
+
+ case Token.SEMI:
+ // No need to output a semi-colon if the next character is a right-curly...
+ if (preserveAllSemiColons || offset < length && getToken(0).getType() != Token.RC) {
+ result.append(';');
+ }
+
+ if (linebreakpos >= 0 && result.length() - linestartpos > linebreakpos) {
+ // Some source control tools don't like it when files containing lines longer
+ // than, say 8000 characters, are checked in. The linebreak option is used in
+ // that case to split long lines after a specific column.
+ result.append('\n');
+ linestartpos = result.length();
+ }
+ break;
+
+ case Token.COMMA:
+ // No need to output a comma if the next character is a right-curly or a right-square bracket
+ if (offset < length && getToken(0).getType() != Token.RC && getToken(0).getType() != Token.RB) {
+ result.append(',');
+ }
+ break;
+
+ case Token.CONDCOMMENT:
+ case Token.KEEPCOMMENT:
+ if (result.length() > 0 && result.charAt(result.length() - 1) != '\n') {
+ result.append("\n");
+ }
+ result.append("/*");
+ if (token.getType() == Token.KEEPCOMMENT) {
+ result.append("!");
+ }
+ result.append(symbol);
+ result.append("*/\n");
+ break;
+
+ default:
+ String literal = (String) literals.get(new Integer(token.getType()));
+ if (literal != null) {
+ result.append(literal);
+ } else {
+ warn("This symbol cannot be printed: " + symbol, true);
+ }
+ break;
+ }
+ }
+
+ // Append a semi-colon at the end, even if unnecessary semi-colons are
+ // supposed to be removed. This is especially useful when concatenating
+ // several minified files (the absence of an ending semi-colon at the
+ // end of one file may very likely cause a syntax error)
+ if (!preserveAllSemiColons &&
+ result.length() > 0 &&
+ getToken(-1).getType() != Token.CONDCOMMENT &&
+ getToken(-1).getType() != Token.KEEPCOMMENT) {
+ if (result.charAt(result.length() - 1) == '\n') {
+ result.setCharAt(result.length() - 1, ';');
+ } else {
+ result.append(';');
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/src/com/yahoo/platform/yui/compressor/JavaScriptIdentifier.java b/src/com/yahoo/platform/yui/compressor/JavaScriptIdentifier.java
new file mode 100644
index 0000000..1ac3ac1
--- /dev/null
+++ b/src/com/yahoo/platform/yui/compressor/JavaScriptIdentifier.java
@@ -0,0 +1,55 @@
+/*
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+package com.yahoo.platform.yui.compressor;
+
+import org.mozilla.javascript.Token;
+
+/**
+ * JavaScriptIdentifier represents a variable/function identifier.
+ */
+class JavaScriptIdentifier extends JavaScriptToken {
+
+ private int refcount = 0;
+ private String mungedValue;
+ private ScriptOrFnScope declaredScope;
+ private boolean markedForMunging = true;
+
+ JavaScriptIdentifier(String value, ScriptOrFnScope declaredScope) {
+ super(Token.NAME, value);
+ this.declaredScope = declaredScope;
+ }
+
+ ScriptOrFnScope getDeclaredScope() {
+ return declaredScope;
+ }
+
+ void setMungedValue(String value) {
+ mungedValue = value;
+ }
+
+ String getMungedValue() {
+ return mungedValue;
+ }
+
+ void preventMunging() {
+ markedForMunging = false;
+ }
+
+ boolean isMarkedForMunging() {
+ return markedForMunging;
+ }
+
+ void incrementRefcount() {
+ refcount++;
+ }
+
+ int getRefcount() {
+ return refcount;
+ }
+}
diff --git a/src/com/yahoo/platform/yui/compressor/JavaScriptToken.java b/src/com/yahoo/platform/yui/compressor/JavaScriptToken.java
new file mode 100644
index 0000000..52baaf0
--- /dev/null
+++ b/src/com/yahoo/platform/yui/compressor/JavaScriptToken.java
@@ -0,0 +1,28 @@
+/*
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+package com.yahoo.platform.yui.compressor;
+
+public class JavaScriptToken {
+
+ private int type;
+ private String value;
+
+ JavaScriptToken(int type, String value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ int getType() {
+ return type;
+ }
+
+ String getValue() {
+ return value;
+ }
+}
diff --git a/src/com/yahoo/platform/yui/compressor/ScriptOrFnScope.java b/src/com/yahoo/platform/yui/compressor/ScriptOrFnScope.java
new file mode 100644
index 0000000..c377857
--- /dev/null
+++ b/src/com/yahoo/platform/yui/compressor/ScriptOrFnScope.java
@@ -0,0 +1,160 @@
+/*
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+package com.yahoo.platform.yui.compressor;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+class ScriptOrFnScope {
+
+ private int braceNesting;
+ private ScriptOrFnScope parentScope;
+ private ArrayList subScopes;
+ private Hashtable identifiers = new Hashtable();
+ private Hashtable hints = new Hashtable();
+ private boolean markedForMunging = true;
+ private int varcount = 0;
+
+ ScriptOrFnScope(int braceNesting, ScriptOrFnScope parentScope) {
+ this.braceNesting = braceNesting;
+ this.parentScope = parentScope;
+ this.subScopes = new ArrayList();
+ if (parentScope != null) {
+ parentScope.subScopes.add(this);
+ }
+ }
+
+ int getBraceNesting() {
+ return braceNesting;
+ }
+
+ ScriptOrFnScope getParentScope() {
+ return parentScope;
+ }
+
+ JavaScriptIdentifier declareIdentifier(String symbol) {
+ JavaScriptIdentifier identifier = (JavaScriptIdentifier) identifiers.get(symbol);
+ if (identifier == null) {
+ identifier = new JavaScriptIdentifier(symbol, this);
+ identifiers.put(symbol, identifier);
+ }
+ return identifier;
+ }
+
+ JavaScriptIdentifier getIdentifier(String symbol) {
+ return (JavaScriptIdentifier) identifiers.get(symbol);
+ }
+
+ void addHint(String variableName, String variableType) {
+ hints.put(variableName, variableType);
+ }
+
+ void preventMunging() {
+ if (parentScope != null) {
+ // The symbols in the global scope don't get munged,
+ // but the sub-scopes it contains do get munged.
+ markedForMunging = false;
+ }
+ }
+
+ private ArrayList getUsedSymbols() {
+ ArrayList result = new ArrayList();
+ Enumeration elements = identifiers.elements();
+ while (elements.hasMoreElements()) {
+ JavaScriptIdentifier identifier = (JavaScriptIdentifier) elements.nextElement();
+ String mungedValue = identifier.getMungedValue();
+ if (mungedValue == null) {
+ mungedValue = identifier.getValue();
+ }
+ result.add(mungedValue);
+ }
+ return result;
+ }
+
+ private ArrayList getAllUsedSymbols() {
+ ArrayList result = new ArrayList();
+ ScriptOrFnScope scope = this;
+ while (scope != null) {
+ result.addAll(scope.getUsedSymbols());
+ scope = scope.parentScope;
+ }
+ return result;
+ }
+
+ int incrementVarCount() {
+ varcount++;
+ return varcount;
+ }
+
+ void munge() {
+
+ if (!markedForMunging) {
+ // Stop right here if this scope was flagged as unsafe for munging.
+ return;
+ }
+
+ int pickFromSet = 1;
+
+ // Do not munge symbols in the global scope!
+ if (parentScope != null) {
+
+ ArrayList freeSymbols = new ArrayList();
+
+ freeSymbols.addAll(JavaScriptCompressor.ones);
+ freeSymbols.removeAll(getAllUsedSymbols());
+ if (freeSymbols.size() == 0) {
+ pickFromSet = 2;
+ freeSymbols.addAll(JavaScriptCompressor.twos);
+ freeSymbols.removeAll(getAllUsedSymbols());
+ }
+ if (freeSymbols.size() == 0) {
+ pickFromSet = 3;
+ freeSymbols.addAll(JavaScriptCompressor.threes);
+ freeSymbols.removeAll(getAllUsedSymbols());
+ }
+ if (freeSymbols.size() == 0) {
+ throw new IllegalStateException("The YUI Compressor ran out of symbols. Aborting...");
+ }
+
+ Enumeration elements = identifiers.elements();
+ while (elements.hasMoreElements()) {
+ if (freeSymbols.size() == 0) {
+ pickFromSet++;
+ if (pickFromSet == 2) {
+ freeSymbols.addAll(JavaScriptCompressor.twos);
+ } else if (pickFromSet == 3) {
+ freeSymbols.addAll(JavaScriptCompressor.threes);
+ } else {
+ throw new IllegalStateException("The YUI Compressor ran out of symbols. Aborting...");
+ }
+ // It is essential to remove the symbols already used in
+ // the containing scopes, or some of the variables declared
+ // in the containing scopes will be redeclared, which can
+ // lead to errors.
+ freeSymbols.removeAll(getAllUsedSymbols());
+ }
+
+ String mungedValue;
+ JavaScriptIdentifier identifier = (JavaScriptIdentifier) elements.nextElement();
+ if (identifier.isMarkedForMunging()) {
+ mungedValue = (String) freeSymbols.remove(0);
+ } else {
+ mungedValue = identifier.getValue();
+ }
+ identifier.setMungedValue(mungedValue);
+ }
+ }
+
+ for (int i = 0; i < subScopes.size(); i++) {
+ ScriptOrFnScope scope = (ScriptOrFnScope) subScopes.get(i);
+ scope.munge();
+ }
+ }
+}
diff --git a/src/com/yahoo/platform/yui/compressor/YUICompressor.java b/src/com/yahoo/platform/yui/compressor/YUICompressor.java
new file mode 100644
index 0000000..a1f96c3
--- /dev/null
+++ b/src/com/yahoo/platform/yui/compressor/YUICompressor.java
@@ -0,0 +1,275 @@
+/*
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+package com.yahoo.platform.yui.compressor;
+
+import jargs.gnu.CmdLineParser;
+import org.mozilla.javascript.ErrorReporter;
+import org.mozilla.javascript.EvaluatorException;
+
+import java.io.*;
+import java.nio.charset.Charset;
+
+public class YUICompressor {
+
+ public static void main(String args[]) {
+
+ CmdLineParser parser = new CmdLineParser();
+ CmdLineParser.Option typeOpt = parser.addStringOption("type");
+ CmdLineParser.Option versionOpt = parser.addBooleanOption('V', "version");
+ CmdLineParser.Option verboseOpt = parser.addBooleanOption('v', "verbose");
+ CmdLineParser.Option nomungeOpt = parser.addBooleanOption("nomunge");
+ CmdLineParser.Option linebreakOpt = parser.addStringOption("line-break");
+ CmdLineParser.Option preserveSemiOpt = parser.addBooleanOption("preserve-semi");
+ CmdLineParser.Option disableOptimizationsOpt = parser.addBooleanOption("disable-optimizations");
+ CmdLineParser.Option helpOpt = parser.addBooleanOption('h', "help");
+ CmdLineParser.Option charsetOpt = parser.addStringOption("charset");
+ CmdLineParser.Option outputFilenameOpt = parser.addStringOption('o', "output");
+
+ Reader in = null;
+ Writer out = null;
+
+ try {
+
+ parser.parse(args);
+
+ Boolean help = (Boolean) parser.getOptionValue(helpOpt);
+ if (help != null && help.booleanValue()) {
+ usage();
+ System.exit(0);
+ }
+
+ Boolean version = (Boolean) parser.getOptionValue(versionOpt);
+ if (version != null && version.booleanValue()) {
+ version();
+ System.exit(0);
+ }
+
+ boolean verbose = parser.getOptionValue(verboseOpt) != null;
+
+ String charset = (String) parser.getOptionValue(charsetOpt);
+ if (charset == null || !Charset.isSupported(charset)) {
+ // charset = System.getProperty("file.encoding");
+ // if (charset == null) {
+ // charset = "UTF-8";
+ // }
+
+ // UTF-8 seems to be a better choice than what the system is reporting
+ charset = "UTF-8";
+
+
+ if (verbose) {
+ System.err.println("\n[INFO] Using charset " + charset);
+ }
+ }
+
+ int linebreakpos = -1;
+ String linebreakstr = (String) parser.getOptionValue(linebreakOpt);
+ if (linebreakstr != null) {
+ try {
+ linebreakpos = Integer.parseInt(linebreakstr, 10);
+ } catch (NumberFormatException e) {
+ usage();
+ System.exit(1);
+ }
+ }
+
+ String typeOverride = (String) parser.getOptionValue(typeOpt);
+ if (typeOverride != null && !typeOverride.equalsIgnoreCase("js") && !typeOverride.equalsIgnoreCase("css")) {
+ usage();
+ System.exit(1);
+ }
+
+ boolean munge = parser.getOptionValue(nomungeOpt) == null;
+ boolean preserveAllSemiColons = parser.getOptionValue(preserveSemiOpt) != null;
+ boolean disableOptimizations = parser.getOptionValue(disableOptimizationsOpt) != null;
+
+ String[] fileArgs = parser.getRemainingArgs();
+ java.util.List files = java.util.Arrays.asList(fileArgs);
+ if (files.isEmpty()) {
+ if (typeOverride == null) {
+ usage();
+ System.exit(1);
+ }
+ files = new java.util.ArrayList();
+ files.add("-"); // read from stdin
+ }
+
+ String output = (String) parser.getOptionValue(outputFilenameOpt);
+ String pattern[] = output != null ? output.split(":") : new String[0];
+
+ java.util.Iterator filenames = files.iterator();
+ while(filenames.hasNext()) {
+ String inputFilename = (String)filenames.next();
+ String type = null;
+ try {
+ if (inputFilename.equals("-")) {
+
+ in = new InputStreamReader(System.in, charset);
+ type = typeOverride;
+
+ } else {
+
+ if ( typeOverride != null ) {
+ type = typeOverride;
+ }
+ else {
+ int idx = inputFilename.lastIndexOf('.');
+ if (idx >= 0 && idx < inputFilename.length() - 1) {
+ type = inputFilename.substring(idx + 1);
+ }
+ }
+
+ if (type == null || !type.equalsIgnoreCase("js") && !type.equalsIgnoreCase("css")) {
+ usage();
+ System.exit(1);
+ }
+
+ in = new InputStreamReader(new FileInputStream(inputFilename), charset);
+ }
+
+ String outputFilename = output;
+ // if a substitution pattern was passed in
+ if (pattern.length > 1 && files.size() > 0) {
+ outputFilename = inputFilename.replaceFirst(pattern[0], pattern[1]);
+ }
+
+ if (type.equalsIgnoreCase("js")) {
+
+ try {
+ final String localFilename = inputFilename;
+
+ JavaScriptCompressor compressor = new JavaScriptCompressor(in, new ErrorReporter() {
+
+ public void warning(String message, String sourceName,
+ int line, String lineSource, int lineOffset) {
+ System.err.println("\n[WARNING] in " + localFilename);
+ if (line < 0) {
+ System.err.println(" " + message);
+ } else {
+ System.err.println(" " + line + ':' + lineOffset + ':' + message);
+ }
+ }
+
+ public void error(String message, String sourceName,
+ int line, String lineSource, int lineOffset) {
+ System.err.println("[ERROR] in " + localFilename);
+ if (line < 0) {
+ System.err.println(" " + message);
+ } else {
+ System.err.println(" " + line + ':' + lineOffset + ':' + message);
+ }
+ }
+
+ public EvaluatorException runtimeError(String message, String sourceName,
+ int line, String lineSource, int lineOffset) {
+ error(message, sourceName, line, lineSource, lineOffset);
+ return new EvaluatorException(message);
+ }
+ });
+
+ // Close the input stream first, and then open the output stream,
+ // in case the output file should override the input file.
+ in.close(); in = null;
+
+ if (outputFilename == null) {
+ out = new OutputStreamWriter(System.out, charset);
+ } else {
+ out = new OutputStreamWriter(new FileOutputStream(outputFilename), charset);
+ }
+
+ compressor.compress(out, linebreakpos, munge, verbose,
+ preserveAllSemiColons, disableOptimizations);
+
+ } catch (EvaluatorException e) {
+
+ e.printStackTrace();
+ // Return a special error code used specifically by the web front-end.
+ System.exit(2);
+
+ }
+
+ } else if (type.equalsIgnoreCase("css")) {
+
+ CssCompressor compressor = new CssCompressor(in);
+
+ // Close the input stream first, and then open the output stream,
+ // in case the output file should override the input file.
+ in.close(); in = null;
+
+ if (outputFilename == null) {
+ out = new OutputStreamWriter(System.out, charset);
+ } else {
+ out = new OutputStreamWriter(new FileOutputStream(outputFilename), charset);
+ }
+
+ compressor.compress(out, linebreakpos);
+ }
+
+ } catch (IOException e) {
+
+ e.printStackTrace();
+ System.exit(1);
+
+ } finally {
+
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ } catch (CmdLineParser.OptionException e) {
+
+ usage();
+ System.exit(1);
+ }
+ }
+
+ private static void version() {
+ System.err.println("@VERSION@");
+ }
+ private static void usage() {
+ System.err.println(
+ "YUICompressor Version: @VERSION@\n"
+
+ + "\nUsage: java -jar yuicompressor- at VERSION@.jar [options] [input file]\n"
+ + "\n"
+ + "Global Options\n"
+ + " -V, --version Print version information\n"
+ + " -h, --help Displays this information\n"
+ + " --type <js|css> Specifies the type of the input file\n"
+ + " --charset <charset> Read the input file using <charset>\n"
+ + " --line-break <column> Insert a line break after the specified column number\n"
+ + " -v, --verbose Display informational messages and warnings\n"
+ + " -o <file> Place the output into <file>. Defaults to stdout.\n"
+ + " Multiple files can be processed using the following syntax:\n"
+ + " java -jar yuicompressor.jar -o '.css$:-min.css' *.css\n"
+ + " java -jar yuicompressor.jar -o '.js$:-min.js' *.js\n\n"
+
+ + "JavaScript Options\n"
+ + " --nomunge Minify only, do not obfuscate\n"
+ + " --preserve-semi Preserve all semicolons\n"
+ + " --disable-optimizations Disable all micro optimizations\n\n"
+
+ + "If no input file is specified, it defaults to stdin. In this case, the 'type'\n"
+ + "option is required. Otherwise, the 'type' option is required only if the input\n"
+ + "file extension is neither 'js' nor 'css'.");
+ }
+}
diff --git a/src/org/mozilla/classfile/ByteCode.java b/src/org/mozilla/classfile/ByteCode.java
new file mode 100644
index 0000000..fa4713e
--- /dev/null
+++ b/src/org/mozilla/classfile/ByteCode.java
@@ -0,0 +1,274 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.classfile;
+
+/**
+ * This class provides opcode values expected by the JVM in Java class files.
+ *
+ * It also provides tables for internal use by the ClassFileWriter.
+ *
+ * @author Roger Lawrence
+ */
+public class ByteCode {
+
+ /**
+ * The byte opcodes defined by the Java Virtual Machine.
+ */
+ public static final int
+ NOP = 0x00,
+ ACONST_NULL = 0x01,
+ ICONST_M1 = 0x02,
+ ICONST_0 = 0x03,
+ ICONST_1 = 0x04,
+ ICONST_2 = 0x05,
+ ICONST_3 = 0x06,
+ ICONST_4 = 0x07,
+ ICONST_5 = 0x08,
+ LCONST_0 = 0x09,
+ LCONST_1 = 0x0A,
+ FCONST_0 = 0x0B,
+ FCONST_1 = 0x0C,
+ FCONST_2 = 0x0D,
+ DCONST_0 = 0x0E,
+ DCONST_1 = 0x0F,
+ BIPUSH = 0x10,
+ SIPUSH = 0x11,
+ LDC = 0x12,
+ LDC_W = 0x13,
+ LDC2_W = 0x14,
+ ILOAD = 0x15,
+ LLOAD = 0x16,
+ FLOAD = 0x17,
+ DLOAD = 0x18,
+ ALOAD = 0x19,
+ ILOAD_0 = 0x1A,
+ ILOAD_1 = 0x1B,
+ ILOAD_2 = 0x1C,
+ ILOAD_3 = 0x1D,
+ LLOAD_0 = 0x1E,
+ LLOAD_1 = 0x1F,
+ LLOAD_2 = 0x20,
+ LLOAD_3 = 0x21,
+ FLOAD_0 = 0x22,
+ FLOAD_1 = 0x23,
+ FLOAD_2 = 0x24,
+ FLOAD_3 = 0x25,
+ DLOAD_0 = 0x26,
+ DLOAD_1 = 0x27,
+ DLOAD_2 = 0x28,
+ DLOAD_3 = 0x29,
+ ALOAD_0 = 0x2A,
+ ALOAD_1 = 0x2B,
+ ALOAD_2 = 0x2C,
+ ALOAD_3 = 0x2D,
+ IALOAD = 0x2E,
+ LALOAD = 0x2F,
+ FALOAD = 0x30,
+ DALOAD = 0x31,
+ AALOAD = 0x32,
+ BALOAD = 0x33,
+ CALOAD = 0x34,
+ SALOAD = 0x35,
+ ISTORE = 0x36,
+ LSTORE = 0x37,
+ FSTORE = 0x38,
+ DSTORE = 0x39,
+ ASTORE = 0x3A,
+ ISTORE_0 = 0x3B,
+ ISTORE_1 = 0x3C,
+ ISTORE_2 = 0x3D,
+ ISTORE_3 = 0x3E,
+ LSTORE_0 = 0x3F,
+ LSTORE_1 = 0x40,
+ LSTORE_2 = 0x41,
+ LSTORE_3 = 0x42,
+ FSTORE_0 = 0x43,
+ FSTORE_1 = 0x44,
+ FSTORE_2 = 0x45,
+ FSTORE_3 = 0x46,
+ DSTORE_0 = 0x47,
+ DSTORE_1 = 0x48,
+ DSTORE_2 = 0x49,
+ DSTORE_3 = 0x4A,
+ ASTORE_0 = 0x4B,
+ ASTORE_1 = 0x4C,
+ ASTORE_2 = 0x4D,
+ ASTORE_3 = 0x4E,
+ IASTORE = 0x4F,
+ LASTORE = 0x50,
+ FASTORE = 0x51,
+ DASTORE = 0x52,
+ AASTORE = 0x53,
+ BASTORE = 0x54,
+ CASTORE = 0x55,
+ SASTORE = 0x56,
+ POP = 0x57,
+ POP2 = 0x58,
+ DUP = 0x59,
+ DUP_X1 = 0x5A,
+ DUP_X2 = 0x5B,
+ DUP2 = 0x5C,
+ DUP2_X1 = 0x5D,
+ DUP2_X2 = 0x5E,
+ SWAP = 0x5F,
+ IADD = 0x60,
+ LADD = 0x61,
+ FADD = 0x62,
+ DADD = 0x63,
+ ISUB = 0x64,
+ LSUB = 0x65,
+ FSUB = 0x66,
+ DSUB = 0x67,
+ IMUL = 0x68,
+ LMUL = 0x69,
+ FMUL = 0x6A,
+ DMUL = 0x6B,
+ IDIV = 0x6C,
+ LDIV = 0x6D,
+ FDIV = 0x6E,
+ DDIV = 0x6F,
+ IREM = 0x70,
+ LREM = 0x71,
+ FREM = 0x72,
+ DREM = 0x73,
+ INEG = 0x74,
+ LNEG = 0x75,
+ FNEG = 0x76,
+ DNEG = 0x77,
+ ISHL = 0x78,
+ LSHL = 0x79,
+ ISHR = 0x7A,
+ LSHR = 0x7B,
+ IUSHR = 0x7C,
+ LUSHR = 0x7D,
+ IAND = 0x7E,
+ LAND = 0x7F,
+ IOR = 0x80,
+ LOR = 0x81,
+ IXOR = 0x82,
+ LXOR = 0x83,
+ IINC = 0x84,
+ I2L = 0x85,
+ I2F = 0x86,
+ I2D = 0x87,
+ L2I = 0x88,
+ L2F = 0x89,
+ L2D = 0x8A,
+ F2I = 0x8B,
+ F2L = 0x8C,
+ F2D = 0x8D,
+ D2I = 0x8E,
+ D2L = 0x8F,
+ D2F = 0x90,
+ I2B = 0x91,
+ I2C = 0x92,
+ I2S = 0x93,
+ LCMP = 0x94,
+ FCMPL = 0x95,
+ FCMPG = 0x96,
+ DCMPL = 0x97,
+ DCMPG = 0x98,
+ IFEQ = 0x99,
+ IFNE = 0x9A,
+ IFLT = 0x9B,
+ IFGE = 0x9C,
+ IFGT = 0x9D,
+ IFLE = 0x9E,
+ IF_ICMPEQ = 0x9F,
+ IF_ICMPNE = 0xA0,
+ IF_ICMPLT = 0xA1,
+ IF_ICMPGE = 0xA2,
+ IF_ICMPGT = 0xA3,
+ IF_ICMPLE = 0xA4,
+ IF_ACMPEQ = 0xA5,
+ IF_ACMPNE = 0xA6,
+ GOTO = 0xA7,
+ JSR = 0xA8,
+ RET = 0xA9,
+ TABLESWITCH = 0xAA,
+ LOOKUPSWITCH = 0xAB,
+ IRETURN = 0xAC,
+ LRETURN = 0xAD,
+ FRETURN = 0xAE,
+ DRETURN = 0xAF,
+ ARETURN = 0xB0,
+ RETURN = 0xB1,
+ GETSTATIC = 0xB2,
+ PUTSTATIC = 0xB3,
+ GETFIELD = 0xB4,
+ PUTFIELD = 0xB5,
+ INVOKEVIRTUAL = 0xB6,
+ INVOKESPECIAL = 0xB7,
+ INVOKESTATIC = 0xB8,
+ INVOKEINTERFACE = 0xB9,
+ NEW = 0xBB,
+ NEWARRAY = 0xBC,
+ ANEWARRAY = 0xBD,
+ ARRAYLENGTH = 0xBE,
+ ATHROW = 0xBF,
+ CHECKCAST = 0xC0,
+ INSTANCEOF = 0xC1,
+ MONITORENTER = 0xC2,
+ MONITOREXIT = 0xC3,
+ WIDE = 0xC4,
+ MULTIANEWARRAY = 0xC5,
+ IFNULL = 0xC6,
+ IFNONNULL = 0xC7,
+ GOTO_W = 0xC8,
+ JSR_W = 0xC9,
+ BREAKPOINT = 0xCA,
+
+ IMPDEP1 = 0xFE,
+ IMPDEP2 = 0xFF;
+
+ /**
+ * Types for the NEWARRAY opcode.
+ */
+ public static final byte
+ T_BOOLEAN = 4,
+ T_CHAR = 5,
+ T_FLOAT = 6,
+ T_DOUBLE = 7,
+ T_BYTE = 8,
+ T_SHORT = 9,
+ T_INT = 10,
+ T_LONG = 11;
+
+
+}
diff --git a/src/org/mozilla/classfile/ClassFileWriter.java b/src/org/mozilla/classfile/ClassFileWriter.java
new file mode 100644
index 0000000..f2d76cb
--- /dev/null
+++ b/src/org/mozilla/classfile/ClassFileWriter.java
@@ -0,0 +1,3043 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.classfile;
+
+import org.mozilla.javascript.ObjToIntMap;
+import org.mozilla.javascript.ObjArray;
+import org.mozilla.javascript.UintMap;
+
+import java.io.*;
+
+/**
+ * ClassFileWriter
+ *
+ * A ClassFileWriter is used to write a Java class file. Methods are
+ * provided to create fields and methods, and within methods to write
+ * Java bytecodes.
+ *
+ * @author Roger Lawrence
+ */
+public class ClassFileWriter {
+
+ /**
+ * Thrown for cases where the error in generating the class file is
+ * due to a program size constraints rather than a likely bug in the
+ * compiler.
+ */
+ public static class ClassFileFormatException extends RuntimeException {
+
+ private static final long serialVersionUID = 1263998431033790599L;
+
+ ClassFileFormatException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Construct a ClassFileWriter for a class.
+ *
+ * @param className the name of the class to write, including
+ * full package qualification.
+ * @param superClassName the name of the superclass of the class
+ * to write, including full package qualification.
+ * @param sourceFileName the name of the source file to use for
+ * producing debug information, or null if debug information
+ * is not desired
+ */
+ public ClassFileWriter(String className, String superClassName,
+ String sourceFileName)
+ {
+ generatedClassName = className;
+ itsConstantPool = new ConstantPool(this);
+ itsThisClassIndex = itsConstantPool.addClass(className);
+ itsSuperClassIndex = itsConstantPool.addClass(superClassName);
+ if (sourceFileName != null)
+ itsSourceFileNameIndex = itsConstantPool.addUtf8(sourceFileName);
+ itsFlags = ACC_PUBLIC;
+ }
+
+ public final String getClassName()
+ {
+ return generatedClassName;
+ }
+
+ /**
+ * Add an interface implemented by this class.
+ *
+ * This method may be called multiple times for classes that
+ * implement multiple interfaces.
+ *
+ * @param interfaceName a name of an interface implemented
+ * by the class being written, including full package
+ * qualification.
+ */
+ public void addInterface(String interfaceName) {
+ short interfaceIndex = itsConstantPool.addClass(interfaceName);
+ itsInterfaces.add(new Short(interfaceIndex));
+ }
+
+ public static final short
+ ACC_PUBLIC = 0x0001,
+ ACC_PRIVATE = 0x0002,
+ ACC_PROTECTED = 0x0004,
+ ACC_STATIC = 0x0008,
+ ACC_FINAL = 0x0010,
+ ACC_SYNCHRONIZED = 0x0020,
+ ACC_VOLATILE = 0x0040,
+ ACC_TRANSIENT = 0x0080,
+ ACC_NATIVE = 0x0100,
+ ACC_ABSTRACT = 0x0400;
+
+ /**
+ * Set the class's flags.
+ *
+ * Flags must be a set of the following flags, bitwise or'd
+ * together:
+ * ACC_PUBLIC
+ * ACC_PRIVATE
+ * ACC_PROTECTED
+ * ACC_FINAL
+ * ACC_ABSTRACT
+ * TODO: check that this is the appropriate set
+ * @param flags the set of class flags to set
+ */
+ public void setFlags(short flags) {
+ itsFlags = flags;
+ }
+
+ static String getSlashedForm(String name)
+ {
+ return name.replace('.', '/');
+ }
+
+ /**
+ * Convert Java class name in dot notation into
+ * "Lname-with-dots-replaced-by-slashes;" form suitable for use as
+ * JVM type signatures.
+ */
+ public static String classNameToSignature(String name)
+ {
+ int nameLength = name.length();
+ int colonPos = 1 + nameLength;
+ char[] buf = new char[colonPos + 1];
+ buf[0] = 'L';
+ buf[colonPos] = ';';
+ name.getChars(0, nameLength, buf, 1);
+ for (int i = 1; i != colonPos; ++i) {
+ if (buf[i] == '.') {
+ buf[i] = '/';
+ }
+ }
+ return new String(buf, 0, colonPos + 1);
+ }
+
+ /**
+ * Add a field to the class.
+ *
+ * @param fieldName the name of the field
+ * @param type the type of the field using ...
+ * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
+ * bitwise or'd together
+ */
+ public void addField(String fieldName, String type, short flags) {
+ short fieldNameIndex = itsConstantPool.addUtf8(fieldName);
+ short typeIndex = itsConstantPool.addUtf8(type);
+ itsFields.add(new ClassFileField(fieldNameIndex, typeIndex, flags));
+ }
+
+ /**
+ * Add a field to the class.
+ *
+ * @param fieldName the name of the field
+ * @param type the type of the field using ...
+ * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
+ * bitwise or'd together
+ * @param value an initial integral value
+ */
+ public void addField(String fieldName, String type, short flags,
+ int value)
+ {
+ short fieldNameIndex = itsConstantPool.addUtf8(fieldName);
+ short typeIndex = itsConstantPool.addUtf8(type);
+ ClassFileField field = new ClassFileField(fieldNameIndex, typeIndex,
+ flags);
+ field.setAttributes(itsConstantPool.addUtf8("ConstantValue"),
+ (short)0,
+ (short)0,
+ itsConstantPool.addConstant(value));
+ itsFields.add(field);
+ }
+
+ /**
+ * Add a field to the class.
+ *
+ * @param fieldName the name of the field
+ * @param type the type of the field using ...
+ * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
+ * bitwise or'd together
+ * @param value an initial long value
+ */
+ public void addField(String fieldName, String type, short flags,
+ long value)
+ {
+ short fieldNameIndex = itsConstantPool.addUtf8(fieldName);
+ short typeIndex = itsConstantPool.addUtf8(type);
+ ClassFileField field = new ClassFileField(fieldNameIndex, typeIndex,
+ flags);
+ field.setAttributes(itsConstantPool.addUtf8("ConstantValue"),
+ (short)0,
+ (short)2,
+ itsConstantPool.addConstant(value));
+ itsFields.add(field);
+ }
+
+ /**
+ * Add a field to the class.
+ *
+ * @param fieldName the name of the field
+ * @param type the type of the field using ...
+ * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
+ * bitwise or'd together
+ * @param value an initial double value
+ */
+ public void addField(String fieldName, String type, short flags,
+ double value)
+ {
+ short fieldNameIndex = itsConstantPool.addUtf8(fieldName);
+ short typeIndex = itsConstantPool.addUtf8(type);
+ ClassFileField field = new ClassFileField(fieldNameIndex, typeIndex,
+ flags);
+ field.setAttributes(itsConstantPool.addUtf8("ConstantValue"),
+ (short)0,
+ (short)2,
+ itsConstantPool.addConstant(value));
+ itsFields.add(field);
+ }
+
+ /**
+ * Add Information about java variable to use when generating the local
+ * variable table.
+ *
+ * @param name variable name.
+ * @param type variable type as bytecode descriptor string.
+ * @param startPC the starting bytecode PC where this variable is live,
+ * or -1 if it does not have a Java register.
+ * @param register the Java register number of variable
+ * or -1 if it does not have a Java register.
+ */
+ public void addVariableDescriptor(String name, String type, int startPC, int register)
+ {
+ int nameIndex = itsConstantPool.addUtf8(name);
+ int descriptorIndex = itsConstantPool.addUtf8(type);
+ int [] chunk = { nameIndex, descriptorIndex, startPC, register };
+ if (itsVarDescriptors == null) {
+ itsVarDescriptors = new ObjArray();
+ }
+ itsVarDescriptors.add(chunk);
+ }
+
+ /**
+ * Add a method and begin adding code.
+ *
+ * This method must be called before other methods for adding code,
+ * exception tables, etc. can be invoked.
+ *
+ * @param methodName the name of the method
+ * @param type a string representing the type
+ * @param flags the attributes of the field, such as ACC_PUBLIC, etc.
+ * bitwise or'd together
+ */
+ public void startMethod(String methodName, String type, short flags) {
+ short methodNameIndex = itsConstantPool.addUtf8(methodName);
+ short typeIndex = itsConstantPool.addUtf8(type);
+ itsCurrentMethod = new ClassFileMethod(methodNameIndex, typeIndex,
+ flags);
+ itsMethods.add(itsCurrentMethod);
+ }
+
+ /**
+ * Complete generation of the method.
+ *
+ * After this method is called, no more code can be added to the
+ * method begun with <code>startMethod</code>.
+ *
+ * @param maxLocals the maximum number of local variable slots
+ * (a.k.a. Java registers) used by the method
+ */
+ public void stopMethod(short maxLocals) {
+ if (itsCurrentMethod == null)
+ throw new IllegalStateException("No method to stop");
+
+ fixLabelGotos();
+
+ itsMaxLocals = maxLocals;
+
+ int lineNumberTableLength = 0;
+ if (itsLineNumberTable != null) {
+ // 6 bytes for the attribute header
+ // 2 bytes for the line number count
+ // 4 bytes for each entry
+ lineNumberTableLength = 6 + 2 + (itsLineNumberTableTop * 4);
+ }
+
+ int variableTableLength = 0;
+ if (itsVarDescriptors != null) {
+ // 6 bytes for the attribute header
+ // 2 bytes for the variable count
+ // 10 bytes for each entry
+ variableTableLength = 6 + 2 + (itsVarDescriptors.size() * 10);
+ }
+
+ int attrLength = 2 + // attribute_name_index
+ 4 + // attribute_length
+ 2 + // max_stack
+ 2 + // max_locals
+ 4 + // code_length
+ itsCodeBufferTop +
+ 2 + // exception_table_length
+ (itsExceptionTableTop * 8) +
+ 2 + // attributes_count
+ lineNumberTableLength +
+ variableTableLength;
+
+ if (attrLength > 65536) {
+ // See http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html,
+ // section 4.10, "The amount of code per non-native, non-abstract
+ // method is limited to 65536 bytes...
+ throw new ClassFileFormatException(
+ "generated bytecode for method exceeds 64K limit.");
+ }
+ byte[] codeAttribute = new byte[attrLength];
+ int index = 0;
+ int codeAttrIndex = itsConstantPool.addUtf8("Code");
+ index = putInt16(codeAttrIndex, codeAttribute, index);
+ attrLength -= 6; // discount the attribute header
+ index = putInt32(attrLength, codeAttribute, index);
+ index = putInt16(itsMaxStack, codeAttribute, index);
+ index = putInt16(itsMaxLocals, codeAttribute, index);
+ index = putInt32(itsCodeBufferTop, codeAttribute, index);
+ System.arraycopy(itsCodeBuffer, 0, codeAttribute, index,
+ itsCodeBufferTop);
+ index += itsCodeBufferTop;
+
+ if (itsExceptionTableTop > 0) {
+ index = putInt16(itsExceptionTableTop, codeAttribute, index);
+ for (int i = 0; i < itsExceptionTableTop; i++) {
+ ExceptionTableEntry ete = itsExceptionTable[i];
+ short startPC = (short)getLabelPC(ete.itsStartLabel);
+ short endPC = (short)getLabelPC(ete.itsEndLabel);
+ short handlerPC = (short)getLabelPC(ete.itsHandlerLabel);
+ short catchType = ete.itsCatchType;
+ if (startPC == -1)
+ throw new IllegalStateException("start label not defined");
+ if (endPC == -1)
+ throw new IllegalStateException("end label not defined");
+ if (handlerPC == -1)
+ throw new IllegalStateException(
+ "handler label not defined");
+
+ index = putInt16(startPC, codeAttribute, index);
+ index = putInt16(endPC, codeAttribute, index);
+ index = putInt16(handlerPC, codeAttribute, index);
+ index = putInt16(catchType, codeAttribute, index);
+ }
+ }
+ else {
+ // write 0 as exception table length
+ index = putInt16(0, codeAttribute, index);
+ }
+
+ int attributeCount = 0;
+ if (itsLineNumberTable != null)
+ attributeCount++;
+ if (itsVarDescriptors != null)
+ attributeCount++;
+ index = putInt16(attributeCount, codeAttribute, index);
+
+ if (itsLineNumberTable != null) {
+ int lineNumberTableAttrIndex
+ = itsConstantPool.addUtf8("LineNumberTable");
+ index = putInt16(lineNumberTableAttrIndex, codeAttribute, index);
+ int tableAttrLength = 2 + (itsLineNumberTableTop * 4);
+ index = putInt32(tableAttrLength, codeAttribute, index);
+ index = putInt16(itsLineNumberTableTop, codeAttribute, index);
+ for (int i = 0; i < itsLineNumberTableTop; i++) {
+ index = putInt32(itsLineNumberTable[i], codeAttribute, index);
+ }
+ }
+
+ if (itsVarDescriptors != null) {
+ int variableTableAttrIndex
+ = itsConstantPool.addUtf8("LocalVariableTable");
+ index = putInt16(variableTableAttrIndex, codeAttribute, index);
+ int varCount = itsVarDescriptors.size();
+ int tableAttrLength = 2 + (varCount * 10);
+ index = putInt32(tableAttrLength, codeAttribute, index);
+ index = putInt16(varCount, codeAttribute, index);
+ for (int i = 0; i < varCount; i++) {
+ int[] chunk = (int[])itsVarDescriptors.get(i);
+ int nameIndex = chunk[0];
+ int descriptorIndex = chunk[1];
+ int startPC = chunk[2];
+ int register = chunk[3];
+ int length = itsCodeBufferTop - startPC;
+
+ index = putInt16(startPC, codeAttribute, index);
+ index = putInt16(length, codeAttribute, index);
+ index = putInt16(nameIndex, codeAttribute, index);
+ index = putInt16(descriptorIndex, codeAttribute, index);
+ index = putInt16(register, codeAttribute, index);
+ }
+ }
+
+ itsCurrentMethod.setCodeAttribute(codeAttribute);
+
+ itsExceptionTable = null;
+ itsExceptionTableTop = 0;
+ itsLineNumberTableTop = 0;
+ itsCodeBufferTop = 0;
+ itsCurrentMethod = null;
+ itsMaxStack = 0;
+ itsStackTop = 0;
+ itsLabelTableTop = 0;
+ itsFixupTableTop = 0;
+ itsVarDescriptors = null;
+ }
+
+ /**
+ * Add the single-byte opcode to the current method.
+ *
+ * @param theOpCode the opcode of the bytecode
+ */
+ public void add(int theOpCode) {
+ if (opcodeCount(theOpCode) != 0)
+ throw new IllegalArgumentException("Unexpected operands");
+ int newStack = itsStackTop + stackChange(theOpCode);
+ if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
+ if (DEBUGCODE)
+ System.out.println("Add " + bytecodeStr(theOpCode));
+ addToCodeBuffer(theOpCode);
+ itsStackTop = (short)newStack;
+ if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
+ if (DEBUGSTACK) {
+ System.out.println("After "+bytecodeStr(theOpCode)
+ +" stack = "+itsStackTop);
+ }
+ }
+
+ /**
+ * Add a single-operand opcode to the current method.
+ *
+ * @param theOpCode the opcode of the bytecode
+ * @param theOperand the operand of the bytecode
+ */
+ public void add(int theOpCode, int theOperand) {
+ if (DEBUGCODE) {
+ System.out.println("Add "+bytecodeStr(theOpCode)
+ +", "+Integer.toHexString(theOperand));
+ }
+ int newStack = itsStackTop + stackChange(theOpCode);
+ if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
+
+ switch (theOpCode) {
+ case ByteCode.GOTO :
+ // fallthru...
+ case ByteCode.IFEQ :
+ case ByteCode.IFNE :
+ case ByteCode.IFLT :
+ case ByteCode.IFGE :
+ case ByteCode.IFGT :
+ case ByteCode.IFLE :
+ case ByteCode.IF_ICMPEQ :
+ case ByteCode.IF_ICMPNE :
+ case ByteCode.IF_ICMPLT :
+ case ByteCode.IF_ICMPGE :
+ case ByteCode.IF_ICMPGT :
+ case ByteCode.IF_ICMPLE :
+ case ByteCode.IF_ACMPEQ :
+ case ByteCode.IF_ACMPNE :
+ case ByteCode.JSR :
+ case ByteCode.IFNULL :
+ case ByteCode.IFNONNULL : {
+ if ((theOperand & 0x80000000) != 0x80000000) {
+ if ((theOperand < 0) || (theOperand > 65535))
+ throw new IllegalArgumentException(
+ "Bad label for branch");
+ }
+ int branchPC = itsCodeBufferTop;
+ addToCodeBuffer(theOpCode);
+ if ((theOperand & 0x80000000) != 0x80000000) {
+ // hard displacement
+ addToCodeInt16(theOperand);
+ }
+ else { // a label
+ int targetPC = getLabelPC(theOperand);
+ if (DEBUGLABELS) {
+ int theLabel = theOperand & 0x7FFFFFFF;
+ System.out.println("Fixing branch to " +
+ theLabel + " at " + targetPC +
+ " from " + branchPC);
+ }
+ if (targetPC != -1) {
+ int offset = targetPC - branchPC;
+ addToCodeInt16(offset);
+ }
+ else {
+ addLabelFixup(theOperand, branchPC + 1);
+ addToCodeInt16(0);
+ }
+ }
+ }
+ break;
+
+ case ByteCode.BIPUSH :
+ if ((byte)theOperand != theOperand)
+ throw new IllegalArgumentException("out of range byte");
+ addToCodeBuffer(theOpCode);
+ addToCodeBuffer((byte)theOperand);
+ break;
+
+ case ByteCode.SIPUSH :
+ if ((short)theOperand != theOperand)
+ throw new IllegalArgumentException("out of range short");
+ addToCodeBuffer(theOpCode);
+ addToCodeInt16(theOperand);
+ break;
+
+ case ByteCode.NEWARRAY :
+ if (!(0 <= theOperand && theOperand < 256))
+ throw new IllegalArgumentException("out of range index");
+ addToCodeBuffer(theOpCode);
+ addToCodeBuffer(theOperand);
+ break;
+
+ case ByteCode.GETFIELD :
+ case ByteCode.PUTFIELD :
+ if (!(0 <= theOperand && theOperand < 65536))
+ throw new IllegalArgumentException("out of range field");
+ addToCodeBuffer(theOpCode);
+ addToCodeInt16(theOperand);
+ break;
+
+ case ByteCode.LDC :
+ case ByteCode.LDC_W :
+ case ByteCode.LDC2_W :
+ if (!(0 <= theOperand && theOperand < 65536))
+ throw new IllegalArgumentException("out of range index");
+ if (theOperand >= 256
+ || theOpCode == ByteCode.LDC_W
+ || theOpCode == ByteCode.LDC2_W)
+ {
+ if (theOpCode == ByteCode.LDC) {
+ addToCodeBuffer(ByteCode.LDC_W);
+ } else {
+ addToCodeBuffer(theOpCode);
+ }
+ addToCodeInt16(theOperand);
+ } else {
+ addToCodeBuffer(theOpCode);
+ addToCodeBuffer(theOperand);
+ }
+ break;
+
+ case ByteCode.RET :
+ case ByteCode.ILOAD :
+ case ByteCode.LLOAD :
+ case ByteCode.FLOAD :
+ case ByteCode.DLOAD :
+ case ByteCode.ALOAD :
+ case ByteCode.ISTORE :
+ case ByteCode.LSTORE :
+ case ByteCode.FSTORE :
+ case ByteCode.DSTORE :
+ case ByteCode.ASTORE :
+ if (!(0 <= theOperand && theOperand < 65536))
+ throw new ClassFileFormatException("out of range variable");
+ if (theOperand >= 256) {
+ addToCodeBuffer(ByteCode.WIDE);
+ addToCodeBuffer(theOpCode);
+ addToCodeInt16(theOperand);
+ }
+ else {
+ addToCodeBuffer(theOpCode);
+ addToCodeBuffer(theOperand);
+ }
+ break;
+
+ default :
+ throw new IllegalArgumentException(
+ "Unexpected opcode for 1 operand");
+ }
+
+ itsStackTop = (short)newStack;
+ if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
+ if (DEBUGSTACK) {
+ System.out.println("After "+bytecodeStr(theOpCode)
+ +" stack = "+itsStackTop);
+ }
+ }
+
+ /**
+ * Generate the load constant bytecode for the given integer.
+ *
+ * @param k the constant
+ */
+ public void addLoadConstant(int k) {
+ switch (k) {
+ case 0: add(ByteCode.ICONST_0); break;
+ case 1: add(ByteCode.ICONST_1); break;
+ case 2: add(ByteCode.ICONST_2); break;
+ case 3: add(ByteCode.ICONST_3); break;
+ case 4: add(ByteCode.ICONST_4); break;
+ case 5: add(ByteCode.ICONST_5); break;
+ default:
+ add(ByteCode.LDC, itsConstantPool.addConstant(k));
+ break;
+ }
+ }
+
+ /**
+ * Generate the load constant bytecode for the given long.
+ *
+ * @param k the constant
+ */
+ public void addLoadConstant(long k) {
+ add(ByteCode.LDC2_W, itsConstantPool.addConstant(k));
+ }
+
+ /**
+ * Generate the load constant bytecode for the given float.
+ *
+ * @param k the constant
+ */
+ public void addLoadConstant(float k) {
+ add(ByteCode.LDC, itsConstantPool.addConstant(k));
+ }
+
+ /**
+ * Generate the load constant bytecode for the given double.
+ *
+ * @param k the constant
+ */
+ public void addLoadConstant(double k) {
+ add(ByteCode.LDC2_W, itsConstantPool.addConstant(k));
+ }
+
+ /**
+ * Generate the load constant bytecode for the given string.
+ *
+ * @param k the constant
+ */
+ public void addLoadConstant(String k) {
+ add(ByteCode.LDC, itsConstantPool.addConstant(k));
+ }
+
+ /**
+ * Add the given two-operand bytecode to the current method.
+ *
+ * @param theOpCode the opcode of the bytecode
+ * @param theOperand1 the first operand of the bytecode
+ * @param theOperand2 the second operand of the bytecode
+ */
+ public void add(int theOpCode, int theOperand1, int theOperand2) {
+ if (DEBUGCODE) {
+ System.out.println("Add "+bytecodeStr(theOpCode)
+ +", "+Integer.toHexString(theOperand1)
+ +", "+Integer.toHexString(theOperand2));
+ }
+ int newStack = itsStackTop + stackChange(theOpCode);
+ if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
+
+ if (theOpCode == ByteCode.IINC) {
+ if (!(0 <= theOperand1 && theOperand1 < 65536))
+ throw new ClassFileFormatException("out of range variable");
+ if (!(0 <= theOperand2 && theOperand2 < 65536))
+ throw new ClassFileFormatException("out of range increment");
+
+ if (theOperand1 > 255 || theOperand2 < -128 || theOperand2 > 127) {
+ addToCodeBuffer(ByteCode.WIDE);
+ addToCodeBuffer(ByteCode.IINC);
+ addToCodeInt16(theOperand1);
+ addToCodeInt16(theOperand2);
+ }
+ else {
+ addToCodeBuffer(ByteCode.WIDE);
+ addToCodeBuffer(ByteCode.IINC);
+ addToCodeBuffer(theOperand1);
+ addToCodeBuffer(theOperand2);
+ }
+ }
+ else if (theOpCode == ByteCode.MULTIANEWARRAY) {
+ if (!(0 <= theOperand1 && theOperand1 < 65536))
+ throw new IllegalArgumentException("out of range index");
+ if (!(0 <= theOperand2 && theOperand2 < 256))
+ throw new IllegalArgumentException("out of range dimensions");
+
+ addToCodeBuffer(ByteCode.MULTIANEWARRAY);
+ addToCodeInt16(theOperand1);
+ addToCodeBuffer(theOperand2);
+ }
+ else {
+ throw new IllegalArgumentException(
+ "Unexpected opcode for 2 operands");
+ }
+ itsStackTop = (short)newStack;
+ if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
+ if (DEBUGSTACK) {
+ System.out.println("After "+bytecodeStr(theOpCode)
+ +" stack = "+itsStackTop);
+ }
+
+ }
+
+ public void add(int theOpCode, String className) {
+ if (DEBUGCODE) {
+ System.out.println("Add "+bytecodeStr(theOpCode)
+ +", "+className);
+ }
+ int newStack = itsStackTop + stackChange(theOpCode);
+ if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
+ switch (theOpCode) {
+ case ByteCode.NEW :
+ case ByteCode.ANEWARRAY :
+ case ByteCode.CHECKCAST :
+ case ByteCode.INSTANCEOF : {
+ short classIndex = itsConstantPool.addClass(className);
+ addToCodeBuffer(theOpCode);
+ addToCodeInt16(classIndex);
+ }
+ break;
+
+ default :
+ throw new IllegalArgumentException(
+ "bad opcode for class reference");
+ }
+ itsStackTop = (short)newStack;
+ if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
+ if (DEBUGSTACK) {
+ System.out.println("After "+bytecodeStr(theOpCode)
+ +" stack = "+itsStackTop);
+ }
+ }
+
+
+ public void add(int theOpCode, String className, String fieldName,
+ String fieldType)
+ {
+ if (DEBUGCODE) {
+ System.out.println("Add "+bytecodeStr(theOpCode)
+ +", "+className+", "+fieldName+", "+fieldType);
+ }
+ int newStack = itsStackTop + stackChange(theOpCode);
+ char fieldTypeChar = fieldType.charAt(0);
+ int fieldSize = (fieldTypeChar == 'J' || fieldTypeChar == 'D')
+ ? 2 : 1;
+ switch (theOpCode) {
+ case ByteCode.GETFIELD :
+ case ByteCode.GETSTATIC :
+ newStack += fieldSize;
+ break;
+ case ByteCode.PUTSTATIC :
+ case ByteCode.PUTFIELD :
+ newStack -= fieldSize;
+ break;
+ default :
+ throw new IllegalArgumentException(
+ "bad opcode for field reference");
+ }
+ if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
+ short fieldRefIndex = itsConstantPool.addFieldRef(className,
+ fieldName, fieldType);
+ addToCodeBuffer(theOpCode);
+ addToCodeInt16(fieldRefIndex);
+
+ itsStackTop = (short)newStack;
+ if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
+ if (DEBUGSTACK) {
+ System.out.println("After "+bytecodeStr(theOpCode)
+ +" stack = "+itsStackTop);
+ }
+ }
+
+ public void addInvoke(int theOpCode, String className, String methodName,
+ String methodType)
+ {
+ if (DEBUGCODE) {
+ System.out.println("Add "+bytecodeStr(theOpCode)
+ +", "+className+", "+methodName+", "
+ +methodType);
+ }
+ int parameterInfo = sizeOfParameters(methodType);
+ int parameterCount = parameterInfo >>> 16;
+ int stackDiff = (short)parameterInfo;
+
+ int newStack = itsStackTop + stackDiff;
+ newStack += stackChange(theOpCode); // adjusts for 'this'
+ if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
+
+ switch (theOpCode) {
+ case ByteCode.INVOKEVIRTUAL :
+ case ByteCode.INVOKESPECIAL :
+ case ByteCode.INVOKESTATIC :
+ case ByteCode.INVOKEINTERFACE : {
+ addToCodeBuffer(theOpCode);
+ if (theOpCode == ByteCode.INVOKEINTERFACE) {
+ short ifMethodRefIndex
+ = itsConstantPool.addInterfaceMethodRef(
+ className, methodName,
+ methodType);
+ addToCodeInt16(ifMethodRefIndex);
+ addToCodeBuffer(parameterCount + 1);
+ addToCodeBuffer(0);
+ }
+ else {
+ short methodRefIndex = itsConstantPool.addMethodRef(
+ className, methodName,
+ methodType);
+ addToCodeInt16(methodRefIndex);
+ }
+ }
+ break;
+
+ default :
+ throw new IllegalArgumentException(
+ "bad opcode for method reference");
+ }
+ itsStackTop = (short)newStack;
+ if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
+ if (DEBUGSTACK) {
+ System.out.println("After "+bytecodeStr(theOpCode)
+ +" stack = "+itsStackTop);
+ }
+ }
+
+ /**
+ * Generate code to load the given integer on stack.
+ *
+ * @param k the constant
+ */
+ public void addPush(int k)
+ {
+ if ((byte)k == k) {
+ if (k == -1) {
+ add(ByteCode.ICONST_M1);
+ } else if (0 <= k && k <= 5) {
+ add((byte)(ByteCode.ICONST_0 + k));
+ } else {
+ add(ByteCode.BIPUSH, (byte)k);
+ }
+ } else if ((short)k == k) {
+ add(ByteCode.SIPUSH, (short)k);
+ } else {
+ addLoadConstant(k);
+ }
+ }
+
+ public void addPush(boolean k)
+ {
+ add(k ? ByteCode.ICONST_1 : ByteCode.ICONST_0);
+ }
+
+ /**
+ * Generate code to load the given long on stack.
+ *
+ * @param k the constant
+ */
+ public void addPush(long k)
+ {
+ int ik = (int)k;
+ if (ik == k) {
+ addPush(ik);
+ add(ByteCode.I2L);
+ } else {
+ addLoadConstant(k);
+ }
+ }
+
+ /**
+ * Generate code to load the given double on stack.
+ *
+ * @param k the constant
+ */
+ public void addPush(double k)
+ {
+ if (k == 0.0) {
+ // zero
+ add(ByteCode.DCONST_0);
+ if (1.0 / k < 0) {
+ // Negative zero
+ add(ByteCode.DNEG);
+ }
+ } else if (k == 1.0 || k == -1.0) {
+ add(ByteCode.DCONST_1);
+ if (k < 0) {
+ add(ByteCode.DNEG);
+ }
+ } else {
+ addLoadConstant(k);
+ }
+ }
+
+ /**
+ * Generate the code to leave on stack the given string even if the
+ * string encoding exeeds the class file limit for single string constant
+ *
+ * @param k the constant
+ */
+ public void addPush(String k) {
+ int length = k.length();
+ int limit = itsConstantPool.getUtfEncodingLimit(k, 0, length);
+ if (limit == length) {
+ addLoadConstant(k);
+ return;
+ }
+ // Split string into picies fitting the UTF limit and generate code for
+ // StringBuffer sb = new StringBuffer(length);
+ // sb.append(loadConstant(piece_1));
+ // ...
+ // sb.append(loadConstant(piece_N));
+ // sb.toString();
+ final String SB = "java/lang/StringBuffer";
+ add(ByteCode.NEW, SB);
+ add(ByteCode.DUP);
+ addPush(length);
+ addInvoke(ByteCode.INVOKESPECIAL, SB, "<init>", "(I)V");
+ int cursor = 0;
+ for (;;) {
+ add(ByteCode.DUP);
+ String s = k.substring(cursor, limit);
+ addLoadConstant(s);
+ addInvoke(ByteCode.INVOKEVIRTUAL, SB, "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
+ add(ByteCode.POP);
+ if (limit == length) {
+ break;
+ }
+ cursor = limit;
+ limit = itsConstantPool.getUtfEncodingLimit(k, limit, length);
+ }
+ addInvoke(ByteCode.INVOKEVIRTUAL, SB, "toString",
+ "()Ljava/lang/String;");
+ }
+
+ /**
+ * Check if k fits limit on string constant size imposed by class file
+ * format.
+ *
+ * @param k the string constant
+ */
+ public boolean isUnderStringSizeLimit(String k)
+ {
+ return itsConstantPool.isUnderUtfEncodingLimit(k);
+ }
+
+ /**
+ * Store integer from stack top into the given local.
+ *
+ * @param local number of local register
+ */
+ public void addIStore(int local)
+ {
+ xop(ByteCode.ISTORE_0, ByteCode.ISTORE, local);
+ }
+
+ /**
+ * Store long from stack top into the given local.
+ *
+ * @param local number of local register
+ */
+ public void addLStore(int local)
+ {
+ xop(ByteCode.LSTORE_0, ByteCode.LSTORE, local);
+ }
+
+ /**
+ * Store float from stack top into the given local.
+ *
+ * @param local number of local register
+ */
+ public void addFStore(int local)
+ {
+ xop(ByteCode.FSTORE_0, ByteCode.FSTORE, local);
+ }
+
+ /**
+ * Store double from stack top into the given local.
+ *
+ * @param local number of local register
+ */
+ public void addDStore(int local)
+ {
+ xop(ByteCode.DSTORE_0, ByteCode.DSTORE, local);
+ }
+
+ /**
+ * Store object from stack top into the given local.
+ *
+ * @param local number of local register
+ */
+ public void addAStore(int local)
+ {
+ xop(ByteCode.ASTORE_0, ByteCode.ASTORE, local);
+ }
+
+ /**
+ * Load integer from the given local into stack.
+ *
+ * @param local number of local register
+ */
+ public void addILoad(int local)
+ {
+ xop(ByteCode.ILOAD_0, ByteCode.ILOAD, local);
+ }
+
+ /**
+ * Load long from the given local into stack.
+ *
+ * @param local number of local register
+ */
+ public void addLLoad(int local)
+ {
+ xop(ByteCode.LLOAD_0, ByteCode.LLOAD, local);
+ }
+
+ /**
+ * Load float from the given local into stack.
+ *
+ * @param local number of local register
+ */
+ public void addFLoad(int local)
+ {
+ xop(ByteCode.FLOAD_0, ByteCode.FLOAD, local);
+ }
+
+ /**
+ * Load double from the given local into stack.
+ *
+ * @param local number of local register
+ */
+ public void addDLoad(int local)
+ {
+ xop(ByteCode.DLOAD_0, ByteCode.DLOAD, local);
+ }
+
+ /**
+ * Load object from the given local into stack.
+ *
+ * @param local number of local register
+ */
+ public void addALoad(int local)
+ {
+ xop(ByteCode.ALOAD_0, ByteCode.ALOAD, local);
+ }
+
+ /**
+ * Load "this" into stack.
+ */
+ public void addLoadThis()
+ {
+ add(ByteCode.ALOAD_0);
+ }
+
+ private void xop(int shortOp, int op, int local)
+ {
+ switch (local) {
+ case 0:
+ add(shortOp);
+ break;
+ case 1:
+ add(shortOp + 1);
+ break;
+ case 2:
+ add(shortOp + 2);
+ break;
+ case 3:
+ add(shortOp + 3);
+ break;
+ default:
+ add(op, local);
+ }
+ }
+
+ public int addTableSwitch(int low, int high)
+ {
+ if (DEBUGCODE) {
+ System.out.println("Add "+bytecodeStr(ByteCode.TABLESWITCH)
+ +" "+low+" "+high);
+ }
+ if (low > high)
+ throw new ClassFileFormatException("Bad bounds: "+low+' '+ high);
+
+ int newStack = itsStackTop + stackChange(ByteCode.TABLESWITCH);
+ if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
+
+ int entryCount = high - low + 1;
+ int padSize = 3 & ~itsCodeBufferTop; // == 3 - itsCodeBufferTop % 4
+
+ int N = addReservedCodeSpace(1 + padSize + 4 * (1 + 2 + entryCount));
+ int switchStart = N;
+ itsCodeBuffer[N++] = (byte)ByteCode.TABLESWITCH;
+ while (padSize != 0) {
+ itsCodeBuffer[N++] = 0;
+ --padSize;
+ }
+ N += 4; // skip default offset
+ N = putInt32(low, itsCodeBuffer, N);
+ putInt32(high, itsCodeBuffer, N);
+
+ itsStackTop = (short)newStack;
+ if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
+ if (DEBUGSTACK) {
+ System.out.println("After "+bytecodeStr(ByteCode.TABLESWITCH)
+ +" stack = "+itsStackTop);
+ }
+
+ return switchStart;
+ }
+
+ public final void markTableSwitchDefault(int switchStart)
+ {
+ setTableSwitchJump(switchStart, -1, itsCodeBufferTop);
+ }
+
+ public final void markTableSwitchCase(int switchStart, int caseIndex)
+ {
+ setTableSwitchJump(switchStart, caseIndex, itsCodeBufferTop);
+ }
+
+ public final void markTableSwitchCase(int switchStart, int caseIndex,
+ int stackTop)
+ {
+ if (!(0 <= stackTop && stackTop <= itsMaxStack))
+ throw new IllegalArgumentException("Bad stack index: "+stackTop);
+ itsStackTop = (short)stackTop;
+ setTableSwitchJump(switchStart, caseIndex, itsCodeBufferTop);
+ }
+
+ public void setTableSwitchJump(int switchStart, int caseIndex,
+ int jumpTarget)
+ {
+ if (!(0 <= jumpTarget && jumpTarget <= itsCodeBufferTop))
+ throw new IllegalArgumentException("Bad jump target: "+jumpTarget);
+ if (!(caseIndex >= -1))
+ throw new IllegalArgumentException("Bad case index: "+caseIndex);
+
+ int padSize = 3 & ~switchStart; // == 3 - switchStart % 4
+ int caseOffset;
+ if (caseIndex < 0) {
+ // default label
+ caseOffset = switchStart + 1 + padSize;
+ } else {
+ caseOffset = switchStart + 1 + padSize + 4 * (3 + caseIndex);
+ }
+ if (!(0 <= switchStart
+ && switchStart <= itsCodeBufferTop - 4 * 4 - padSize - 1))
+ {
+ throw new IllegalArgumentException(
+ switchStart+" is outside a possible range of tableswitch"
+ +" in already generated code");
+ }
+ if ((0xFF & itsCodeBuffer[switchStart]) != ByteCode.TABLESWITCH) {
+ throw new IllegalArgumentException(
+ switchStart+" is not offset of tableswitch statement");
+ }
+ if (!(0 <= caseOffset && caseOffset + 4 <= itsCodeBufferTop)) {
+ // caseIndex >= -1 does not guarantee that caseOffset >= 0 due
+ // to a possible overflow.
+ throw new ClassFileFormatException(
+ "Too big case index: "+caseIndex);
+ }
+ // ALERT: perhaps check against case bounds?
+ putInt32(jumpTarget - switchStart, itsCodeBuffer, caseOffset);
+ }
+
+ public int acquireLabel()
+ {
+ int top = itsLabelTableTop;
+ if (itsLabelTable == null || top == itsLabelTable.length) {
+ if (itsLabelTable == null) {
+ itsLabelTable = new int[MIN_LABEL_TABLE_SIZE];
+ }else {
+ int[] tmp = new int[itsLabelTable.length * 2];
+ System.arraycopy(itsLabelTable, 0, tmp, 0, top);
+ itsLabelTable = tmp;
+ }
+ }
+ itsLabelTableTop = top + 1;
+ itsLabelTable[top] = -1;
+ return top | 0x80000000;
+ }
+
+ public void markLabel(int label)
+ {
+ if (!(label < 0))
+ throw new IllegalArgumentException("Bad label, no biscuit");
+
+ label &= 0x7FFFFFFF;
+ if (label > itsLabelTableTop)
+ throw new IllegalArgumentException("Bad label");
+
+ if (itsLabelTable[label] != -1) {
+ throw new IllegalStateException("Can only mark label once");
+ }
+
+ itsLabelTable[label] = itsCodeBufferTop;
+ }
+
+ public void markLabel(int label, short stackTop)
+ {
+ markLabel(label);
+ itsStackTop = stackTop;
+ }
+
+ public void markHandler(int theLabel) {
+ itsStackTop = 1;
+ markLabel(theLabel);
+ }
+
+ private int getLabelPC(int label)
+ {
+ if (!(label < 0))
+ throw new IllegalArgumentException("Bad label, no biscuit");
+ label &= 0x7FFFFFFF;
+ if (!(label < itsLabelTableTop))
+ throw new IllegalArgumentException("Bad label");
+ return itsLabelTable[label];
+ }
+
+ private void addLabelFixup(int label, int fixupSite)
+ {
+ if (!(label < 0))
+ throw new IllegalArgumentException("Bad label, no biscuit");
+ label &= 0x7FFFFFFF;
+ if (!(label < itsLabelTableTop))
+ throw new IllegalArgumentException("Bad label");
+ int top = itsFixupTableTop;
+ if (itsFixupTable == null || top == itsFixupTable.length) {
+ if (itsFixupTable == null) {
+ itsFixupTable = new long[MIN_FIXUP_TABLE_SIZE];
+ }else {
+ long[] tmp = new long[itsFixupTable.length * 2];
+ System.arraycopy(itsFixupTable, 0, tmp, 0, top);
+ itsFixupTable = tmp;
+ }
+ }
+ itsFixupTableTop = top + 1;
+ itsFixupTable[top] = ((long)label << 32) | fixupSite;
+ }
+
+ private void fixLabelGotos()
+ {
+ byte[] codeBuffer = itsCodeBuffer;
+ for (int i = 0; i < itsFixupTableTop; i++) {
+ long fixup = itsFixupTable[i];
+ int label = (int)(fixup >> 32);
+ int fixupSite = (int)fixup;
+ int pc = itsLabelTable[label];
+ if (pc == -1) {
+ // Unlocated label
+ throw new RuntimeException();
+ }
+ // -1 to get delta from instruction start
+ int offset = pc - (fixupSite - 1);
+ if ((short)offset != offset) {
+ throw new ClassFileFormatException
+ ("Program too complex: too big jump offset");
+ }
+ codeBuffer[fixupSite] = (byte)(offset >> 8);
+ codeBuffer[fixupSite + 1] = (byte)offset;
+ }
+ itsFixupTableTop = 0;
+ }
+
+ /**
+ * Get the current offset into the code of the current method.
+ *
+ * @return an integer representing the offset
+ */
+ public int getCurrentCodeOffset() {
+ return itsCodeBufferTop;
+ }
+
+ public short getStackTop() {
+ return itsStackTop;
+ }
+
+ public void setStackTop(short n) {
+ itsStackTop = n;
+ }
+
+ public void adjustStackTop(int delta) {
+ int newStack = itsStackTop + delta;
+ if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack);
+ itsStackTop = (short)newStack;
+ if (newStack > itsMaxStack) itsMaxStack = (short)newStack;
+ if (DEBUGSTACK) {
+ System.out.println("After "+"adjustStackTop("+delta+")"
+ +" stack = "+itsStackTop);
+ }
+ }
+
+ private void addToCodeBuffer(int b)
+ {
+ int N = addReservedCodeSpace(1);
+ itsCodeBuffer[N] = (byte)b;
+ }
+
+ private void addToCodeInt16(int value)
+ {
+ int N = addReservedCodeSpace(2);
+ putInt16(value, itsCodeBuffer, N);
+ }
+
+ private int addReservedCodeSpace(int size)
+ {
+ if (itsCurrentMethod == null)
+ throw new IllegalArgumentException("No method to add to");
+ int oldTop = itsCodeBufferTop;
+ int newTop = oldTop + size;
+ if (newTop > itsCodeBuffer.length) {
+ int newSize = itsCodeBuffer.length * 2;
+ if (newTop > newSize) { newSize = newTop; }
+ byte[] tmp = new byte[newSize];
+ System.arraycopy(itsCodeBuffer, 0, tmp, 0, oldTop);
+ itsCodeBuffer = tmp;
+ }
+ itsCodeBufferTop = newTop;
+ return oldTop;
+ }
+
+ public void addExceptionHandler(int startLabel, int endLabel,
+ int handlerLabel, String catchClassName)
+ {
+ if ((startLabel & 0x80000000) != 0x80000000)
+ throw new IllegalArgumentException("Bad startLabel");
+ if ((endLabel & 0x80000000) != 0x80000000)
+ throw new IllegalArgumentException("Bad endLabel");
+ if ((handlerLabel & 0x80000000) != 0x80000000)
+ throw new IllegalArgumentException("Bad handlerLabel");
+
+ /*
+ * If catchClassName is null, use 0 for the catch_type_index; which
+ * means catch everything. (Even when the verifier has let you throw
+ * something other than a Throwable.)
+ */
+ short catch_type_index = (catchClassName == null)
+ ? 0
+ : itsConstantPool.addClass(catchClassName);
+ ExceptionTableEntry newEntry = new ExceptionTableEntry(
+ startLabel,
+ endLabel,
+ handlerLabel,
+ catch_type_index);
+ int N = itsExceptionTableTop;
+ if (N == 0) {
+ itsExceptionTable = new ExceptionTableEntry[ExceptionTableSize];
+ } else if (N == itsExceptionTable.length) {
+ ExceptionTableEntry[] tmp = new ExceptionTableEntry[N * 2];
+ System.arraycopy(itsExceptionTable, 0, tmp, 0, N);
+ itsExceptionTable = tmp;
+ }
+ itsExceptionTable[N] = newEntry;
+ itsExceptionTableTop = N + 1;
+
+ }
+
+ public void addLineNumberEntry(short lineNumber) {
+ if (itsCurrentMethod == null)
+ throw new IllegalArgumentException("No method to stop");
+ int N = itsLineNumberTableTop;
+ if (N == 0) {
+ itsLineNumberTable = new int[LineNumberTableSize];
+ } else if (N == itsLineNumberTable.length) {
+ int[] tmp = new int[N * 2];
+ System.arraycopy(itsLineNumberTable, 0, tmp, 0, N);
+ itsLineNumberTable = tmp;
+ }
+ itsLineNumberTable[N] = (itsCodeBufferTop << 16) + lineNumber;
+ itsLineNumberTableTop = N + 1;
+ }
+
+ /**
+ * Write the class file to the OutputStream.
+ *
+ * @param oStream the stream to write to
+ * @throws IOException if writing to the stream produces an exception
+ */
+ public void write(OutputStream oStream)
+ throws IOException
+ {
+ byte[] array = toByteArray();
+ oStream.write(array);
+ }
+
+ private int getWriteSize()
+ {
+ int size = 0;
+
+ if (itsSourceFileNameIndex != 0) {
+ itsConstantPool.addUtf8("SourceFile");
+ }
+
+ size += 8; //writeLong(FileHeaderConstant);
+ size += itsConstantPool.getWriteSize();
+ size += 2; //writeShort(itsFlags);
+ size += 2; //writeShort(itsThisClassIndex);
+ size += 2; //writeShort(itsSuperClassIndex);
+ size += 2; //writeShort(itsInterfaces.size());
+ size += 2 * itsInterfaces.size();
+
+ size += 2; //writeShort(itsFields.size());
+ for (int i = 0; i < itsFields.size(); i++) {
+ size += ((ClassFileField)(itsFields.get(i))).getWriteSize();
+ }
+
+ size += 2; //writeShort(itsMethods.size());
+ for (int i = 0; i < itsMethods.size(); i++) {
+ size += ((ClassFileMethod)(itsMethods.get(i))).getWriteSize();
+ }
+
+ if (itsSourceFileNameIndex != 0) {
+ size += 2; //writeShort(1); attributes count
+ size += 2; //writeShort(sourceFileAttributeNameIndex);
+ size += 4; //writeInt(2);
+ size += 2; //writeShort(itsSourceFileNameIndex);
+ }else {
+ size += 2; //out.writeShort(0); no attributes
+ }
+
+ return size;
+ }
+
+ /**
+ * Get the class file as array of bytesto the OutputStream.
+ */
+ public byte[] toByteArray()
+ {
+ int dataSize = getWriteSize();
+ byte[] data = new byte[dataSize];
+ int offset = 0;
+
+ short sourceFileAttributeNameIndex = 0;
+ if (itsSourceFileNameIndex != 0) {
+ sourceFileAttributeNameIndex = itsConstantPool.addUtf8(
+ "SourceFile");
+ }
+
+ offset = putInt64(FileHeaderConstant, data, offset);
+ offset = itsConstantPool.write(data, offset);
+ offset = putInt16(itsFlags, data, offset);
+ offset = putInt16(itsThisClassIndex, data, offset);
+ offset = putInt16(itsSuperClassIndex, data, offset);
+ offset = putInt16(itsInterfaces.size(), data, offset);
+ for (int i = 0; i < itsInterfaces.size(); i++) {
+ int interfaceIndex = ((Short)(itsInterfaces.get(i))).shortValue();
+ offset = putInt16(interfaceIndex, data, offset);
+ }
+ offset = putInt16(itsFields.size(), data, offset);
+ for (int i = 0; i < itsFields.size(); i++) {
+ ClassFileField field = (ClassFileField)itsFields.get(i);
+ offset = field.write(data, offset);
+ }
+ offset = putInt16(itsMethods.size(), data, offset);
+ for (int i = 0; i < itsMethods.size(); i++) {
+ ClassFileMethod method = (ClassFileMethod)itsMethods.get(i);
+ offset = method.write(data, offset);
+ }
+ if (itsSourceFileNameIndex != 0) {
+ offset = putInt16(1, data, offset); // attributes count
+ offset = putInt16(sourceFileAttributeNameIndex, data, offset);
+ offset = putInt32(2, data, offset);
+ offset = putInt16(itsSourceFileNameIndex, data, offset);
+ } else {
+ offset = putInt16(0, data, offset); // no attributes
+ }
+
+ if (offset != dataSize) {
+ // Check getWriteSize is consistent with write!
+ throw new RuntimeException();
+ }
+
+ return data;
+ }
+
+ static int putInt64(long value, byte[] array, int offset)
+ {
+ offset = putInt32((int)(value >>> 32), array, offset);
+ return putInt32((int)value, array, offset);
+ }
+
+ private static void badStack(int value)
+ {
+ String s;
+ if (value < 0) { s = "Stack underflow: "+value; }
+ else { s = "Too big stack: "+value; }
+ throw new IllegalStateException(s);
+ }
+
+ /*
+ Really weird. Returns an int with # parameters in hi 16 bits, and
+ stack difference removal of parameters from stack and pushing the
+ result (it does not take into account removal of this in case of
+ non-static methods).
+ If Java really supported references we wouldn't have to be this
+ perverted.
+ */
+ private static int sizeOfParameters(String pString)
+ {
+ int length = pString.length();
+ int rightParenthesis = pString.lastIndexOf(')');
+ if (3 <= length /* minimal signature takes at least 3 chars: ()V */
+ && pString.charAt(0) == '('
+ && 1 <= rightParenthesis && rightParenthesis + 1 < length)
+ {
+ boolean ok = true;
+ int index = 1;
+ int stackDiff = 0;
+ int count = 0;
+ stringLoop:
+ while (index != rightParenthesis) {
+ switch (pString.charAt(index)) {
+ default:
+ ok = false;
+ break stringLoop;
+ case 'J' :
+ case 'D' :
+ --stackDiff;
+ // fall thru
+ case 'B' :
+ case 'S' :
+ case 'C' :
+ case 'I' :
+ case 'Z' :
+ case 'F' :
+ --stackDiff;
+ ++count;
+ ++index;
+ continue;
+ case '[' :
+ ++index;
+ int c = pString.charAt(index);
+ while (c == '[') {
+ ++index;
+ c = pString.charAt(index);
+ }
+ switch (c) {
+ default:
+ ok = false;
+ break stringLoop;
+ case 'J' :
+ case 'D' :
+ case 'B' :
+ case 'S' :
+ case 'C' :
+ case 'I' :
+ case 'Z' :
+ case 'F' :
+ --stackDiff;
+ ++count;
+ ++index;
+ continue;
+ case 'L':
+ // fall thru
+ }
+ // fall thru
+ case 'L' : {
+ --stackDiff;
+ ++count;
+ ++index;
+ int semicolon = pString.indexOf(';', index);
+ if (!(index + 1 <= semicolon
+ && semicolon < rightParenthesis))
+ {
+ ok = false;
+ break stringLoop;
+ }
+ index = semicolon + 1;
+ continue;
+ }
+ }
+ }
+ if (ok) {
+ switch (pString.charAt(rightParenthesis + 1)) {
+ default:
+ ok = false;
+ break;
+ case 'J' :
+ case 'D' :
+ ++stackDiff;
+ // fall thru
+ case 'B' :
+ case 'S' :
+ case 'C' :
+ case 'I' :
+ case 'Z' :
+ case 'F' :
+ case 'L' :
+ case '[' :
+ ++stackDiff;
+ // fall thru
+ case 'V' :
+ break;
+ }
+ if (ok) {
+ return ((count << 16) | (0xFFFF & stackDiff));
+ }
+ }
+ }
+ throw new IllegalArgumentException(
+ "Bad parameter signature: "+pString);
+ }
+
+ static int putInt16(int value, byte[] array, int offset)
+ {
+ array[offset + 0] = (byte)(value >>> 8);
+ array[offset + 1] = (byte)value;
+ return offset + 2;
+ }
+
+ static int putInt32(int value, byte[] array, int offset)
+ {
+ array[offset + 0] = (byte)(value >>> 24);
+ array[offset + 1] = (byte)(value >>> 16);
+ array[offset + 2] = (byte)(value >>> 8);
+ array[offset + 3] = (byte)value;
+ return offset + 4;
+ }
+
+ /**
+ * Number of operands accompanying the opcode.
+ */
+ static int opcodeCount(int opcode)
+ {
+ switch (opcode) {
+ case ByteCode.AALOAD:
+ case ByteCode.AASTORE:
+ case ByteCode.ACONST_NULL:
+ case ByteCode.ALOAD_0:
+ case ByteCode.ALOAD_1:
+ case ByteCode.ALOAD_2:
+ case ByteCode.ALOAD_3:
+ case ByteCode.ARETURN:
+ case ByteCode.ARRAYLENGTH:
+ case ByteCode.ASTORE_0:
+ case ByteCode.ASTORE_1:
+ case ByteCode.ASTORE_2:
+ case ByteCode.ASTORE_3:
+ case ByteCode.ATHROW:
+ case ByteCode.BALOAD:
+ case ByteCode.BASTORE:
+ case ByteCode.BREAKPOINT:
+ case ByteCode.CALOAD:
+ case ByteCode.CASTORE:
+ case ByteCode.D2F:
+ case ByteCode.D2I:
+ case ByteCode.D2L:
+ case ByteCode.DADD:
+ case ByteCode.DALOAD:
+ case ByteCode.DASTORE:
+ case ByteCode.DCMPG:
+ case ByteCode.DCMPL:
+ case ByteCode.DCONST_0:
+ case ByteCode.DCONST_1:
+ case ByteCode.DDIV:
+ case ByteCode.DLOAD_0:
+ case ByteCode.DLOAD_1:
+ case ByteCode.DLOAD_2:
+ case ByteCode.DLOAD_3:
+ case ByteCode.DMUL:
+ case ByteCode.DNEG:
+ case ByteCode.DREM:
+ case ByteCode.DRETURN:
+ case ByteCode.DSTORE_0:
+ case ByteCode.DSTORE_1:
+ case ByteCode.DSTORE_2:
+ case ByteCode.DSTORE_3:
+ case ByteCode.DSUB:
+ case ByteCode.DUP:
+ case ByteCode.DUP2:
+ case ByteCode.DUP2_X1:
+ case ByteCode.DUP2_X2:
+ case ByteCode.DUP_X1:
+ case ByteCode.DUP_X2:
+ case ByteCode.F2D:
+ case ByteCode.F2I:
+ case ByteCode.F2L:
+ case ByteCode.FADD:
+ case ByteCode.FALOAD:
+ case ByteCode.FASTORE:
+ case ByteCode.FCMPG:
+ case ByteCode.FCMPL:
+ case ByteCode.FCONST_0:
+ case ByteCode.FCONST_1:
+ case ByteCode.FCONST_2:
+ case ByteCode.FDIV:
+ case ByteCode.FLOAD_0:
+ case ByteCode.FLOAD_1:
+ case ByteCode.FLOAD_2:
+ case ByteCode.FLOAD_3:
+ case ByteCode.FMUL:
+ case ByteCode.FNEG:
+ case ByteCode.FREM:
+ case ByteCode.FRETURN:
+ case ByteCode.FSTORE_0:
+ case ByteCode.FSTORE_1:
+ case ByteCode.FSTORE_2:
+ case ByteCode.FSTORE_3:
+ case ByteCode.FSUB:
+ case ByteCode.I2B:
+ case ByteCode.I2C:
+ case ByteCode.I2D:
+ case ByteCode.I2F:
+ case ByteCode.I2L:
+ case ByteCode.I2S:
+ case ByteCode.IADD:
+ case ByteCode.IALOAD:
+ case ByteCode.IAND:
+ case ByteCode.IASTORE:
+ case ByteCode.ICONST_0:
+ case ByteCode.ICONST_1:
+ case ByteCode.ICONST_2:
+ case ByteCode.ICONST_3:
+ case ByteCode.ICONST_4:
+ case ByteCode.ICONST_5:
+ case ByteCode.ICONST_M1:
+ case ByteCode.IDIV:
+ case ByteCode.ILOAD_0:
+ case ByteCode.ILOAD_1:
+ case ByteCode.ILOAD_2:
+ case ByteCode.ILOAD_3:
+ case ByteCode.IMPDEP1:
+ case ByteCode.IMPDEP2:
+ case ByteCode.IMUL:
+ case ByteCode.INEG:
+ case ByteCode.IOR:
+ case ByteCode.IREM:
+ case ByteCode.IRETURN:
+ case ByteCode.ISHL:
+ case ByteCode.ISHR:
+ case ByteCode.ISTORE_0:
+ case ByteCode.ISTORE_1:
+ case ByteCode.ISTORE_2:
+ case ByteCode.ISTORE_3:
+ case ByteCode.ISUB:
+ case ByteCode.IUSHR:
+ case ByteCode.IXOR:
+ case ByteCode.L2D:
+ case ByteCode.L2F:
+ case ByteCode.L2I:
+ case ByteCode.LADD:
+ case ByteCode.LALOAD:
+ case ByteCode.LAND:
+ case ByteCode.LASTORE:
+ case ByteCode.LCMP:
+ case ByteCode.LCONST_0:
+ case ByteCode.LCONST_1:
+ case ByteCode.LDIV:
+ case ByteCode.LLOAD_0:
+ case ByteCode.LLOAD_1:
+ case ByteCode.LLOAD_2:
+ case ByteCode.LLOAD_3:
+ case ByteCode.LMUL:
+ case ByteCode.LNEG:
+ case ByteCode.LOR:
+ case ByteCode.LREM:
+ case ByteCode.LRETURN:
+ case ByteCode.LSHL:
+ case ByteCode.LSHR:
+ case ByteCode.LSTORE_0:
+ case ByteCode.LSTORE_1:
+ case ByteCode.LSTORE_2:
+ case ByteCode.LSTORE_3:
+ case ByteCode.LSUB:
+ case ByteCode.LUSHR:
+ case ByteCode.LXOR:
+ case ByteCode.MONITORENTER:
+ case ByteCode.MONITOREXIT:
+ case ByteCode.NOP:
+ case ByteCode.POP:
+ case ByteCode.POP2:
+ case ByteCode.RETURN:
+ case ByteCode.SALOAD:
+ case ByteCode.SASTORE:
+ case ByteCode.SWAP:
+ case ByteCode.WIDE:
+ return 0;
+ case ByteCode.ALOAD:
+ case ByteCode.ANEWARRAY:
+ case ByteCode.ASTORE:
+ case ByteCode.BIPUSH:
+ case ByteCode.CHECKCAST:
+ case ByteCode.DLOAD:
+ case ByteCode.DSTORE:
+ case ByteCode.FLOAD:
+ case ByteCode.FSTORE:
+ case ByteCode.GETFIELD:
+ case ByteCode.GETSTATIC:
+ case ByteCode.GOTO:
+ case ByteCode.GOTO_W:
+ case ByteCode.IFEQ:
+ case ByteCode.IFGE:
+ case ByteCode.IFGT:
+ case ByteCode.IFLE:
+ case ByteCode.IFLT:
+ case ByteCode.IFNE:
+ case ByteCode.IFNONNULL:
+ case ByteCode.IFNULL:
+ case ByteCode.IF_ACMPEQ:
+ case ByteCode.IF_ACMPNE:
+ case ByteCode.IF_ICMPEQ:
+ case ByteCode.IF_ICMPGE:
+ case ByteCode.IF_ICMPGT:
+ case ByteCode.IF_ICMPLE:
+ case ByteCode.IF_ICMPLT:
+ case ByteCode.IF_ICMPNE:
+ case ByteCode.ILOAD:
+ case ByteCode.INSTANCEOF:
+ case ByteCode.INVOKEINTERFACE:
+ case ByteCode.INVOKESPECIAL:
+ case ByteCode.INVOKESTATIC:
+ case ByteCode.INVOKEVIRTUAL:
+ case ByteCode.ISTORE:
+ case ByteCode.JSR:
+ case ByteCode.JSR_W:
+ case ByteCode.LDC:
+ case ByteCode.LDC2_W:
+ case ByteCode.LDC_W:
+ case ByteCode.LLOAD:
+ case ByteCode.LSTORE:
+ case ByteCode.NEW:
+ case ByteCode.NEWARRAY:
+ case ByteCode.PUTFIELD:
+ case ByteCode.PUTSTATIC:
+ case ByteCode.RET:
+ case ByteCode.SIPUSH:
+ return 1;
+
+ case ByteCode.IINC:
+ case ByteCode.MULTIANEWARRAY:
+ return 2;
+
+ case ByteCode.LOOKUPSWITCH:
+ case ByteCode.TABLESWITCH:
+ return -1;
+ }
+ throw new IllegalArgumentException("Bad opcode: "+opcode);
+ }
+
+ /**
+ * The effect on the operand stack of a given opcode.
+ */
+ static int stackChange(int opcode)
+ {
+ // For INVOKE... accounts only for popping this (unless static),
+ // ignoring parameters and return type
+ switch (opcode) {
+ case ByteCode.DASTORE:
+ case ByteCode.LASTORE:
+ return -4;
+
+ case ByteCode.AASTORE:
+ case ByteCode.BASTORE:
+ case ByteCode.CASTORE:
+ case ByteCode.DCMPG:
+ case ByteCode.DCMPL:
+ case ByteCode.FASTORE:
+ case ByteCode.IASTORE:
+ case ByteCode.LCMP:
+ case ByteCode.SASTORE:
+ return -3;
+
+ case ByteCode.DADD:
+ case ByteCode.DDIV:
+ case ByteCode.DMUL:
+ case ByteCode.DREM:
+ case ByteCode.DRETURN:
+ case ByteCode.DSTORE:
+ case ByteCode.DSTORE_0:
+ case ByteCode.DSTORE_1:
+ case ByteCode.DSTORE_2:
+ case ByteCode.DSTORE_3:
+ case ByteCode.DSUB:
+ case ByteCode.IF_ACMPEQ:
+ case ByteCode.IF_ACMPNE:
+ case ByteCode.IF_ICMPEQ:
+ case ByteCode.IF_ICMPGE:
+ case ByteCode.IF_ICMPGT:
+ case ByteCode.IF_ICMPLE:
+ case ByteCode.IF_ICMPLT:
+ case ByteCode.IF_ICMPNE:
+ case ByteCode.LADD:
+ case ByteCode.LAND:
+ case ByteCode.LDIV:
+ case ByteCode.LMUL:
+ case ByteCode.LOR:
+ case ByteCode.LREM:
+ case ByteCode.LRETURN:
+ case ByteCode.LSTORE:
+ case ByteCode.LSTORE_0:
+ case ByteCode.LSTORE_1:
+ case ByteCode.LSTORE_2:
+ case ByteCode.LSTORE_3:
+ case ByteCode.LSUB:
+ case ByteCode.LXOR:
+ case ByteCode.POP2:
+ return -2;
+
+ case ByteCode.AALOAD:
+ case ByteCode.ARETURN:
+ case ByteCode.ASTORE:
+ case ByteCode.ASTORE_0:
+ case ByteCode.ASTORE_1:
+ case ByteCode.ASTORE_2:
+ case ByteCode.ASTORE_3:
+ case ByteCode.ATHROW:
+ case ByteCode.BALOAD:
+ case ByteCode.CALOAD:
+ case ByteCode.D2F:
+ case ByteCode.D2I:
+ case ByteCode.FADD:
+ case ByteCode.FALOAD:
+ case ByteCode.FCMPG:
+ case ByteCode.FCMPL:
+ case ByteCode.FDIV:
+ case ByteCode.FMUL:
+ case ByteCode.FREM:
+ case ByteCode.FRETURN:
+ case ByteCode.FSTORE:
+ case ByteCode.FSTORE_0:
+ case ByteCode.FSTORE_1:
+ case ByteCode.FSTORE_2:
+ case ByteCode.FSTORE_3:
+ case ByteCode.FSUB:
+ case ByteCode.GETFIELD:
+ case ByteCode.IADD:
+ case ByteCode.IALOAD:
+ case ByteCode.IAND:
+ case ByteCode.IDIV:
+ case ByteCode.IFEQ:
+ case ByteCode.IFGE:
+ case ByteCode.IFGT:
+ case ByteCode.IFLE:
+ case ByteCode.IFLT:
+ case ByteCode.IFNE:
+ case ByteCode.IFNONNULL:
+ case ByteCode.IFNULL:
+ case ByteCode.IMUL:
+ case ByteCode.INVOKEINTERFACE: //
+ case ByteCode.INVOKESPECIAL: // but needs to account for
+ case ByteCode.INVOKEVIRTUAL: // pops 'this' (unless static)
+ case ByteCode.IOR:
+ case ByteCode.IREM:
+ case ByteCode.IRETURN:
+ case ByteCode.ISHL:
+ case ByteCode.ISHR:
+ case ByteCode.ISTORE:
+ case ByteCode.ISTORE_0:
+ case ByteCode.ISTORE_1:
+ case ByteCode.ISTORE_2:
+ case ByteCode.ISTORE_3:
+ case ByteCode.ISUB:
+ case ByteCode.IUSHR:
+ case ByteCode.IXOR:
+ case ByteCode.L2F:
+ case ByteCode.L2I:
+ case ByteCode.LOOKUPSWITCH:
+ case ByteCode.LSHL:
+ case ByteCode.LSHR:
+ case ByteCode.LUSHR:
+ case ByteCode.MONITORENTER:
+ case ByteCode.MONITOREXIT:
+ case ByteCode.POP:
+ case ByteCode.PUTFIELD:
+ case ByteCode.SALOAD:
+ case ByteCode.TABLESWITCH:
+ return -1;
+
+ case ByteCode.ANEWARRAY:
+ case ByteCode.ARRAYLENGTH:
+ case ByteCode.BREAKPOINT:
+ case ByteCode.CHECKCAST:
+ case ByteCode.D2L:
+ case ByteCode.DALOAD:
+ case ByteCode.DNEG:
+ case ByteCode.F2I:
+ case ByteCode.FNEG:
+ case ByteCode.GETSTATIC:
+ case ByteCode.GOTO:
+ case ByteCode.GOTO_W:
+ case ByteCode.I2B:
+ case ByteCode.I2C:
+ case ByteCode.I2F:
+ case ByteCode.I2S:
+ case ByteCode.IINC:
+ case ByteCode.IMPDEP1:
+ case ByteCode.IMPDEP2:
+ case ByteCode.INEG:
+ case ByteCode.INSTANCEOF:
+ case ByteCode.INVOKESTATIC:
+ case ByteCode.L2D:
+ case ByteCode.LALOAD:
+ case ByteCode.LNEG:
+ case ByteCode.NEWARRAY:
+ case ByteCode.NOP:
+ case ByteCode.PUTSTATIC:
+ case ByteCode.RET:
+ case ByteCode.RETURN:
+ case ByteCode.SWAP:
+ case ByteCode.WIDE:
+ return 0;
+
+ case ByteCode.ACONST_NULL:
+ case ByteCode.ALOAD:
+ case ByteCode.ALOAD_0:
+ case ByteCode.ALOAD_1:
+ case ByteCode.ALOAD_2:
+ case ByteCode.ALOAD_3:
+ case ByteCode.BIPUSH:
+ case ByteCode.DUP:
+ case ByteCode.DUP_X1:
+ case ByteCode.DUP_X2:
+ case ByteCode.F2D:
+ case ByteCode.F2L:
+ case ByteCode.FCONST_0:
+ case ByteCode.FCONST_1:
+ case ByteCode.FCONST_2:
+ case ByteCode.FLOAD:
+ case ByteCode.FLOAD_0:
+ case ByteCode.FLOAD_1:
+ case ByteCode.FLOAD_2:
+ case ByteCode.FLOAD_3:
+ case ByteCode.I2D:
+ case ByteCode.I2L:
+ case ByteCode.ICONST_0:
+ case ByteCode.ICONST_1:
+ case ByteCode.ICONST_2:
+ case ByteCode.ICONST_3:
+ case ByteCode.ICONST_4:
+ case ByteCode.ICONST_5:
+ case ByteCode.ICONST_M1:
+ case ByteCode.ILOAD:
+ case ByteCode.ILOAD_0:
+ case ByteCode.ILOAD_1:
+ case ByteCode.ILOAD_2:
+ case ByteCode.ILOAD_3:
+ case ByteCode.JSR:
+ case ByteCode.JSR_W:
+ case ByteCode.LDC:
+ case ByteCode.LDC_W:
+ case ByteCode.MULTIANEWARRAY:
+ case ByteCode.NEW:
+ case ByteCode.SIPUSH:
+ return 1;
+
+ case ByteCode.DCONST_0:
+ case ByteCode.DCONST_1:
+ case ByteCode.DLOAD:
+ case ByteCode.DLOAD_0:
+ case ByteCode.DLOAD_1:
+ case ByteCode.DLOAD_2:
+ case ByteCode.DLOAD_3:
+ case ByteCode.DUP2:
+ case ByteCode.DUP2_X1:
+ case ByteCode.DUP2_X2:
+ case ByteCode.LCONST_0:
+ case ByteCode.LCONST_1:
+ case ByteCode.LDC2_W:
+ case ByteCode.LLOAD:
+ case ByteCode.LLOAD_0:
+ case ByteCode.LLOAD_1:
+ case ByteCode.LLOAD_2:
+ case ByteCode.LLOAD_3:
+ return 2;
+ }
+ throw new IllegalArgumentException("Bad opcode: "+opcode);
+ }
+
+ /*
+ * Number of bytes of operands generated after the opcode.
+ * Not in use currently.
+ */
+/*
+ int extra(int opcode)
+ {
+ switch (opcode) {
+ case ByteCode.AALOAD:
+ case ByteCode.AASTORE:
+ case ByteCode.ACONST_NULL:
+ case ByteCode.ALOAD_0:
+ case ByteCode.ALOAD_1:
+ case ByteCode.ALOAD_2:
+ case ByteCode.ALOAD_3:
+ case ByteCode.ARETURN:
+ case ByteCode.ARRAYLENGTH:
+ case ByteCode.ASTORE_0:
+ case ByteCode.ASTORE_1:
+ case ByteCode.ASTORE_2:
+ case ByteCode.ASTORE_3:
+ case ByteCode.ATHROW:
+ case ByteCode.BALOAD:
+ case ByteCode.BASTORE:
+ case ByteCode.BREAKPOINT:
+ case ByteCode.CALOAD:
+ case ByteCode.CASTORE:
+ case ByteCode.D2F:
+ case ByteCode.D2I:
+ case ByteCode.D2L:
+ case ByteCode.DADD:
+ case ByteCode.DALOAD:
+ case ByteCode.DASTORE:
+ case ByteCode.DCMPG:
+ case ByteCode.DCMPL:
+ case ByteCode.DCONST_0:
+ case ByteCode.DCONST_1:
+ case ByteCode.DDIV:
+ case ByteCode.DLOAD_0:
+ case ByteCode.DLOAD_1:
+ case ByteCode.DLOAD_2:
+ case ByteCode.DLOAD_3:
+ case ByteCode.DMUL:
+ case ByteCode.DNEG:
+ case ByteCode.DREM:
+ case ByteCode.DRETURN:
+ case ByteCode.DSTORE_0:
+ case ByteCode.DSTORE_1:
+ case ByteCode.DSTORE_2:
+ case ByteCode.DSTORE_3:
+ case ByteCode.DSUB:
+ case ByteCode.DUP2:
+ case ByteCode.DUP2_X1:
+ case ByteCode.DUP2_X2:
+ case ByteCode.DUP:
+ case ByteCode.DUP_X1:
+ case ByteCode.DUP_X2:
+ case ByteCode.F2D:
+ case ByteCode.F2I:
+ case ByteCode.F2L:
+ case ByteCode.FADD:
+ case ByteCode.FALOAD:
+ case ByteCode.FASTORE:
+ case ByteCode.FCMPG:
+ case ByteCode.FCMPL:
+ case ByteCode.FCONST_0:
+ case ByteCode.FCONST_1:
+ case ByteCode.FCONST_2:
+ case ByteCode.FDIV:
+ case ByteCode.FLOAD_0:
+ case ByteCode.FLOAD_1:
+ case ByteCode.FLOAD_2:
+ case ByteCode.FLOAD_3:
+ case ByteCode.FMUL:
+ case ByteCode.FNEG:
+ case ByteCode.FREM:
+ case ByteCode.FRETURN:
+ case ByteCode.FSTORE_0:
+ case ByteCode.FSTORE_1:
+ case ByteCode.FSTORE_2:
+ case ByteCode.FSTORE_3:
+ case ByteCode.FSUB:
+ case ByteCode.I2B:
+ case ByteCode.I2C:
+ case ByteCode.I2D:
+ case ByteCode.I2F:
+ case ByteCode.I2L:
+ case ByteCode.I2S:
+ case ByteCode.IADD:
+ case ByteCode.IALOAD:
+ case ByteCode.IAND:
+ case ByteCode.IASTORE:
+ case ByteCode.ICONST_0:
+ case ByteCode.ICONST_1:
+ case ByteCode.ICONST_2:
+ case ByteCode.ICONST_3:
+ case ByteCode.ICONST_4:
+ case ByteCode.ICONST_5:
+ case ByteCode.ICONST_M1:
+ case ByteCode.IDIV:
+ case ByteCode.ILOAD_0:
+ case ByteCode.ILOAD_1:
+ case ByteCode.ILOAD_2:
+ case ByteCode.ILOAD_3:
+ case ByteCode.IMPDEP1:
+ case ByteCode.IMPDEP2:
+ case ByteCode.IMUL:
+ case ByteCode.INEG:
+ case ByteCode.IOR:
+ case ByteCode.IREM:
+ case ByteCode.IRETURN:
+ case ByteCode.ISHL:
+ case ByteCode.ISHR:
+ case ByteCode.ISTORE_0:
+ case ByteCode.ISTORE_1:
+ case ByteCode.ISTORE_2:
+ case ByteCode.ISTORE_3:
+ case ByteCode.ISUB:
+ case ByteCode.IUSHR:
+ case ByteCode.IXOR:
+ case ByteCode.L2D:
+ case ByteCode.L2F:
+ case ByteCode.L2I:
+ case ByteCode.LADD:
+ case ByteCode.LALOAD:
+ case ByteCode.LAND:
+ case ByteCode.LASTORE:
+ case ByteCode.LCMP:
+ case ByteCode.LCONST_0:
+ case ByteCode.LCONST_1:
+ case ByteCode.LDIV:
+ case ByteCode.LLOAD_0:
+ case ByteCode.LLOAD_1:
+ case ByteCode.LLOAD_2:
+ case ByteCode.LLOAD_3:
+ case ByteCode.LMUL:
+ case ByteCode.LNEG:
+ case ByteCode.LOR:
+ case ByteCode.LREM:
+ case ByteCode.LRETURN:
+ case ByteCode.LSHL:
+ case ByteCode.LSHR:
+ case ByteCode.LSTORE_0:
+ case ByteCode.LSTORE_1:
+ case ByteCode.LSTORE_2:
+ case ByteCode.LSTORE_3:
+ case ByteCode.LSUB:
+ case ByteCode.LUSHR:
+ case ByteCode.LXOR:
+ case ByteCode.MONITORENTER:
+ case ByteCode.MONITOREXIT:
+ case ByteCode.NOP:
+ case ByteCode.POP2:
+ case ByteCode.POP:
+ case ByteCode.RETURN:
+ case ByteCode.SALOAD:
+ case ByteCode.SASTORE:
+ case ByteCode.SWAP:
+ case ByteCode.WIDE:
+ return 0;
+
+ case ByteCode.ALOAD:
+ case ByteCode.ASTORE:
+ case ByteCode.BIPUSH:
+ case ByteCode.DLOAD:
+ case ByteCode.DSTORE:
+ case ByteCode.FLOAD:
+ case ByteCode.FSTORE:
+ case ByteCode.ILOAD:
+ case ByteCode.ISTORE:
+ case ByteCode.LDC:
+ case ByteCode.LLOAD:
+ case ByteCode.LSTORE:
+ case ByteCode.NEWARRAY:
+ case ByteCode.RET:
+ return 1;
+
+ case ByteCode.ANEWARRAY:
+ case ByteCode.CHECKCAST:
+ case ByteCode.GETFIELD:
+ case ByteCode.GETSTATIC:
+ case ByteCode.GOTO:
+ case ByteCode.IFEQ:
+ case ByteCode.IFGE:
+ case ByteCode.IFGT:
+ case ByteCode.IFLE:
+ case ByteCode.IFLT:
+ case ByteCode.IFNE:
+ case ByteCode.IFNONNULL:
+ case ByteCode.IFNULL:
+ case ByteCode.IF_ACMPEQ:
+ case ByteCode.IF_ACMPNE:
+ case ByteCode.IF_ICMPEQ:
+ case ByteCode.IF_ICMPGE:
+ case ByteCode.IF_ICMPGT:
+ case ByteCode.IF_ICMPLE:
+ case ByteCode.IF_ICMPLT:
+ case ByteCode.IF_ICMPNE:
+ case ByteCode.IINC:
+ case ByteCode.INSTANCEOF:
+ case ByteCode.INVOKEINTERFACE:
+ case ByteCode.INVOKESPECIAL:
+ case ByteCode.INVOKESTATIC:
+ case ByteCode.INVOKEVIRTUAL:
+ case ByteCode.JSR:
+ case ByteCode.LDC2_W:
+ case ByteCode.LDC_W:
+ case ByteCode.NEW:
+ case ByteCode.PUTFIELD:
+ case ByteCode.PUTSTATIC:
+ case ByteCode.SIPUSH:
+ return 2;
+
+ case ByteCode.MULTIANEWARRAY:
+ return 3;
+
+ case ByteCode.GOTO_W:
+ case ByteCode.JSR_W:
+ return 4;
+
+ case ByteCode.LOOKUPSWITCH: // depends on alignment
+ case ByteCode.TABLESWITCH: // depends on alignment
+ return -1;
+ }
+ throw new IllegalArgumentException("Bad opcode: "+opcode);
+ }
+*/
+ private static String bytecodeStr(int code)
+ {
+ if (DEBUGSTACK || DEBUGCODE) {
+ switch (code) {
+ case ByteCode.NOP: return "nop";
+ case ByteCode.ACONST_NULL: return "aconst_null";
+ case ByteCode.ICONST_M1: return "iconst_m1";
+ case ByteCode.ICONST_0: return "iconst_0";
+ case ByteCode.ICONST_1: return "iconst_1";
+ case ByteCode.ICONST_2: return "iconst_2";
+ case ByteCode.ICONST_3: return "iconst_3";
+ case ByteCode.ICONST_4: return "iconst_4";
+ case ByteCode.ICONST_5: return "iconst_5";
+ case ByteCode.LCONST_0: return "lconst_0";
+ case ByteCode.LCONST_1: return "lconst_1";
+ case ByteCode.FCONST_0: return "fconst_0";
+ case ByteCode.FCONST_1: return "fconst_1";
+ case ByteCode.FCONST_2: return "fconst_2";
+ case ByteCode.DCONST_0: return "dconst_0";
+ case ByteCode.DCONST_1: return "dconst_1";
+ case ByteCode.BIPUSH: return "bipush";
+ case ByteCode.SIPUSH: return "sipush";
+ case ByteCode.LDC: return "ldc";
+ case ByteCode.LDC_W: return "ldc_w";
+ case ByteCode.LDC2_W: return "ldc2_w";
+ case ByteCode.ILOAD: return "iload";
+ case ByteCode.LLOAD: return "lload";
+ case ByteCode.FLOAD: return "fload";
+ case ByteCode.DLOAD: return "dload";
+ case ByteCode.ALOAD: return "aload";
+ case ByteCode.ILOAD_0: return "iload_0";
+ case ByteCode.ILOAD_1: return "iload_1";
+ case ByteCode.ILOAD_2: return "iload_2";
+ case ByteCode.ILOAD_3: return "iload_3";
+ case ByteCode.LLOAD_0: return "lload_0";
+ case ByteCode.LLOAD_1: return "lload_1";
+ case ByteCode.LLOAD_2: return "lload_2";
+ case ByteCode.LLOAD_3: return "lload_3";
+ case ByteCode.FLOAD_0: return "fload_0";
+ case ByteCode.FLOAD_1: return "fload_1";
+ case ByteCode.FLOAD_2: return "fload_2";
+ case ByteCode.FLOAD_3: return "fload_3";
+ case ByteCode.DLOAD_0: return "dload_0";
+ case ByteCode.DLOAD_1: return "dload_1";
+ case ByteCode.DLOAD_2: return "dload_2";
+ case ByteCode.DLOAD_3: return "dload_3";
+ case ByteCode.ALOAD_0: return "aload_0";
+ case ByteCode.ALOAD_1: return "aload_1";
+ case ByteCode.ALOAD_2: return "aload_2";
+ case ByteCode.ALOAD_3: return "aload_3";
+ case ByteCode.IALOAD: return "iaload";
+ case ByteCode.LALOAD: return "laload";
+ case ByteCode.FALOAD: return "faload";
+ case ByteCode.DALOAD: return "daload";
+ case ByteCode.AALOAD: return "aaload";
+ case ByteCode.BALOAD: return "baload";
+ case ByteCode.CALOAD: return "caload";
+ case ByteCode.SALOAD: return "saload";
+ case ByteCode.ISTORE: return "istore";
+ case ByteCode.LSTORE: return "lstore";
+ case ByteCode.FSTORE: return "fstore";
+ case ByteCode.DSTORE: return "dstore";
+ case ByteCode.ASTORE: return "astore";
+ case ByteCode.ISTORE_0: return "istore_0";
+ case ByteCode.ISTORE_1: return "istore_1";
+ case ByteCode.ISTORE_2: return "istore_2";
+ case ByteCode.ISTORE_3: return "istore_3";
+ case ByteCode.LSTORE_0: return "lstore_0";
+ case ByteCode.LSTORE_1: return "lstore_1";
+ case ByteCode.LSTORE_2: return "lstore_2";
+ case ByteCode.LSTORE_3: return "lstore_3";
+ case ByteCode.FSTORE_0: return "fstore_0";
+ case ByteCode.FSTORE_1: return "fstore_1";
+ case ByteCode.FSTORE_2: return "fstore_2";
+ case ByteCode.FSTORE_3: return "fstore_3";
+ case ByteCode.DSTORE_0: return "dstore_0";
+ case ByteCode.DSTORE_1: return "dstore_1";
+ case ByteCode.DSTORE_2: return "dstore_2";
+ case ByteCode.DSTORE_3: return "dstore_3";
+ case ByteCode.ASTORE_0: return "astore_0";
+ case ByteCode.ASTORE_1: return "astore_1";
+ case ByteCode.ASTORE_2: return "astore_2";
+ case ByteCode.ASTORE_3: return "astore_3";
+ case ByteCode.IASTORE: return "iastore";
+ case ByteCode.LASTORE: return "lastore";
+ case ByteCode.FASTORE: return "fastore";
+ case ByteCode.DASTORE: return "dastore";
+ case ByteCode.AASTORE: return "aastore";
+ case ByteCode.BASTORE: return "bastore";
+ case ByteCode.CASTORE: return "castore";
+ case ByteCode.SASTORE: return "sastore";
+ case ByteCode.POP: return "pop";
+ case ByteCode.POP2: return "pop2";
+ case ByteCode.DUP: return "dup";
+ case ByteCode.DUP_X1: return "dup_x1";
+ case ByteCode.DUP_X2: return "dup_x2";
+ case ByteCode.DUP2: return "dup2";
+ case ByteCode.DUP2_X1: return "dup2_x1";
+ case ByteCode.DUP2_X2: return "dup2_x2";
+ case ByteCode.SWAP: return "swap";
+ case ByteCode.IADD: return "iadd";
+ case ByteCode.LADD: return "ladd";
+ case ByteCode.FADD: return "fadd";
+ case ByteCode.DADD: return "dadd";
+ case ByteCode.ISUB: return "isub";
+ case ByteCode.LSUB: return "lsub";
+ case ByteCode.FSUB: return "fsub";
+ case ByteCode.DSUB: return "dsub";
+ case ByteCode.IMUL: return "imul";
+ case ByteCode.LMUL: return "lmul";
+ case ByteCode.FMUL: return "fmul";
+ case ByteCode.DMUL: return "dmul";
+ case ByteCode.IDIV: return "idiv";
+ case ByteCode.LDIV: return "ldiv";
+ case ByteCode.FDIV: return "fdiv";
+ case ByteCode.DDIV: return "ddiv";
+ case ByteCode.IREM: return "irem";
+ case ByteCode.LREM: return "lrem";
+ case ByteCode.FREM: return "frem";
+ case ByteCode.DREM: return "drem";
+ case ByteCode.INEG: return "ineg";
+ case ByteCode.LNEG: return "lneg";
+ case ByteCode.FNEG: return "fneg";
+ case ByteCode.DNEG: return "dneg";
+ case ByteCode.ISHL: return "ishl";
+ case ByteCode.LSHL: return "lshl";
+ case ByteCode.ISHR: return "ishr";
+ case ByteCode.LSHR: return "lshr";
+ case ByteCode.IUSHR: return "iushr";
+ case ByteCode.LUSHR: return "lushr";
+ case ByteCode.IAND: return "iand";
+ case ByteCode.LAND: return "land";
+ case ByteCode.IOR: return "ior";
+ case ByteCode.LOR: return "lor";
+ case ByteCode.IXOR: return "ixor";
+ case ByteCode.LXOR: return "lxor";
+ case ByteCode.IINC: return "iinc";
+ case ByteCode.I2L: return "i2l";
+ case ByteCode.I2F: return "i2f";
+ case ByteCode.I2D: return "i2d";
+ case ByteCode.L2I: return "l2i";
+ case ByteCode.L2F: return "l2f";
+ case ByteCode.L2D: return "l2d";
+ case ByteCode.F2I: return "f2i";
+ case ByteCode.F2L: return "f2l";
+ case ByteCode.F2D: return "f2d";
+ case ByteCode.D2I: return "d2i";
+ case ByteCode.D2L: return "d2l";
+ case ByteCode.D2F: return "d2f";
+ case ByteCode.I2B: return "i2b";
+ case ByteCode.I2C: return "i2c";
+ case ByteCode.I2S: return "i2s";
+ case ByteCode.LCMP: return "lcmp";
+ case ByteCode.FCMPL: return "fcmpl";
+ case ByteCode.FCMPG: return "fcmpg";
+ case ByteCode.DCMPL: return "dcmpl";
+ case ByteCode.DCMPG: return "dcmpg";
+ case ByteCode.IFEQ: return "ifeq";
+ case ByteCode.IFNE: return "ifne";
+ case ByteCode.IFLT: return "iflt";
+ case ByteCode.IFGE: return "ifge";
+ case ByteCode.IFGT: return "ifgt";
+ case ByteCode.IFLE: return "ifle";
+ case ByteCode.IF_ICMPEQ: return "if_icmpeq";
+ case ByteCode.IF_ICMPNE: return "if_icmpne";
+ case ByteCode.IF_ICMPLT: return "if_icmplt";
+ case ByteCode.IF_ICMPGE: return "if_icmpge";
+ case ByteCode.IF_ICMPGT: return "if_icmpgt";
+ case ByteCode.IF_ICMPLE: return "if_icmple";
+ case ByteCode.IF_ACMPEQ: return "if_acmpeq";
+ case ByteCode.IF_ACMPNE: return "if_acmpne";
+ case ByteCode.GOTO: return "goto";
+ case ByteCode.JSR: return "jsr";
+ case ByteCode.RET: return "ret";
+ case ByteCode.TABLESWITCH: return "tableswitch";
+ case ByteCode.LOOKUPSWITCH: return "lookupswitch";
+ case ByteCode.IRETURN: return "ireturn";
+ case ByteCode.LRETURN: return "lreturn";
+ case ByteCode.FRETURN: return "freturn";
+ case ByteCode.DRETURN: return "dreturn";
+ case ByteCode.ARETURN: return "areturn";
+ case ByteCode.RETURN: return "return";
+ case ByteCode.GETSTATIC: return "getstatic";
+ case ByteCode.PUTSTATIC: return "putstatic";
+ case ByteCode.GETFIELD: return "getfield";
+ case ByteCode.PUTFIELD: return "putfield";
+ case ByteCode.INVOKEVIRTUAL: return "invokevirtual";
+ case ByteCode.INVOKESPECIAL: return "invokespecial";
+ case ByteCode.INVOKESTATIC: return "invokestatic";
+ case ByteCode.INVOKEINTERFACE: return "invokeinterface";
+ case ByteCode.NEW: return "new";
+ case ByteCode.NEWARRAY: return "newarray";
+ case ByteCode.ANEWARRAY: return "anewarray";
+ case ByteCode.ARRAYLENGTH: return "arraylength";
+ case ByteCode.ATHROW: return "athrow";
+ case ByteCode.CHECKCAST: return "checkcast";
+ case ByteCode.INSTANCEOF: return "instanceof";
+ case ByteCode.MONITORENTER: return "monitorenter";
+ case ByteCode.MONITOREXIT: return "monitorexit";
+ case ByteCode.WIDE: return "wide";
+ case ByteCode.MULTIANEWARRAY: return "multianewarray";
+ case ByteCode.IFNULL: return "ifnull";
+ case ByteCode.IFNONNULL: return "ifnonnull";
+ case ByteCode.GOTO_W: return "goto_w";
+ case ByteCode.JSR_W: return "jsr_w";
+ case ByteCode.BREAKPOINT: return "breakpoint";
+
+ case ByteCode.IMPDEP1: return "impdep1";
+ case ByteCode.IMPDEP2: return "impdep2";
+ }
+ }
+ return "";
+ }
+
+ final char[] getCharBuffer(int minimalSize)
+ {
+ if (minimalSize > tmpCharBuffer.length) {
+ int newSize = tmpCharBuffer.length * 2;
+ if (minimalSize > newSize) { newSize = minimalSize; }
+ tmpCharBuffer = new char[newSize];
+ }
+ return tmpCharBuffer;
+ }
+
+ private static final int LineNumberTableSize = 16;
+ private static final int ExceptionTableSize = 4;
+
+ private final static long FileHeaderConstant = 0xCAFEBABE0003002DL;
+ // Set DEBUG flags to true to get better checking and progress info.
+ private static final boolean DEBUGSTACK = false;
+ private static final boolean DEBUGLABELS = false;
+ private static final boolean DEBUGCODE = false;
+
+ private String generatedClassName;
+
+ private ExceptionTableEntry itsExceptionTable[];
+ private int itsExceptionTableTop;
+
+ private int itsLineNumberTable[]; // pack start_pc & line_number together
+ private int itsLineNumberTableTop;
+
+ private byte[] itsCodeBuffer = new byte[256];
+ private int itsCodeBufferTop;
+
+ private ConstantPool itsConstantPool;
+
+ private ClassFileMethod itsCurrentMethod;
+ private short itsStackTop;
+
+ private short itsMaxStack;
+ private short itsMaxLocals;
+
+ private ObjArray itsMethods = new ObjArray();
+ private ObjArray itsFields = new ObjArray();
+ private ObjArray itsInterfaces = new ObjArray();
+
+ private short itsFlags;
+ private short itsThisClassIndex;
+ private short itsSuperClassIndex;
+ private short itsSourceFileNameIndex;
+
+ private static final int MIN_LABEL_TABLE_SIZE = 32;
+ private int[] itsLabelTable;
+ private int itsLabelTableTop;
+
+// itsFixupTable[i] = (label_index << 32) | fixup_site
+ private static final int MIN_FIXUP_TABLE_SIZE = 40;
+ private long[] itsFixupTable;
+ private int itsFixupTableTop;
+ private ObjArray itsVarDescriptors;
+
+ private char[] tmpCharBuffer = new char[64];
+}
+
+final class ExceptionTableEntry
+{
+
+ ExceptionTableEntry(int startLabel, int endLabel,
+ int handlerLabel, short catchType)
+ {
+ itsStartLabel = startLabel;
+ itsEndLabel = endLabel;
+ itsHandlerLabel = handlerLabel;
+ itsCatchType = catchType;
+ }
+
+ int itsStartLabel;
+ int itsEndLabel;
+ int itsHandlerLabel;
+ short itsCatchType;
+}
+
+final class ClassFileField
+{
+
+ ClassFileField(short nameIndex, short typeIndex, short flags)
+ {
+ itsNameIndex = nameIndex;
+ itsTypeIndex = typeIndex;
+ itsFlags = flags;
+ itsHasAttributes = false;
+ }
+
+ void setAttributes(short attr1, short attr2, short attr3, int index)
+ {
+ itsHasAttributes = true;
+ itsAttr1 = attr1;
+ itsAttr2 = attr2;
+ itsAttr3 = attr3;
+ itsIndex = index;
+ }
+
+ int write(byte[] data, int offset)
+ {
+ offset = ClassFileWriter.putInt16(itsFlags, data, offset);
+ offset = ClassFileWriter.putInt16(itsNameIndex, data, offset);
+ offset = ClassFileWriter.putInt16(itsTypeIndex, data, offset);
+ if (!itsHasAttributes) {
+ // write 0 attributes
+ offset = ClassFileWriter.putInt16(0, data, offset);
+ } else {
+ offset = ClassFileWriter.putInt16(1, data, offset);
+ offset = ClassFileWriter.putInt16(itsAttr1, data, offset);
+ offset = ClassFileWriter.putInt16(itsAttr2, data, offset);
+ offset = ClassFileWriter.putInt16(itsAttr3, data, offset);
+ offset = ClassFileWriter.putInt16(itsIndex, data, offset);
+ }
+ return offset;
+ }
+
+ int getWriteSize()
+ {
+ int size = 2 * 3;
+ if (!itsHasAttributes) {
+ size += 2;
+ } else {
+ size += 2 + 2 * 4;
+ }
+ return size;
+ }
+
+ private short itsNameIndex;
+ private short itsTypeIndex;
+ private short itsFlags;
+ private boolean itsHasAttributes;
+ private short itsAttr1, itsAttr2, itsAttr3;
+ private int itsIndex;
+}
+
+final class ClassFileMethod
+{
+
+ ClassFileMethod(short nameIndex, short typeIndex, short flags)
+ {
+ itsNameIndex = nameIndex;
+ itsTypeIndex = typeIndex;
+ itsFlags = flags;
+ }
+
+ void setCodeAttribute(byte codeAttribute[])
+ {
+ itsCodeAttribute = codeAttribute;
+ }
+
+ int write(byte[] data, int offset)
+ {
+ offset = ClassFileWriter.putInt16(itsFlags, data, offset);
+ offset = ClassFileWriter.putInt16(itsNameIndex, data, offset);
+ offset = ClassFileWriter.putInt16(itsTypeIndex, data, offset);
+ // Code attribute only
+ offset = ClassFileWriter.putInt16(1, data, offset);
+ System.arraycopy(itsCodeAttribute, 0, data, offset,
+ itsCodeAttribute.length);
+ offset += itsCodeAttribute.length;
+ return offset;
+ }
+
+ int getWriteSize()
+ {
+ return 2 * 4 + itsCodeAttribute.length;
+ }
+
+ private short itsNameIndex;
+ private short itsTypeIndex;
+ private short itsFlags;
+ private byte[] itsCodeAttribute;
+
+}
+
+final class ConstantPool
+{
+
+ ConstantPool(ClassFileWriter cfw)
+ {
+ this.cfw = cfw;
+ itsTopIndex = 1; // the zero'th entry is reserved
+ itsPool = new byte[ConstantPoolSize];
+ itsTop = 0;
+ }
+
+ private static final int ConstantPoolSize = 256;
+ private static final byte
+ CONSTANT_Class = 7,
+ CONSTANT_Fieldref = 9,
+ CONSTANT_Methodref = 10,
+ CONSTANT_InterfaceMethodref = 11,
+ CONSTANT_String = 8,
+ CONSTANT_Integer = 3,
+ CONSTANT_Float = 4,
+ CONSTANT_Long = 5,
+ CONSTANT_Double = 6,
+ CONSTANT_NameAndType = 12,
+ CONSTANT_Utf8 = 1;
+
+ int write(byte[] data, int offset)
+ {
+ offset = ClassFileWriter.putInt16((short)itsTopIndex, data, offset);
+ System.arraycopy(itsPool, 0, data, offset, itsTop);
+ offset += itsTop;
+ return offset;
+ }
+
+ int getWriteSize()
+ {
+ return 2 + itsTop;
+ }
+
+ int addConstant(int k)
+ {
+ ensure(5);
+ itsPool[itsTop++] = CONSTANT_Integer;
+ itsTop = ClassFileWriter.putInt32(k, itsPool, itsTop);
+ return (short)(itsTopIndex++);
+ }
+
+ int addConstant(long k)
+ {
+ ensure(9);
+ itsPool[itsTop++] = CONSTANT_Long;
+ itsTop = ClassFileWriter.putInt64(k, itsPool, itsTop);
+ int index = itsTopIndex;
+ itsTopIndex += 2;
+ return index;
+ }
+
+ int addConstant(float k)
+ {
+ ensure(5);
+ itsPool[itsTop++] = CONSTANT_Float;
+ int bits = Float.floatToIntBits(k);
+ itsTop = ClassFileWriter.putInt32(bits, itsPool, itsTop);
+ return itsTopIndex++;
+ }
+
+ int addConstant(double k)
+ {
+ ensure(9);
+ itsPool[itsTop++] = CONSTANT_Double;
+ long bits = Double.doubleToLongBits(k);
+ itsTop = ClassFileWriter.putInt64(bits, itsPool, itsTop);
+ int index = itsTopIndex;
+ itsTopIndex += 2;
+ return index;
+ }
+
+ int addConstant(String k)
+ {
+ int utf8Index = 0xFFFF & addUtf8(k);
+ int theIndex = itsStringConstHash.getInt(utf8Index, -1);
+ if (theIndex == -1) {
+ theIndex = itsTopIndex++;
+ ensure(3);
+ itsPool[itsTop++] = CONSTANT_String;
+ itsTop = ClassFileWriter.putInt16(utf8Index, itsPool, itsTop);
+ itsStringConstHash.put(utf8Index, theIndex);
+ }
+ return theIndex;
+ }
+
+ boolean isUnderUtfEncodingLimit(String s)
+ {
+ int strLen = s.length();
+ if (strLen * 3 <= MAX_UTF_ENCODING_SIZE) {
+ return true;
+ } else if (strLen > MAX_UTF_ENCODING_SIZE) {
+ return false;
+ }
+ return strLen == getUtfEncodingLimit(s, 0, strLen);
+ }
+
+ /**
+ * Get maximum i such that <tt>start <= i <= end</tt> and
+ * <tt>s.substring(start, i)</tt> fits JVM UTF string encoding limit.
+ */
+ int getUtfEncodingLimit(String s, int start, int end)
+ {
+ if ((end - start) * 3 <= MAX_UTF_ENCODING_SIZE) {
+ return end;
+ }
+ int limit = MAX_UTF_ENCODING_SIZE;
+ for (int i = start; i != end; i++) {
+ int c = s.charAt(i);
+ if (0 != c && c <= 0x7F) {
+ --limit;
+ } else if (c < 0x7FF) {
+ limit -= 2;
+ } else {
+ limit -= 3;
+ }
+ if (limit < 0) {
+ return i;
+ }
+ }
+ return end;
+ }
+
+ short addUtf8(String k)
+ {
+ int theIndex = itsUtf8Hash.get(k, -1);
+ if (theIndex == -1) {
+ int strLen = k.length();
+ boolean tooBigString;
+ if (strLen > MAX_UTF_ENCODING_SIZE) {
+ tooBigString = true;
+ } else {
+ tooBigString = false;
+ // Ask for worst case scenario buffer when each char takes 3
+ // bytes
+ ensure(1 + 2 + strLen * 3);
+ int top = itsTop;
+
+ itsPool[top++] = CONSTANT_Utf8;
+ top += 2; // skip length
+
+ char[] chars = cfw.getCharBuffer(strLen);
+ k.getChars(0, strLen, chars, 0);
+
+ for (int i = 0; i != strLen; i++) {
+ int c = chars[i];
+ if (c != 0 && c <= 0x7F) {
+ itsPool[top++] = (byte)c;
+ } else if (c > 0x7FF) {
+ itsPool[top++] = (byte)(0xE0 | (c >> 12));
+ itsPool[top++] = (byte)(0x80 | ((c >> 6) & 0x3F));
+ itsPool[top++] = (byte)(0x80 | (c & 0x3F));
+ } else {
+ itsPool[top++] = (byte)(0xC0 | (c >> 6));
+ itsPool[top++] = (byte)(0x80 | (c & 0x3F));
+ }
+ }
+
+ int utfLen = top - (itsTop + 1 + 2);
+ if (utfLen > MAX_UTF_ENCODING_SIZE) {
+ tooBigString = true;
+ } else {
+ // Write back length
+ itsPool[itsTop + 1] = (byte)(utfLen >>> 8);
+ itsPool[itsTop + 2] = (byte)utfLen;
+
+ itsTop = top;
+ theIndex = itsTopIndex++;
+ itsUtf8Hash.put(k, theIndex);
+ }
+ }
+ if (tooBigString) {
+ throw new IllegalArgumentException("Too big string");
+ }
+ }
+ return (short)theIndex;
+ }
+
+ private short addNameAndType(String name, String type)
+ {
+ short nameIndex = addUtf8(name);
+ short typeIndex = addUtf8(type);
+ ensure(5);
+ itsPool[itsTop++] = CONSTANT_NameAndType;
+ itsTop = ClassFileWriter.putInt16(nameIndex, itsPool, itsTop);
+ itsTop = ClassFileWriter.putInt16(typeIndex, itsPool, itsTop);
+ return (short)(itsTopIndex++);
+ }
+
+ short addClass(String className)
+ {
+ int theIndex = itsClassHash.get(className, -1);
+ if (theIndex == -1) {
+ String slashed = className;
+ if (className.indexOf('.') > 0) {
+ slashed = ClassFileWriter.getSlashedForm(className);
+ theIndex = itsClassHash.get(slashed, -1);
+ if (theIndex != -1) {
+ itsClassHash.put(className, theIndex);
+ }
+ }
+ if (theIndex == -1) {
+ int utf8Index = addUtf8(slashed);
+ ensure(3);
+ itsPool[itsTop++] = CONSTANT_Class;
+ itsTop = ClassFileWriter.putInt16(utf8Index, itsPool, itsTop);
+ theIndex = itsTopIndex++;
+ itsClassHash.put(slashed, theIndex);
+ if (className != slashed) {
+ itsClassHash.put(className, theIndex);
+ }
+ }
+ }
+ return (short)theIndex;
+ }
+
+ short addFieldRef(String className, String fieldName, String fieldType)
+ {
+ FieldOrMethodRef ref = new FieldOrMethodRef(className, fieldName,
+ fieldType);
+
+ int theIndex = itsFieldRefHash.get(ref, -1);
+ if (theIndex == -1) {
+ short ntIndex = addNameAndType(fieldName, fieldType);
+ short classIndex = addClass(className);
+ ensure(5);
+ itsPool[itsTop++] = CONSTANT_Fieldref;
+ itsTop = ClassFileWriter.putInt16(classIndex, itsPool, itsTop);
+ itsTop = ClassFileWriter.putInt16(ntIndex, itsPool, itsTop);
+ theIndex = itsTopIndex++;
+ itsFieldRefHash.put(ref, theIndex);
+ }
+ return (short)theIndex;
+ }
+
+ short addMethodRef(String className, String methodName,
+ String methodType)
+ {
+ FieldOrMethodRef ref = new FieldOrMethodRef(className, methodName,
+ methodType);
+
+ int theIndex = itsMethodRefHash.get(ref, -1);
+ if (theIndex == -1) {
+ short ntIndex = addNameAndType(methodName, methodType);
+ short classIndex = addClass(className);
+ ensure(5);
+ itsPool[itsTop++] = CONSTANT_Methodref;
+ itsTop = ClassFileWriter.putInt16(classIndex, itsPool, itsTop);
+ itsTop = ClassFileWriter.putInt16(ntIndex, itsPool, itsTop);
+ theIndex = itsTopIndex++;
+ itsMethodRefHash.put(ref, theIndex);
+ }
+ return (short)theIndex;
+ }
+
+ short addInterfaceMethodRef(String className,
+ String methodName, String methodType)
+ {
+ short ntIndex = addNameAndType(methodName, methodType);
+ short classIndex = addClass(className);
+ ensure(5);
+ itsPool[itsTop++] = CONSTANT_InterfaceMethodref;
+ itsTop = ClassFileWriter.putInt16(classIndex, itsPool, itsTop);
+ itsTop = ClassFileWriter.putInt16(ntIndex, itsPool, itsTop);
+ return (short)(itsTopIndex++);
+ }
+
+ void ensure(int howMuch)
+ {
+ if (itsTop + howMuch > itsPool.length) {
+ int newCapacity = itsPool.length * 2;
+ if (itsTop + howMuch > newCapacity) {
+ newCapacity = itsTop + howMuch;
+ }
+ byte[] tmp = new byte[newCapacity];
+ System.arraycopy(itsPool, 0, tmp, 0, itsTop);
+ itsPool = tmp;
+ }
+ }
+
+ private ClassFileWriter cfw;
+
+ private static final int MAX_UTF_ENCODING_SIZE = 65535;
+
+ private UintMap itsStringConstHash = new UintMap();
+ private ObjToIntMap itsUtf8Hash = new ObjToIntMap();
+ private ObjToIntMap itsFieldRefHash = new ObjToIntMap();
+ private ObjToIntMap itsMethodRefHash = new ObjToIntMap();
+ private ObjToIntMap itsClassHash = new ObjToIntMap();
+
+ private int itsTop;
+ private int itsTopIndex;
+ private byte itsPool[];
+}
+
+final class FieldOrMethodRef
+{
+ FieldOrMethodRef(String className, String name, String type)
+ {
+ this.className = className;
+ this.name = name;
+ this.type = type;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (!(obj instanceof FieldOrMethodRef)) { return false; }
+ FieldOrMethodRef x = (FieldOrMethodRef)obj;
+ return className.equals(x.className)
+ && name.equals(x.name)
+ && type.equals(x.type);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ if (hashCode == -1) {
+ int h1 = className.hashCode();
+ int h2 = name.hashCode();
+ int h3 = type.hashCode();
+ hashCode = h1 ^ h2 ^ h3;
+ }
+ return hashCode;
+ }
+
+ private String className;
+ private String name;
+ private String type;
+ private int hashCode = -1;
+}
diff --git a/src/org/mozilla/javascript/Arguments.java b/src/org/mozilla/javascript/Arguments.java
new file mode 100644
index 0000000..d914294
--- /dev/null
+++ b/src/org/mozilla/javascript/Arguments.java
@@ -0,0 +1,322 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * This class implements the "arguments" object.
+ *
+ * See ECMA 10.1.8
+ *
+ * @see org.mozilla.javascript.NativeCall
+ * @author Norris Boyd
+ */
+final class Arguments extends IdScriptableObject
+{
+ static final long serialVersionUID = 4275508002492040609L;
+
+ public Arguments(NativeCall activation)
+ {
+ this.activation = activation;
+
+ Scriptable parent = activation.getParentScope();
+ setParentScope(parent);
+ setPrototype(ScriptableObject.getObjectPrototype(parent));
+
+ args = activation.originalArgs;
+ lengthObj = new Integer(args.length);
+
+ NativeFunction f = activation.function;
+ calleeObj = f;
+
+ int version = f.getLanguageVersion();
+ if (version <= Context.VERSION_1_3
+ && version != Context.VERSION_DEFAULT)
+ {
+ callerObj = null;
+ } else {
+ callerObj = NOT_FOUND;
+ }
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return "Object";
+ }
+
+ @Override
+ public boolean has(int index, Scriptable start)
+ {
+ if (0 <= index && index < args.length) {
+ if (args[index] != NOT_FOUND) {
+ return true;
+ }
+ }
+ return super.has(index, start);
+ }
+
+ @Override
+ public Object get(int index, Scriptable start)
+ {
+ if (0 <= index && index < args.length) {
+ Object value = args[index];
+ if (value != NOT_FOUND) {
+ if (sharedWithActivation(index)) {
+ NativeFunction f = activation.function;
+ String argName = f.getParamOrVarName(index);
+ value = activation.get(argName, activation);
+ if (value == NOT_FOUND) Kit.codeBug();
+ }
+ return value;
+ }
+ }
+ return super.get(index, start);
+ }
+
+ private boolean sharedWithActivation(int index)
+ {
+ NativeFunction f = activation.function;
+ int definedCount = f.getParamCount();
+ if (index < definedCount) {
+ // Check if argument is not hidden by later argument with the same
+ // name as hidden arguments are not shared with activation
+ if (index < definedCount - 1) {
+ String argName = f.getParamOrVarName(index);
+ for (int i = index + 1; i < definedCount; i++) {
+ if (argName.equals(f.getParamOrVarName(i))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void put(int index, Scriptable start, Object value)
+ {
+ if (0 <= index && index < args.length) {
+ if (args[index] != NOT_FOUND) {
+ if (sharedWithActivation(index)) {
+ String argName;
+ argName = activation.function.getParamOrVarName(index);
+ activation.put(argName, activation, value);
+ return;
+ }
+ synchronized (this) {
+ if (args[index] != NOT_FOUND) {
+ if (args == activation.originalArgs) {
+ args = args.clone();
+ }
+ args[index] = value;
+ return;
+ }
+ }
+ }
+ }
+ super.put(index, start, value);
+ }
+
+ @Override
+ public void delete(int index)
+ {
+ if (0 <= index && index < args.length) {
+ synchronized (this) {
+ if (args[index] != NOT_FOUND) {
+ if (args == activation.originalArgs) {
+ args = args.clone();
+ }
+ args[index] = NOT_FOUND;
+ return;
+ }
+ }
+ }
+ super.delete(index);
+ }
+
+// #string_id_map#
+
+ private static final int
+ Id_callee = 1,
+ Id_length = 2,
+ Id_caller = 3,
+
+ MAX_INSTANCE_ID = 3;
+
+ @Override
+ protected int getMaxInstanceId()
+ {
+ return MAX_INSTANCE_ID;
+ }
+
+ @Override
+ protected int findInstanceIdInfo(String s)
+ {
+ int id;
+// #generated# Last update: 2007-05-09 08:15:04 EDT
+ L0: { id = 0; String X = null; int c;
+ if (s.length()==6) {
+ c=s.charAt(5);
+ if (c=='e') { X="callee";id=Id_callee; }
+ else if (c=='h') { X="length";id=Id_length; }
+ else if (c=='r') { X="caller";id=Id_caller; }
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+
+ if (id == 0) return super.findInstanceIdInfo(s);
+
+ int attr;
+ switch (id) {
+ case Id_callee:
+ case Id_caller:
+ case Id_length:
+ attr = DONTENUM;
+ break;
+ default: throw new IllegalStateException();
+ }
+ return instanceIdInfo(attr, id);
+ }
+
+// #/string_id_map#
+
+ @Override
+ protected String getInstanceIdName(int id)
+ {
+ switch (id) {
+ case Id_callee: return "callee";
+ case Id_length: return "length";
+ case Id_caller: return "caller";
+ }
+ return null;
+ }
+
+ @Override
+ protected Object getInstanceIdValue(int id)
+ {
+ switch (id) {
+ case Id_callee: return calleeObj;
+ case Id_length: return lengthObj;
+ case Id_caller: {
+ Object value = callerObj;
+ if (value == UniqueTag.NULL_VALUE) { value = null; }
+ else if (value == null) {
+ NativeCall caller = activation.parentActivationCall;
+ if (caller != null) {
+ value = caller.get("arguments", caller);
+ }
+ }
+ return value;
+ }
+ }
+ return super.getInstanceIdValue(id);
+ }
+
+ @Override
+ protected void setInstanceIdValue(int id, Object value)
+ {
+ switch (id) {
+ case Id_callee: calleeObj = value; return;
+ case Id_length: lengthObj = value; return;
+ case Id_caller:
+ callerObj = (value != null) ? value : UniqueTag.NULL_VALUE;
+ return;
+ }
+ super.setInstanceIdValue(id, value);
+ }
+
+ @Override
+ Object[] getIds(boolean getAll)
+ {
+ Object[] ids = super.getIds(getAll);
+ if (getAll && args.length != 0) {
+ boolean[] present = null;
+ int extraCount = args.length;
+ for (int i = 0; i != ids.length; ++i) {
+ Object id = ids[i];
+ if (id instanceof Integer) {
+ int index = ((Integer)id).intValue();
+ if (0 <= index && index < args.length) {
+ if (present == null) {
+ present = new boolean[args.length];
+ }
+ if (!present[index]) {
+ present[index] = true;
+ extraCount--;
+ }
+ }
+ }
+ }
+ if (extraCount != 0) {
+ Object[] tmp = new Object[extraCount + ids.length];
+ System.arraycopy(ids, 0, tmp, extraCount, ids.length);
+ ids = tmp;
+ int offset = 0;
+ for (int i = 0; i != args.length; ++i) {
+ if (present == null || !present[i]) {
+ ids[offset] = new Integer(i);
+ ++offset;
+ }
+ }
+ if (offset != extraCount) Kit.codeBug();
+ }
+ }
+ return ids;
+ }
+
+// Fields to hold caller, callee and length properties,
+// where NOT_FOUND value tags deleted properties.
+// In addition if callerObj == NULL_VALUE, it tags null for scripts, as
+// initial callerObj == null means access to caller arguments available
+// only in JS <= 1.3 scripts
+ private Object callerObj;
+ private Object calleeObj;
+ private Object lengthObj;
+
+ private NativeCall activation;
+
+// Initially args holds activation.getOriginalArgs(), but any modification
+// of its elements triggers creation of a copy. If its element holds NOT_FOUND,
+// it indicates deleted index, in which case super class is queried.
+ private Object[] args;
+}
diff --git a/src/org/mozilla/javascript/BaseFunction.java b/src/org/mozilla/javascript/BaseFunction.java
new file mode 100644
index 0000000..ffec329
--- /dev/null
+++ b/src/org/mozilla/javascript/BaseFunction.java
@@ -0,0 +1,564 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Roger Lawrence
+ * Mike McCabe
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * The base class for Function objects
+ * See ECMA 15.3.
+ * @author Norris Boyd
+ */
+public class BaseFunction extends IdScriptableObject implements Function
+{
+
+ static final long serialVersionUID = 5311394446546053859L;
+
+ private static final Object FUNCTION_TAG = "Function";
+
+ static void init(Scriptable scope, boolean sealed)
+ {
+ BaseFunction obj = new BaseFunction();
+ // Function.prototype attributes: see ECMA 15.3.3.1
+ obj.prototypePropertyAttributes = DONTENUM | READONLY | PERMANENT;
+ obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+ }
+
+ public BaseFunction()
+ {
+ }
+
+ public BaseFunction(Scriptable scope, Scriptable prototype)
+ {
+ super(scope, prototype);
+ }
+
+ @Override
+ public String getClassName() {
+ return "Function";
+ }
+
+ /**
+ * Implements the instanceof operator for JavaScript Function objects.
+ * <p>
+ * <code>
+ * foo = new Foo();<br>
+ * foo instanceof Foo; // true<br>
+ * </code>
+ *
+ * @param instance The value that appeared on the LHS of the instanceof
+ * operator
+ * @return true if the "prototype" property of "this" appears in
+ * value's prototype chain
+ *
+ */
+ @Override
+ public boolean hasInstance(Scriptable instance)
+ {
+ Object protoProp = ScriptableObject.getProperty(this, "prototype");
+ if (protoProp instanceof Scriptable) {
+ return ScriptRuntime.jsDelegatesTo(instance, (Scriptable)protoProp);
+ }
+ throw ScriptRuntime.typeError1("msg.instanceof.bad.prototype",
+ getFunctionName());
+ }
+
+// #string_id_map#
+
+ private static final int
+ Id_length = 1,
+ Id_arity = 2,
+ Id_name = 3,
+ Id_prototype = 4,
+ Id_arguments = 5,
+
+ MAX_INSTANCE_ID = 5;
+
+ @Override
+ protected int getMaxInstanceId()
+ {
+ return MAX_INSTANCE_ID;
+ }
+
+ @Override
+ protected int findInstanceIdInfo(String s)
+ {
+ int id;
+// #generated# Last update: 2007-05-09 08:15:15 EDT
+ L0: { id = 0; String X = null; int c;
+ L: switch (s.length()) {
+ case 4: X="name";id=Id_name; break L;
+ case 5: X="arity";id=Id_arity; break L;
+ case 6: X="length";id=Id_length; break L;
+ case 9: c=s.charAt(0);
+ if (c=='a') { X="arguments";id=Id_arguments; }
+ else if (c=='p') { X="prototype";id=Id_prototype; }
+ break L;
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+// #/string_id_map#
+
+ if (id == 0) return super.findInstanceIdInfo(s);
+
+ int attr;
+ switch (id) {
+ case Id_length:
+ case Id_arity:
+ case Id_name:
+ attr = DONTENUM | READONLY | PERMANENT;
+ break;
+ case Id_prototype:
+ attr = prototypePropertyAttributes;
+ break;
+ case Id_arguments:
+ attr = DONTENUM | PERMANENT;
+ break;
+ default: throw new IllegalStateException();
+ }
+ return instanceIdInfo(attr, id);
+ }
+
+ @Override
+ protected String getInstanceIdName(int id)
+ {
+ switch (id) {
+ case Id_length: return "length";
+ case Id_arity: return "arity";
+ case Id_name: return "name";
+ case Id_prototype: return "prototype";
+ case Id_arguments: return "arguments";
+ }
+ return super.getInstanceIdName(id);
+ }
+
+ @Override
+ protected Object getInstanceIdValue(int id)
+ {
+ switch (id) {
+ case Id_length: return ScriptRuntime.wrapInt(getLength());
+ case Id_arity: return ScriptRuntime.wrapInt(getArity());
+ case Id_name: return getFunctionName();
+ case Id_prototype: return getPrototypeProperty();
+ case Id_arguments: return getArguments();
+ }
+ return super.getInstanceIdValue(id);
+ }
+
+ @Override
+ protected void setInstanceIdValue(int id, Object value)
+ {
+ if (id == Id_prototype) {
+ if ((prototypePropertyAttributes & READONLY) == 0) {
+ prototypeProperty = (value != null)
+ ? value : UniqueTag.NULL_VALUE;
+ }
+ return;
+ } else if (id == Id_arguments) {
+ if (value == NOT_FOUND) {
+ // This should not be called since "arguments" is PERMANENT
+ Kit.codeBug();
+ }
+ defaultPut("arguments", value);
+ }
+ super.setInstanceIdValue(id, value);
+ }
+
+ @Override
+ protected void fillConstructorProperties(IdFunctionObject ctor)
+ {
+ // Fix up bootstrapping problem: getPrototype of the IdFunctionObject
+ // can not return Function.prototype because Function object is not
+ // yet defined.
+ ctor.setPrototype(this);
+ super.fillConstructorProperties(ctor);
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_constructor: arity=1; s="constructor"; break;
+ case Id_toString: arity=1; s="toString"; break;
+ case Id_toSource: arity=1; s="toSource"; break;
+ case Id_apply: arity=2; s="apply"; break;
+ case Id_call: arity=1; s="call"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(FUNCTION_TAG, id, s, arity);
+ }
+
+ static boolean isApply(IdFunctionObject f) {
+ return f.hasTag(FUNCTION_TAG) && f.methodId() == Id_apply;
+ }
+
+ static boolean isApplyOrCall(IdFunctionObject f) {
+ if(f.hasTag(FUNCTION_TAG)) {
+ switch(f.methodId()) {
+ case Id_apply:
+ case Id_call:
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(FUNCTION_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+ switch (id) {
+ case Id_constructor:
+ return jsConstructor(cx, scope, args);
+
+ case Id_toString: {
+ BaseFunction realf = realFunction(thisObj, f);
+ int indent = ScriptRuntime.toInt32(args, 0);
+ return realf.decompile(indent, 0);
+ }
+
+ case Id_toSource: {
+ BaseFunction realf = realFunction(thisObj, f);
+ int indent = 0;
+ int flags = Decompiler.TO_SOURCE_FLAG;
+ if (args.length != 0) {
+ indent = ScriptRuntime.toInt32(args[0]);
+ if (indent >= 0) {
+ flags = 0;
+ } else {
+ indent = 0;
+ }
+ }
+ return realf.decompile(indent, flags);
+ }
+
+ case Id_apply:
+ case Id_call:
+ return ScriptRuntime.applyOrCall(id == Id_apply,
+ cx, scope, thisObj, args);
+ }
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+
+ private BaseFunction realFunction(Scriptable thisObj, IdFunctionObject f)
+ {
+ Object x = thisObj.getDefaultValue(ScriptRuntime.FunctionClass);
+ if (x instanceof BaseFunction) {
+ return (BaseFunction)x;
+ }
+ throw ScriptRuntime.typeError1("msg.incompat.call",
+ f.getFunctionName());
+ }
+
+ /**
+ * Make value as DontEnum, DontDelete, ReadOnly
+ * prototype property of this Function object
+ */
+ public void setImmunePrototypeProperty(Object value)
+ {
+ if ((prototypePropertyAttributes & READONLY) != 0) {
+ throw new IllegalStateException();
+ }
+ prototypeProperty = (value != null) ? value : UniqueTag.NULL_VALUE;
+ prototypePropertyAttributes = DONTENUM | PERMANENT | READONLY;
+ }
+
+ protected Scriptable getClassPrototype()
+ {
+ Object protoVal = getPrototypeProperty();
+ if (protoVal instanceof Scriptable) {
+ return (Scriptable) protoVal;
+ }
+ return getClassPrototype(this, "Object");
+ }
+
+ /**
+ * Should be overridden.
+ */
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ return Undefined.instance;
+ }
+
+ public Scriptable construct(Context cx, Scriptable scope, Object[] args)
+ {
+ Scriptable result = createObject(cx, scope);
+ if (result != null) {
+ Object val = call(cx, scope, result, args);
+ if (val instanceof Scriptable) {
+ result = (Scriptable)val;
+ }
+ } else {
+ Object val = call(cx, scope, null, args);
+ if (!(val instanceof Scriptable)) {
+ // It is program error not to return Scriptable from
+ // the call method if createObject returns null.
+ throw new IllegalStateException(
+ "Bad implementaion of call as constructor, name="
+ +getFunctionName()+" in "+getClass().getName());
+ }
+ result = (Scriptable)val;
+ if (result.getPrototype() == null) {
+ result.setPrototype(getClassPrototype());
+ }
+ if (result.getParentScope() == null) {
+ Scriptable parent = getParentScope();
+ if (result != parent) {
+ result.setParentScope(parent);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates new script object.
+ * The default implementation of {@link #construct} uses the method to
+ * to get the value for <tt>thisObj</tt> argument when invoking
+ * {@link #call}.
+ * The methos is allowed to return <tt>null</tt> to indicate that
+ * {@link #call} will create a new object itself. In this case
+ * {@link #construct} will set scope and prototype on the result
+ * {@link #call} unless they are already set.
+ */
+ public Scriptable createObject(Context cx, Scriptable scope)
+ {
+ Scriptable newInstance = new NativeObject();
+ newInstance.setPrototype(getClassPrototype());
+ newInstance.setParentScope(getParentScope());
+ return newInstance;
+ }
+
+ /**
+ * Decompile the source information associated with this js
+ * function/script back into a string.
+ *
+ * @param indent How much to indent the decompiled result.
+ *
+ * @param flags Flags specifying format of decompilation output.
+ */
+ String decompile(int indent, int flags)
+ {
+ StringBuffer sb = new StringBuffer();
+ boolean justbody = (0 != (flags & Decompiler.ONLY_BODY_FLAG));
+ if (!justbody) {
+ sb.append("function ");
+ sb.append(getFunctionName());
+ sb.append("() {\n\t");
+ }
+ sb.append("[native code, arity=");
+ sb.append(getArity());
+ sb.append("]\n");
+ if (!justbody) {
+ sb.append("}\n");
+ }
+ return sb.toString();
+ }
+
+ public int getArity() { return 0; }
+
+ public int getLength() { return 0; }
+
+ public String getFunctionName()
+ {
+ return "";
+ }
+
+ final Object getPrototypeProperty() {
+ Object result = prototypeProperty;
+ if (result == null) {
+ synchronized (this) {
+ result = prototypeProperty;
+ if (result == null) {
+ setupDefaultPrototype();
+ result = prototypeProperty;
+ }
+ }
+ }
+ else if (result == UniqueTag.NULL_VALUE) { result = null; }
+ return result;
+ }
+
+ private void setupDefaultPrototype()
+ {
+ NativeObject obj = new NativeObject();
+ final int attr = ScriptableObject.DONTENUM;
+ obj.defineProperty("constructor", this, attr);
+ // put the prototype property into the object now, then in the
+ // wacky case of a user defining a function Object(), we don't
+ // get an infinite loop trying to find the prototype.
+ prototypeProperty = obj;
+ Scriptable proto = getObjectPrototype(this);
+ if (proto != obj) {
+ // not the one we just made, it must remain grounded
+ obj.setPrototype(proto);
+ }
+ }
+
+ private Object getArguments()
+ {
+ // <Function name>.arguments is deprecated, so we use a slow
+ // way of getting it that doesn't add to the invocation cost.
+ // TODO: add warning, error based on version
+ Object value = defaultGet("arguments");
+ if (value != NOT_FOUND) {
+ // Should after changing <Function name>.arguments its
+ // activation still be available during Function call?
+ // This code assumes it should not:
+ // defaultGet("arguments") != NOT_FOUND
+ // means assigned arguments
+ return value;
+ }
+ Context cx = Context.getContext();
+ NativeCall activation = ScriptRuntime.findFunctionActivation(cx, this);
+ return (activation == null)
+ ? null
+ : activation.get("arguments", activation);
+ }
+
+ private static Object jsConstructor(Context cx, Scriptable scope,
+ Object[] args)
+ {
+ int arglen = args.length;
+ StringBuffer sourceBuf = new StringBuffer();
+
+ sourceBuf.append("function ");
+ /* version != 1.2 Function constructor behavior -
+ * print 'anonymous' as the function name if the
+ * version (under which the function was compiled) is
+ * less than 1.2... or if it's greater than 1.2, because
+ * we need to be closer to ECMA.
+ */
+ if (cx.getLanguageVersion() != Context.VERSION_1_2) {
+ sourceBuf.append("anonymous");
+ }
+ sourceBuf.append('(');
+
+ // Append arguments as coma separated strings
+ for (int i = 0; i < arglen - 1; i++) {
+ if (i > 0) {
+ sourceBuf.append(',');
+ }
+ sourceBuf.append(ScriptRuntime.toString(args[i]));
+ }
+ sourceBuf.append(") {");
+ if (arglen != 0) {
+ // append function body
+ String funBody = ScriptRuntime.toString(args[arglen - 1]);
+ sourceBuf.append(funBody);
+ }
+ sourceBuf.append('}');
+ String source = sourceBuf.toString();
+
+ int[] linep = new int[1];
+ String filename = Context.getSourcePositionFromStack(linep);
+ if (filename == null) {
+ filename = "<eval'ed string>";
+ linep[0] = 1;
+ }
+
+ String sourceURI = ScriptRuntime.
+ makeUrlForGeneratedScript(false, filename, linep[0]);
+
+ Scriptable global = ScriptableObject.getTopLevelScope(scope);
+
+ ErrorReporter reporter;
+ reporter = DefaultErrorReporter.forEval(cx.getErrorReporter());
+
+ Evaluator evaluator = Context.createInterpreter();
+ if (evaluator == null) {
+ throw new JavaScriptException("Interpreter not present",
+ filename, linep[0]);
+ }
+
+ // Compile with explicit interpreter instance to force interpreter
+ // mode.
+ return cx.compileFunction(global, source, evaluator, reporter,
+ sourceURI, 1, null);
+ }
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #string_id_map#
+// #generated# Last update: 2007-05-09 08:15:15 EDT
+ L0: { id = 0; String X = null; int c;
+ L: switch (s.length()) {
+ case 4: X="call";id=Id_call; break L;
+ case 5: X="apply";id=Id_apply; break L;
+ case 8: c=s.charAt(3);
+ if (c=='o') { X="toSource";id=Id_toSource; }
+ else if (c=='t') { X="toString";id=Id_toString; }
+ break L;
+ case 11: X="constructor";id=Id_constructor; break L;
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_constructor = 1,
+ Id_toString = 2,
+ Id_toSource = 3,
+ Id_apply = 4,
+ Id_call = 5,
+
+ MAX_PROTOTYPE_ID = 5;
+
+// #/string_id_map#
+
+ private Object prototypeProperty;
+ // For function object instances, attribute is PERMANENT; see ECMA 15.3.5.2
+ private int prototypePropertyAttributes = PERMANENT;
+}
+
diff --git a/src/org/mozilla/javascript/Callable.java b/src/org/mozilla/javascript/Callable.java
new file mode 100644
index 0000000..03e0fce
--- /dev/null
+++ b/src/org/mozilla/javascript/Callable.java
@@ -0,0 +1,59 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov, igor at fastmail.fm
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * Generic notion of callable object that can execute some script-related code
+ * upon request with specified values for script scope and this objects.
+ */
+public interface Callable
+{
+ /**
+ * Perform the call.
+ *
+ * @param cx the current Context for this thread
+ * @param scope the scope to use to resolve properties.
+ * @param thisObj the JavaScript <code>this</code> object
+ * @param args the array of arguments
+ * @return the result of the call
+ */
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args);
+}
+
diff --git a/src/org/mozilla/javascript/ClassCache.java b/src/org/mozilla/javascript/ClassCache.java
new file mode 100644
index 0000000..e1c97d4
--- /dev/null
+++ b/src/org/mozilla/javascript/ClassCache.java
@@ -0,0 +1,223 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov, igor at fastmail.fm
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.io.Serializable;
+
+/**
+ * Cache of generated classes and data structures to access Java runtime
+ * from JavaScript.
+ *
+ * @author Igor Bukanov
+ *
+ * @since Rhino 1.5 Release 5
+ */
+public class ClassCache implements Serializable
+{
+ private static final long serialVersionUID = -8866246036237312215L;
+ private static final Object AKEY = "ClassCache";
+ private volatile boolean cachingIsEnabled = true;
+ private transient HashMap<Class<?>,JavaMembers> classTable;
+ private transient HashMap<JavaAdapter.JavaAdapterSignature,Class<?>> classAdapterCache;
+ private transient HashMap<Class<?>,Object> interfaceAdapterCache;
+ private int generatedClassSerial;
+
+ /**
+ * Search for ClassCache object in the given scope.
+ * The method first calls
+ * {@link ScriptableObject#getTopLevelScope(Scriptable scope)}
+ * to get the top most scope and then tries to locate associated
+ * ClassCache object in the prototype chain of the top scope.
+ *
+ * @param scope scope to search for ClassCache object.
+ * @return previously associated ClassCache object or a new instance of
+ * ClassCache if no ClassCache object was found.
+ *
+ * @see #associate(ScriptableObject topScope)
+ */
+ public static ClassCache get(Scriptable scope)
+ {
+ ClassCache cache = (ClassCache)
+ ScriptableObject.getTopScopeValue(scope, AKEY);
+ if (cache == null) {
+ throw new RuntimeException("Can't find top level scope for " +
+ "ClassCache.get");
+ }
+ return cache;
+ }
+
+ /**
+ * Associate ClassCache object with the given top-level scope.
+ * The ClassCache object can only be associated with the given scope once.
+ *
+ * @param topScope scope to associate this ClassCache object with.
+ * @return true if no previous ClassCache objects were embedded into
+ * the scope and this ClassCache were successfully associated
+ * or false otherwise.
+ *
+ * @see #get(Scriptable scope)
+ */
+ public boolean associate(ScriptableObject topScope)
+ {
+ if (topScope.getParentScope() != null) {
+ // Can only associate cache with top level scope
+ throw new IllegalArgumentException();
+ }
+ if (this == topScope.associateValue(AKEY, this)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Empty caches of generated Java classes and Java reflection information.
+ */
+ public synchronized void clearCaches()
+ {
+ classTable = null;
+ classAdapterCache = null;
+ interfaceAdapterCache = null;
+ }
+
+ /**
+ * Check if generated Java classes and Java reflection information
+ * is cached.
+ */
+ public final boolean isCachingEnabled()
+ {
+ return cachingIsEnabled;
+ }
+
+ /**
+ * Set whether to cache some values.
+ * <p>
+ * By default, the engine will cache the results of
+ * <tt>Class.getMethods()</tt> and similar calls.
+ * This can speed execution dramatically, but increases the memory
+ * footprint. Also, with caching enabled, references may be held to
+ * objects past the lifetime of any real usage.
+ * <p>
+ * If caching is enabled and this method is called with a
+ * <code>false</code> argument, the caches will be emptied.
+ * <p>
+ * Caching is enabled by default.
+ *
+ * @param enabled if true, caching is enabled
+ *
+ * @see #clearCaches()
+ */
+ public synchronized void setCachingEnabled(boolean enabled)
+ {
+ if (enabled == cachingIsEnabled)
+ return;
+ if (!enabled)
+ clearCaches();
+ cachingIsEnabled = enabled;
+ }
+
+ /**
+ * @return a map from classes to associated JavaMembers objects
+ */
+ Map<Class<?>,JavaMembers> getClassCacheMap() {
+ if (classTable == null) {
+ classTable = new HashMap<Class<?>,JavaMembers>();
+ }
+ return classTable;
+ }
+
+ Map<JavaAdapter.JavaAdapterSignature,Class<?>> getInterfaceAdapterCacheMap()
+ {
+ if (classAdapterCache == null) {
+ classAdapterCache = new HashMap<JavaAdapter.JavaAdapterSignature,Class<?>>();
+ }
+ return classAdapterCache;
+ }
+
+ /**
+ * @deprecated
+ * The method always returns false.
+ * @see #setInvokerOptimizationEnabled(boolean enabled)
+ */
+ public boolean isInvokerOptimizationEnabled()
+ {
+ return false;
+ }
+
+ /**
+ * @deprecated
+ * The method does nothing.
+ * Invoker optimization is no longer used by Rhino.
+ * On modern JDK like 1.4 or 1.5 the disadvantages of the optimization
+ * like increased memory usage or longer initialization time overweight
+ * small speed increase that can be gained using generated proxy class
+ * to replace reflection.
+ */
+ public synchronized void setInvokerOptimizationEnabled(boolean enabled)
+ {
+ }
+
+ /**
+ * Internal engine method to return serial number for generated classes
+ * to ensure name uniqueness.
+ */
+ public final synchronized int newClassSerialNumber()
+ {
+ return ++generatedClassSerial;
+ }
+
+ Object getInterfaceAdapter(Class<?> cl)
+ {
+ return interfaceAdapterCache == null
+ ? null
+ : interfaceAdapterCache.get(cl);
+ }
+
+ synchronized void cacheInterfaceAdapter(Class<?> cl, Object iadapter)
+ {
+ if (cachingIsEnabled) {
+ if (interfaceAdapterCache == null) {
+ interfaceAdapterCache = new HashMap<Class<?>,Object>();
+ }
+ interfaceAdapterCache.put(cl, iadapter);
+ }
+ }
+}
diff --git a/src/org/mozilla/javascript/ClassShutter.java b/src/org/mozilla/javascript/ClassShutter.java
new file mode 100644
index 0000000..d5f4cd6
--- /dev/null
+++ b/src/org/mozilla/javascript/ClassShutter.java
@@ -0,0 +1,89 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+Embeddings that wish to filter Java classes that are visible to scripts
+through the LiveConnect, should implement this interface.
+
+ at see Context#setClassShutter(ClassShutter)
+ at since 1.5 Release 4
+ at author Norris Boyd
+*/
+
+ public interface ClassShutter {
+
+ /**
+ * Return true iff the Java class with the given name should be exposed
+ * to scripts.
+ * <p>
+ * An embedding may filter which Java classes are exposed through
+ * LiveConnect to JavaScript scripts.
+ * <p>
+ * Due to the fact that there is no package reflection in Java,
+ * this method will also be called with package names. There
+ * is no way for Rhino to tell if "Packages.a.b" is a package name
+ * or a class that doesn't exist. What Rhino does is attempt
+ * to load each segment of "Packages.a.b.c": It first attempts to
+ * load class "a", then attempts to load class "a.b", then
+ * finally attempts to load class "a.b.c". On a Rhino installation
+ * without any ClassShutter set, and without any of the
+ * above classes, the expression "Packages.a.b.c" will result in
+ * a [JavaPackage a.b.c] and not an error.
+ * <p>
+ * With ClassShutter supplied, Rhino will first call
+ * visibleToScripts before attempting to look up the class name. If
+ * visibleToScripts returns false, the class name lookup is not
+ * performed and subsequent Rhino execution assumes the class is
+ * not present. So for "java.lang.System.out.println" the lookup
+ * of "java.lang.System" is skipped and thus Rhino assumes that
+ * "java.lang.System" doesn't exist. So then for "java.lang.System.out",
+ * Rhino attempts to load the class "java.lang.System.out" because
+ * it assumes that "java.lang.System" is a package name.
+ * <p>
+ * @param fullClassName the full name of the class (including the package
+ * name, with '.' as a delimiter). For example the
+ * standard string class is "java.lang.String"
+ * @return whether or not to reveal this class to scripts
+ */
+ public boolean visibleToScripts(String fullClassName);
+}
diff --git a/src/org/mozilla/javascript/CompilerEnvirons.java b/src/org/mozilla/javascript/CompilerEnvirons.java
new file mode 100644
index 0000000..92f31dc
--- /dev/null
+++ b/src/org/mozilla/javascript/CompilerEnvirons.java
@@ -0,0 +1,233 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov, igor at fastmail.fm
+ * Bob Jervis
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.util.Set;
+
+public class CompilerEnvirons
+{
+ public CompilerEnvirons()
+ {
+ errorReporter = DefaultErrorReporter.instance;
+ languageVersion = Context.VERSION_DEFAULT;
+ generateDebugInfo = true;
+ useDynamicScope = false;
+ reservedKeywordAsIdentifier = false;
+ allowMemberExprAsFunctionName = false;
+ xmlAvailable = true;
+ optimizationLevel = 0;
+ generatingSource = true;
+ strictMode = false;
+ warningAsError = false;
+ generateObserverCount = false;
+ }
+
+ public void initFromContext(Context cx)
+ {
+ setErrorReporter(cx.getErrorReporter());
+ this.languageVersion = cx.getLanguageVersion();
+ useDynamicScope = cx.compileFunctionsWithDynamicScopeFlag;
+ generateDebugInfo = (!cx.isGeneratingDebugChanged()
+ || cx.isGeneratingDebug());
+ reservedKeywordAsIdentifier
+ = cx.hasFeature(Context.FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER);
+ allowMemberExprAsFunctionName
+ = cx.hasFeature(Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME);
+ strictMode
+ = cx.hasFeature(Context.FEATURE_STRICT_MODE);
+ warningAsError = cx.hasFeature(Context.FEATURE_WARNING_AS_ERROR);
+ xmlAvailable
+ = cx.hasFeature(Context.FEATURE_E4X);
+
+ optimizationLevel = cx.getOptimizationLevel();
+
+ generatingSource = cx.isGeneratingSource();
+ activationNames = cx.activationNames;
+
+ // Observer code generation in compiled code :
+ generateObserverCount = cx.generateObserverCount;
+ }
+
+ public final ErrorReporter getErrorReporter()
+ {
+ return errorReporter;
+ }
+
+ public void setErrorReporter(ErrorReporter errorReporter)
+ {
+ if (errorReporter == null) throw new IllegalArgumentException();
+ this.errorReporter = errorReporter;
+ }
+
+ public final int getLanguageVersion()
+ {
+ return languageVersion;
+ }
+
+ public void setLanguageVersion(int languageVersion)
+ {
+ Context.checkLanguageVersion(languageVersion);
+ this.languageVersion = languageVersion;
+ }
+
+ public final boolean isGenerateDebugInfo()
+ {
+ return generateDebugInfo;
+ }
+
+ public void setGenerateDebugInfo(boolean flag)
+ {
+ this.generateDebugInfo = flag;
+ }
+
+ public final boolean isUseDynamicScope()
+ {
+ return useDynamicScope;
+ }
+
+ public final boolean isReservedKeywordAsIdentifier()
+ {
+ return reservedKeywordAsIdentifier;
+ }
+
+ public void setReservedKeywordAsIdentifier(boolean flag)
+ {
+ reservedKeywordAsIdentifier = flag;
+ }
+
+ public final boolean isAllowMemberExprAsFunctionName()
+ {
+ return allowMemberExprAsFunctionName;
+ }
+
+ public void setAllowMemberExprAsFunctionName(boolean flag)
+ {
+ allowMemberExprAsFunctionName = flag;
+ }
+
+ public final boolean isXmlAvailable()
+ {
+ return xmlAvailable;
+ }
+
+ public void setXmlAvailable(boolean flag)
+ {
+ xmlAvailable = flag;
+ }
+
+ public final int getOptimizationLevel()
+ {
+ return optimizationLevel;
+ }
+
+ public void setOptimizationLevel(int level)
+ {
+ Context.checkOptimizationLevel(level);
+ this.optimizationLevel = level;
+ }
+
+ public final boolean isGeneratingSource()
+ {
+ return generatingSource;
+ }
+
+ public final boolean isStrictMode()
+ {
+ return strictMode;
+ }
+
+ public final boolean reportWarningAsError()
+ {
+ return warningAsError;
+ }
+
+ /**
+ * Specify whether or not source information should be generated.
+ * <p>
+ * Without source information, evaluating the "toString" method
+ * on JavaScript functions produces only "[native code]" for
+ * the body of the function.
+ * Note that code generated without source is not fully ECMA
+ * conformant.
+ */
+ public void setGeneratingSource(boolean generatingSource)
+ {
+ this.generatingSource = generatingSource;
+ }
+
+ /**
+ * @return true iff code will be generated with callbacks to enable
+ * instruction thresholds
+ */
+ public boolean isGenerateObserverCount() {
+ return generateObserverCount;
+ }
+
+ /**
+ * Turn on or off generation of code with callbacks to
+ * track the count of executed instructions.
+ * Currently only affects JVM byte code generation: this slows down the
+ * generated code, but code generated without the callbacks will not
+ * be counted toward instruction thresholds. Rhino's interpretive
+ * mode does instruction counting without inserting callbacks, so
+ * there is no requirement to compile code differently.
+ * @param generateObserverCount if true, generated code will contain
+ * calls to accumulate an estimate of the instructions executed.
+ */
+ public void setGenerateObserverCount(boolean generateObserverCount) {
+ this.generateObserverCount = generateObserverCount;
+ }
+
+ private ErrorReporter errorReporter;
+
+ private int languageVersion;
+ private boolean generateDebugInfo;
+ private boolean useDynamicScope;
+ private boolean reservedKeywordAsIdentifier;
+ private boolean allowMemberExprAsFunctionName;
+ private boolean xmlAvailable;
+ private int optimizationLevel;
+ private boolean generatingSource;
+ private boolean strictMode;
+ private boolean warningAsError;
+ private boolean generateObserverCount;
+ Set<String> activationNames;
+}
+
diff --git a/src/org/mozilla/javascript/ConstProperties.java b/src/org/mozilla/javascript/ConstProperties.java
new file mode 100644
index 0000000..860db79
--- /dev/null
+++ b/src/org/mozilla/javascript/ConstProperties.java
@@ -0,0 +1,109 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Bob Jervis
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+public interface ConstProperties {
+ /**
+ * Sets a named const property in this object.
+ * <p>
+ * The property is specified by a string name
+ * as defined for <code>Scriptable.get</code>.
+ * <p>
+ * The possible values that may be passed in are as defined for
+ * <code>Scriptable.get</code>. A class that implements this method may choose
+ * to ignore calls to set certain properties, in which case those
+ * properties are effectively read-only.<p>
+ * For properties defined in a prototype chain,
+ * use <code>putProperty</code> in ScriptableObject. <p>
+ * Note that if a property <i>a</i> is defined in the prototype <i>p</i>
+ * of an object <i>o</i>, then evaluating <code>o.a = 23</code> will cause
+ * <code>set</code> to be called on the prototype <i>p</i> with
+ * <i>o</i> as the <i>start</i> parameter.
+ * To preserve JavaScript semantics, it is the Scriptable
+ * object's responsibility to modify <i>o</i>. <p>
+ * This design allows properties to be defined in prototypes and implemented
+ * in terms of getters and setters of Java values without consuming slots
+ * in each instance.<p>
+ * <p>
+ * The values that may be set are limited to the following:
+ * <UL>
+ * <LI>java.lang.Boolean objects</LI>
+ * <LI>java.lang.String objects</LI>
+ * <LI>java.lang.Number objects</LI>
+ * <LI>org.mozilla.javascript.Scriptable objects</LI>
+ * <LI>null</LI>
+ * <LI>The value returned by Context.getUndefinedValue()</LI>
+ * </UL><p>
+ * Arbitrary Java objects may be wrapped in a Scriptable by first calling
+ * <code>Context.toObject</code>. This allows the property of a JavaScript
+ * object to contain an arbitrary Java object as a value.<p>
+ * Note that <code>has</code> will be called by the runtime first before
+ * <code>set</code> is called to determine in which object the
+ * property is defined.
+ * Note that this method is not expected to traverse the prototype chain,
+ * which is different from the ECMA [[Put]] operation.
+ * @param name the name of the property
+ * @param start the object whose property is being set
+ * @param value value to set the property to
+ * @see org.mozilla.javascript.Scriptable#has(String, Scriptable)
+ * @see org.mozilla.javascript.Scriptable#get(String, Scriptable)
+ * @see org.mozilla.javascript.ScriptableObject#putProperty(Scriptable, String, Object)
+ * @see org.mozilla.javascript.Context#toObject(Object, Scriptable)
+ */
+ public void putConst(String name, Scriptable start, Object value);
+
+ /**
+ * Reserves a definition spot for a const. This will set up a definition
+ * of the const property, but set its value to undefined. The semantics of
+ * the start parameter is the same as for putConst.
+ * @param name The name of the property.
+ * @param start The object whose property is being reserved.
+ */
+ public void defineConst(String name, Scriptable start);
+
+ /**
+ * Returns true if the named property is defined as a const on this object.
+ * @param name
+ * @return true if the named property is defined as a const, false
+ * otherwise.
+ */
+ public boolean isConst(String name);
+}
diff --git a/src/org/mozilla/javascript/Context.java b/src/org/mozilla/javascript/Context.java
new file mode 100644
index 0000000..51f060b
--- /dev/null
+++ b/src/org/mozilla/javascript/Context.java
@@ -0,0 +1,2639 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Bob Jervis
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Locale;
+
+import org.mozilla.javascript.debug.DebuggableScript;
+import org.mozilla.javascript.debug.Debugger;
+import org.mozilla.javascript.xml.XMLLib;
+
+/**
+ * This class represents the runtime context of an executing script.
+ *
+ * Before executing a script, an instance of Context must be created
+ * and associated with the thread that will be executing the script.
+ * The Context will be used to store information about the executing
+ * of the script such as the call stack. Contexts are associated with
+ * the current thread using the {@link #call(ContextAction)}
+ * or {@link #enter()} methods.<p>
+ *
+ * Different forms of script execution are supported. Scripts may be
+ * evaluated from the source directly, or first compiled and then later
+ * executed. Interactive execution is also supported.<p>
+ *
+ * Some aspects of script execution, such as type conversions and
+ * object creation, may be accessed directly through methods of
+ * Context.
+ *
+ * @see Scriptable
+ * @author Norris Boyd
+ * @author Brendan Eich
+ */
+
+public class Context
+{
+ /**
+ * Language versions.
+ *
+ * All integral values are reserved for future version numbers.
+ */
+
+ /**
+ * The unknown version.
+ */
+ public static final int VERSION_UNKNOWN = -1;
+
+ /**
+ * The default version.
+ */
+ public static final int VERSION_DEFAULT = 0;
+
+ /**
+ * JavaScript 1.0
+ */
+ public static final int VERSION_1_0 = 100;
+
+ /**
+ * JavaScript 1.1
+ */
+ public static final int VERSION_1_1 = 110;
+
+ /**
+ * JavaScript 1.2
+ */
+ public static final int VERSION_1_2 = 120;
+
+ /**
+ * JavaScript 1.3
+ */
+ public static final int VERSION_1_3 = 130;
+
+ /**
+ * JavaScript 1.4
+ */
+ public static final int VERSION_1_4 = 140;
+
+ /**
+ * JavaScript 1.5
+ */
+ public static final int VERSION_1_5 = 150;
+
+ /**
+ * JavaScript 1.6
+ */
+ public static final int VERSION_1_6 = 160;
+
+ /**
+ * JavaScript 1.7
+ */
+ public static final int VERSION_1_7 = 170;
+
+ /**
+ * Controls behaviour of <tt>Date.prototype.getYear()</tt>.
+ * If <tt>hasFeature(FEATURE_NON_ECMA_GET_YEAR)</tt> returns true,
+ * Date.prototype.getYear subtructs 1900 only if 1900 <= date < 2000.
+ * The default behavior of {@link #hasFeature(int)} is always to subtruct
+ * 1900 as rquired by ECMAScript B.2.4.
+ */
+ public static final int FEATURE_NON_ECMA_GET_YEAR = 1;
+
+ /**
+ * Control if member expression as function name extension is available.
+ * If <tt>hasFeature(FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)</tt> returns
+ * true, allow <tt>function memberExpression(args) { body }</tt> to be
+ * syntax sugar for <tt>memberExpression = function(args) { body }</tt>,
+ * when memberExpression is not a simple identifier.
+ * See ECMAScript-262, section 11.2 for definition of memberExpression.
+ * By default {@link #hasFeature(int)} returns false.
+ */
+ public static final int FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME = 2;
+
+ /**
+ * Control if reserved keywords are treated as identifiers.
+ * If <tt>hasFeature(RESERVED_KEYWORD_AS_IDENTIFIER)</tt> returns true,
+ * treat future reserved keyword (see Ecma-262, section 7.5.3) as ordinary
+ * identifiers but warn about this usage.
+ *
+ * By default {@link #hasFeature(int)} returns false.
+ */
+ public static final int FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER = 3;
+
+ /**
+ * Control if <tt>toString()</tt> should returns the same result
+ * as <tt>toSource()</tt> when applied to objects and arrays.
+ * If <tt>hasFeature(FEATURE_TO_STRING_AS_SOURCE)</tt> returns true,
+ * calling <tt>toString()</tt> on JS objects gives the same result as
+ * calling <tt>toSource()</tt>. That is it returns JS source with code
+ * to create an object with all enumeratable fields of the original object
+ * instead of printing <tt>[object <i>result of
+ * {@link Scriptable#getClassName()}</i>]</tt>.
+ * <p>
+ * By default {@link #hasFeature(int)} returns true only if
+ * the current JS version is set to {@link #VERSION_1_2}.
+ */
+ public static final int FEATURE_TO_STRING_AS_SOURCE = 4;
+
+ /**
+ * Control if properties <tt>__proto__</tt> and <tt>__parent__</tt>
+ * are treated specially.
+ * If <tt>hasFeature(FEATURE_PARENT_PROTO_PROPERTIES)</tt> returns true,
+ * treat <tt>__parent__</tt> and <tt>__proto__</tt> as special properties.
+ * <p>
+ * The properties allow to query and set scope and prototype chains for the
+ * objects. The special meaning of the properties is available
+ * only when they are used as the right hand side of the dot operator.
+ * For example, while <tt>x.__proto__ = y</tt> changes the prototype
+ * chain of the object <tt>x</tt> to point to <tt>y</tt>,
+ * <tt>x["__proto__"] = y</tt> simply assigns a new value to the property
+ * <tt>__proto__</tt> in <tt>x</tt> even when the feature is on.
+ *
+ * By default {@link #hasFeature(int)} returns true.
+ */
+ public static final int FEATURE_PARENT_PROTO_PROPERTIES = 5;
+
+ /**
+ * @deprecated In previous releases, this name was given to
+ * FEATURE_PARENT_PROTO_PROPERTIES.
+ */
+ public static final int FEATURE_PARENT_PROTO_PROPRTIES = 5;
+
+ /**
+ * Control if support for E4X(ECMAScript for XML) extension is available.
+ * If hasFeature(FEATURE_E4X) returns true, the XML syntax is available.
+ * <p>
+ * By default {@link #hasFeature(int)} returns true if
+ * the current JS version is set to {@link #VERSION_DEFAULT}
+ * or is at least {@link #VERSION_1_6}.
+ * @since 1.6 Release 1
+ */
+ public static final int FEATURE_E4X = 6;
+
+ /**
+ * Control if dynamic scope should be used for name access.
+ * If hasFeature(FEATURE_DYNAMIC_SCOPE) returns true, then the name lookup
+ * during name resolution will use the top scope of the script or function
+ * which is at the top of JS execution stack instead of the top scope of the
+ * script or function from the current stack frame if the top scope of
+ * the top stack frame contains the top scope of the current stack frame
+ * on its prototype chain.
+ * <p>
+ * This is useful to define shared scope containing functions that can
+ * be called from scripts and functions using private scopes.
+ * <p>
+ * By default {@link #hasFeature(int)} returns false.
+ * @since 1.6 Release 1
+ */
+ public static final int FEATURE_DYNAMIC_SCOPE = 7;
+
+ /**
+ * Control if strict variable mode is enabled.
+ * When the feature is on Rhino reports runtime errors if assignment
+ * to a global variable that does not exist is executed. When the feature
+ * is off such assignments create a new variable in the global scope as
+ * required by ECMA 262.
+ * <p>
+ * By default {@link #hasFeature(int)} returns false.
+ * @since 1.6 Release 1
+ */
+ public static final int FEATURE_STRICT_VARS = 8;
+
+ /**
+ * Control if strict eval mode is enabled.
+ * When the feature is on Rhino reports runtime errors if non-string
+ * argument is passed to the eval function. When the feature is off
+ * eval simply return non-string argument as is without performing any
+ * evaluation as required by ECMA 262.
+ * <p>
+ * By default {@link #hasFeature(int)} returns false.
+ * @since 1.6 Release 1
+ */
+ public static final int FEATURE_STRICT_EVAL = 9;
+
+ /**
+ * When the feature is on Rhino will add a "fileName" and "lineNumber"
+ * properties to Error objects automatically. When the feature is off, you
+ * have to explicitly pass them as the second and third argument to the
+ * Error constructor. Note that neither behavior is fully ECMA 262
+ * compliant (as 262 doesn't specify a three-arg constructor), but keeping
+ * the feature off results in Error objects that don't have
+ * additional non-ECMA properties when constructed using the ECMA-defined
+ * single-arg constructor and is thus desirable if a stricter ECMA
+ * compliance is desired, specifically adherence to the point 15.11.5. of
+ * the standard.
+ * <p>
+ * By default {@link #hasFeature(int)} returns false.
+ * @since 1.6 Release 6
+ */
+ public static final int FEATURE_LOCATION_INFORMATION_IN_ERROR = 10;
+
+ /**
+ * Controls whether JS 1.5 'strict mode' is enabled.
+ * When the feature is on, Rhino reports more than a dozen different
+ * warnings. When the feature is off, these warnings are not generated.
+ * FEATURE_STRICT_MODE implies FEATURE_STRICT_VARS and FEATURE_STRICT_EVAL.
+ * <p>
+ * By default {@link #hasFeature(int)} returns false.
+ * @since 1.6 Release 6
+ */
+ public static final int FEATURE_STRICT_MODE = 11;
+
+ /**
+ * Controls whether a warning should be treated as an error.
+ * @since 1.6 Release 6
+ */
+ public static final int FEATURE_WARNING_AS_ERROR = 12;
+
+ /**
+ * Enables enhanced access to Java.
+ * Specifically, controls whether private and protected members can be
+ * accessed, and whether scripts can catch all Java exceptions.
+ * <p>
+ * Note that this feature should only be enabled for trusted scripts.
+ * <p>
+ * By default {@link #hasFeature(int)} returns false.
+ * @since 1.7 Release 1
+ */
+ public static final int FEATURE_ENHANCED_JAVA_ACCESS = 13;
+
+
+ public static final String languageVersionProperty = "language version";
+ public static final String errorReporterProperty = "error reporter";
+
+ /**
+ * Convenient value to use as zero-length array of objects.
+ */
+ public static final Object[] emptyArgs = ScriptRuntime.emptyArgs;
+
+ /**
+ * Creates a new Context. The context will be associated with the {@link
+ * ContextFactory#getGlobal() global context factory}.
+ *
+ * Note that the Context must be associated with a thread before
+ * it can be used to execute a script.
+ * @deprecated this constructor is deprecated because it creates a
+ * dependency on a static singleton context factory. Use
+ * {@link ContextFactory#enter()} or
+ * {@link ContextFactory#call(ContextAction)} instead. If you subclass
+ * this class, consider using {@link #Context(ContextFactory)} constructor
+ * instead in the subclasses' constructors.
+ */
+ public Context()
+ {
+ this(ContextFactory.getGlobal());
+ }
+
+ /**
+ * Creates a new context. Provided as a preferred super constructor for
+ * subclasses in place of the deprecated default public constructor.
+ * @param factory the context factory associated with this context (most
+ * likely, the one that created the context). Can not be null. The context
+ * features are inherited from the factory, and the context will also
+ * otherwise use its factory's services.
+ * @throws IllegalArgumentException if factory parameter is null.
+ */
+ protected Context(ContextFactory factory)
+ {
+ if(factory == null) {
+ throw new IllegalArgumentException("factory == null");
+ }
+ this.factory = factory;
+ setLanguageVersion(VERSION_DEFAULT);
+ optimizationLevel = codegenClass != null ? 0 : -1;
+ maximumInterpreterStackDepth = Integer.MAX_VALUE;
+ }
+
+ /**
+ * Get the current Context.
+ *
+ * The current Context is per-thread; this method looks up
+ * the Context associated with the current thread. <p>
+ *
+ * @return the Context associated with the current thread, or
+ * null if no context is associated with the current
+ * thread.
+ * @see ContextFactory#enterContext()
+ * @see ContextFactory#call(ContextAction)
+ */
+ public static Context getCurrentContext()
+ {
+ Object helper = VMBridge.instance.getThreadContextHelper();
+ return VMBridge.instance.getContext(helper);
+ }
+
+ /**
+ * Same as calling {@link ContextFactory#enterContext()} on the global
+ * ContextFactory instance.
+ * @return a Context associated with the current thread
+ * @see #getCurrentContext()
+ * @see #exit()
+ * @see #call(ContextAction)
+ */
+ public static Context enter()
+ {
+ return enter(null);
+ }
+
+ /**
+ * Get a Context associated with the current thread, using
+ * the given Context if need be.
+ * <p>
+ * The same as <code>enter()</code> except that <code>cx</code>
+ * is associated with the current thread and returned if
+ * the current thread has no associated context and <code>cx</code>
+ * is not associated with any other thread.
+ * @param cx a Context to associate with the thread if possible
+ * @return a Context associated with the current thread
+ * @deprecated use {@link ContextFactory#enterContext(Context)} instead as
+ * this method relies on usage of a static singleton "global" ContextFactory.
+ * @see ContextFactory#enterContext(Context)
+ * @see ContextFactory#call(ContextAction)
+ */
+ public static Context enter(Context cx)
+ {
+ return enter(cx, ContextFactory.getGlobal());
+ }
+
+ static final Context enter(Context cx, ContextFactory factory)
+ {
+ Object helper = VMBridge.instance.getThreadContextHelper();
+ Context old = VMBridge.instance.getContext(helper);
+ if (old != null) {
+ cx = old;
+ } else {
+ if (cx == null) {
+ cx = factory.makeContext();
+ if (cx.enterCount != 0) {
+ throw new IllegalStateException("factory.makeContext() returned Context instance already associated with some thread");
+ }
+ factory.onContextCreated(cx);
+ if (factory.isSealed() && !cx.isSealed()) {
+ cx.seal(null);
+ }
+ } else {
+ if (cx.enterCount != 0) {
+ throw new IllegalStateException("can not use Context instance already associated with some thread");
+ }
+ }
+ VMBridge.instance.setContext(helper, cx);
+ }
+ ++cx.enterCount;
+ return cx;
+ }
+
+ /**
+ * Exit a block of code requiring a Context.
+ *
+ * Calling <code>exit()</code> will remove the association between
+ * the current thread and a Context if the prior call to
+ * {@link ContextFactory#enterContext()} on this thread newly associated a
+ * Context with this thread. Once the current thread no longer has an
+ * associated Context, it cannot be used to execute JavaScript until it is
+ * again associated with a Context.
+ * @see ContextFactory#enterContext()
+ */
+ public static void exit()
+ {
+ Object helper = VMBridge.instance.getThreadContextHelper();
+ Context cx = VMBridge.instance.getContext(helper);
+ if (cx == null) {
+ throw new IllegalStateException(
+ "Calling Context.exit without previous Context.enter");
+ }
+ if (cx.enterCount < 1) Kit.codeBug();
+ if (--cx.enterCount == 0) {
+ VMBridge.instance.setContext(helper, null);
+ cx.factory.onContextReleased(cx);
+ }
+ }
+
+ /**
+ * Call {@link ContextAction#run(Context cx)}
+ * using the Context instance associated with the current thread.
+ * If no Context is associated with the thread, then
+ * <tt>ContextFactory.getGlobal().makeContext()</tt> will be called to
+ * construct new Context instance. The instance will be temporary
+ * associated with the thread during call to
+ * {@link ContextAction#run(Context)}.
+ * @deprecated use {@link ContextFactory#call(ContextAction)} instead as
+ * this method relies on usage of a static singleton "global"
+ * ContextFactory.
+ * @return The result of {@link ContextAction#run(Context)}.
+ */
+ public static Object call(ContextAction action)
+ {
+ return call(ContextFactory.getGlobal(), action);
+ }
+
+ /**
+ * Call {@link
+ * Callable#call(Context cx, Scriptable scope, Scriptable thisObj,
+ * Object[] args)}
+ * using the Context instance associated with the current thread.
+ * If no Context is associated with the thread, then
+ * {@link ContextFactory#makeContext()} will be called to construct
+ * new Context instance. The instance will be temporary associated
+ * with the thread during call to {@link ContextAction#run(Context)}.
+ * <p>
+ * It is allowed but not advisable to use null for <tt>factory</tt>
+ * argument in which case the global static singleton ContextFactory
+ * instance will be used to create new context instances.
+ * @see ContextFactory#call(ContextAction)
+ */
+ public static Object call(ContextFactory factory, final Callable callable,
+ final Scriptable scope, final Scriptable thisObj,
+ final Object[] args)
+ {
+ if(factory == null) {
+ factory = ContextFactory.getGlobal();
+ }
+ return call(factory, new ContextAction() {
+ public Object run(Context cx) {
+ return callable.call(cx, scope, thisObj, args);
+ }
+ });
+ }
+
+ /**
+ * The method implements {@link ContextFactory#call(ContextAction)} logic.
+ */
+ static Object call(ContextFactory factory, ContextAction action) {
+ Context cx = enter(null, factory);
+ try {
+ return action.run(cx);
+ }
+ finally {
+ exit();
+ }
+ }
+
+ /**
+ * @deprecated
+ * @see ContextFactory#addListener(ContextFactory.Listener)
+ * @see ContextFactory#getGlobal()
+ */
+ public static void addContextListener(ContextListener listener)
+ {
+ // Special workaround for the debugger
+ String DBG = "org.mozilla.javascript.tools.debugger.Main";
+ if (DBG.equals(listener.getClass().getName())) {
+ Class<?> cl = listener.getClass();
+ Class<?> factoryClass = Kit.classOrNull(
+ "org.mozilla.javascript.ContextFactory");
+ Class<?>[] sig = { factoryClass };
+ Object[] args = { ContextFactory.getGlobal() };
+ try {
+ Method m = cl.getMethod("attachTo", sig);
+ m.invoke(listener, args);
+ } catch (Exception ex) {
+ RuntimeException rex = new RuntimeException();
+ Kit.initCause(rex, ex);
+ throw rex;
+ }
+ return;
+ }
+
+ ContextFactory.getGlobal().addListener(listener);
+ }
+
+ /**
+ * @deprecated
+ * @see ContextFactory#removeListener(ContextFactory.Listener)
+ * @see ContextFactory#getGlobal()
+ */
+ public static void removeContextListener(ContextListener listener)
+ {
+ ContextFactory.getGlobal().addListener(listener);
+ }
+
+ /**
+ * Return {@link ContextFactory} instance used to create this Context.
+ */
+ public final ContextFactory getFactory()
+ {
+ return factory;
+ }
+
+ /**
+ * Checks if this is a sealed Context. A sealed Context instance does not
+ * allow to modify any of its properties and will throw an exception
+ * on any such attempt.
+ * @see #seal(Object sealKey)
+ */
+ public final boolean isSealed()
+ {
+ return sealed;
+ }
+
+ /**
+ * Seal this Context object so any attempt to modify any of its properties
+ * including calling {@link #enter()} and {@link #exit()} methods will
+ * throw an exception.
+ * <p>
+ * If <tt>sealKey</tt> is not null, calling
+ * {@link #unseal(Object sealKey)} with the same key unseals
+ * the object. If <tt>sealKey</tt> is null, unsealing is no longer possible.
+ *
+ * @see #isSealed()
+ * @see #unseal(Object)
+ */
+ public final void seal(Object sealKey)
+ {
+ if (sealed) onSealedMutation();
+ sealed = true;
+ this.sealKey = sealKey;
+ }
+
+ /**
+ * Unseal previously sealed Context object.
+ * The <tt>sealKey</tt> argument should not be null and should match
+ * <tt>sealKey</tt> suplied with the last call to
+ * {@link #seal(Object)} or an exception will be thrown.
+ *
+ * @see #isSealed()
+ * @see #seal(Object sealKey)
+ */
+ public final void unseal(Object sealKey)
+ {
+ if (sealKey == null) throw new IllegalArgumentException();
+ if (this.sealKey != sealKey) throw new IllegalArgumentException();
+ if (!sealed) throw new IllegalStateException();
+ sealed = false;
+ this.sealKey = null;
+ }
+
+ static void onSealedMutation()
+ {
+ throw new IllegalStateException();
+ }
+
+ /**
+ * Get the current language version.
+ * <p>
+ * The language version number affects JavaScript semantics as detailed
+ * in the overview documentation.
+ *
+ * @return an integer that is one of VERSION_1_0, VERSION_1_1, etc.
+ */
+ public final int getLanguageVersion()
+ {
+ return version;
+ }
+
+ /**
+ * Set the language version.
+ *
+ * <p>
+ * Setting the language version will affect functions and scripts compiled
+ * subsequently. See the overview documentation for version-specific
+ * behavior.
+ *
+ * @param version the version as specified by VERSION_1_0, VERSION_1_1, etc.
+ */
+ public void setLanguageVersion(int version)
+ {
+ if (sealed) onSealedMutation();
+ checkLanguageVersion(version);
+ Object listeners = propertyListeners;
+ if (listeners != null && version != this.version) {
+ firePropertyChangeImpl(listeners, languageVersionProperty,
+ new Integer(this.version),
+ new Integer(version));
+ }
+ this.version = version;
+ }
+
+ public static boolean isValidLanguageVersion(int version)
+ {
+ switch (version) {
+ case VERSION_DEFAULT:
+ case VERSION_1_0:
+ case VERSION_1_1:
+ case VERSION_1_2:
+ case VERSION_1_3:
+ case VERSION_1_4:
+ case VERSION_1_5:
+ case VERSION_1_6:
+ case VERSION_1_7:
+ return true;
+ }
+ return false;
+ }
+
+ public static void checkLanguageVersion(int version)
+ {
+ if (isValidLanguageVersion(version)) {
+ return;
+ }
+ throw new IllegalArgumentException("Bad language version: "+version);
+ }
+
+ /**
+ * Get the implementation version.
+ *
+ * <p>
+ * The implementation version is of the form
+ * <pre>
+ * "<i>name langVer</i> <code>release</code> <i>relNum date</i>"
+ * </pre>
+ * where <i>name</i> is the name of the product, <i>langVer</i> is
+ * the language version, <i>relNum</i> is the release number, and
+ * <i>date</i> is the release date for that specific
+ * release in the form "yyyy mm dd".
+ *
+ * @return a string that encodes the product, language version, release
+ * number, and date.
+ */
+ public final String getImplementationVersion()
+ {
+ // XXX Probably it would be better to embed this directly into source
+ // with special build preprocessing but that would require some ant
+ // tweaking and then replacing token in resource files was simpler
+ if (implementationVersion == null) {
+ implementationVersion
+ = ScriptRuntime.getMessage0("implementation.version");
+ }
+ return implementationVersion;
+ }
+
+ /**
+ * Get the current error reporter.
+ *
+ * @see org.mozilla.javascript.ErrorReporter
+ */
+ public final ErrorReporter getErrorReporter()
+ {
+ if (errorReporter == null) {
+ return DefaultErrorReporter.instance;
+ }
+ return errorReporter;
+ }
+
+ /**
+ * Change the current error reporter.
+ *
+ * @return the previous error reporter
+ * @see org.mozilla.javascript.ErrorReporter
+ */
+ public final ErrorReporter setErrorReporter(ErrorReporter reporter)
+ {
+ if (sealed) onSealedMutation();
+ if (reporter == null) throw new IllegalArgumentException();
+ ErrorReporter old = getErrorReporter();
+ if (reporter == old) {
+ return old;
+ }
+ Object listeners = propertyListeners;
+ if (listeners != null) {
+ firePropertyChangeImpl(listeners, errorReporterProperty,
+ old, reporter);
+ }
+ this.errorReporter = reporter;
+ return old;
+ }
+
+ /**
+ * Get the current locale. Returns the default locale if none has
+ * been set.
+ *
+ * @see java.util.Locale
+ */
+
+ public final Locale getLocale()
+ {
+ if (locale == null)
+ locale = Locale.getDefault();
+ return locale;
+ }
+
+ /**
+ * Set the current locale.
+ *
+ * @see java.util.Locale
+ */
+ public final Locale setLocale(Locale loc)
+ {
+ if (sealed) onSealedMutation();
+ Locale result = locale;
+ locale = loc;
+ return result;
+ }
+
+ /**
+ * Register an object to receive notifications when a bound property
+ * has changed
+ * @see java.beans.PropertyChangeEvent
+ * @see #removePropertyChangeListener(java.beans.PropertyChangeListener)
+ * @param l the listener
+ */
+ public final void addPropertyChangeListener(PropertyChangeListener l)
+ {
+ if (sealed) onSealedMutation();
+ propertyListeners = Kit.addListener(propertyListeners, l);
+ }
+
+ /**
+ * Remove an object from the list of objects registered to receive
+ * notification of changes to a bounded property
+ * @see java.beans.PropertyChangeEvent
+ * @see #addPropertyChangeListener(java.beans.PropertyChangeListener)
+ * @param l the listener
+ */
+ public final void removePropertyChangeListener(PropertyChangeListener l)
+ {
+ if (sealed) onSealedMutation();
+ propertyListeners = Kit.removeListener(propertyListeners, l);
+ }
+
+ /**
+ * Notify any registered listeners that a bounded property has changed
+ * @see #addPropertyChangeListener(java.beans.PropertyChangeListener)
+ * @see #removePropertyChangeListener(java.beans.PropertyChangeListener)
+ * @see java.beans.PropertyChangeListener
+ * @see java.beans.PropertyChangeEvent
+ * @param property the bound property
+ * @param oldValue the old value
+ * @param newValue the new value
+ */
+ final void firePropertyChange(String property, Object oldValue,
+ Object newValue)
+ {
+ Object listeners = propertyListeners;
+ if (listeners != null) {
+ firePropertyChangeImpl(listeners, property, oldValue, newValue);
+ }
+ }
+
+ private void firePropertyChangeImpl(Object listeners, String property,
+ Object oldValue, Object newValue)
+ {
+ for (int i = 0; ; ++i) {
+ Object l = Kit.getListener(listeners, i);
+ if (l == null)
+ break;
+ if (l instanceof PropertyChangeListener) {
+ PropertyChangeListener pcl = (PropertyChangeListener)l;
+ pcl.propertyChange(new PropertyChangeEvent(
+ this, property, oldValue, newValue));
+ }
+ }
+ }
+
+ /**
+ * Report a warning using the error reporter for the current thread.
+ *
+ * @param message the warning message to report
+ * @param sourceName a string describing the source, such as a filename
+ * @param lineno the starting line number
+ * @param lineSource the text of the line (may be null)
+ * @param lineOffset the offset into lineSource where problem was detected
+ * @see org.mozilla.javascript.ErrorReporter
+ */
+ public static void reportWarning(String message, String sourceName,
+ int lineno, String lineSource,
+ int lineOffset)
+ {
+ Context cx = Context.getContext();
+ if (cx.hasFeature(FEATURE_WARNING_AS_ERROR))
+ reportError(message, sourceName, lineno, lineSource, lineOffset);
+ else
+ cx.getErrorReporter().warning(message, sourceName, lineno,
+ lineSource, lineOffset);
+ }
+
+ /**
+ * Report a warning using the error reporter for the current thread.
+ *
+ * @param message the warning message to report
+ * @see org.mozilla.javascript.ErrorReporter
+ */
+ public static void reportWarning(String message)
+ {
+ int[] linep = { 0 };
+ String filename = getSourcePositionFromStack(linep);
+ Context.reportWarning(message, filename, linep[0], null, 0);
+ }
+
+ public static void reportWarning(String message, Throwable t)
+ {
+ int[] linep = { 0 };
+ String filename = getSourcePositionFromStack(linep);
+ Writer sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ pw.println(message);
+ t.printStackTrace(pw);
+ pw.flush();
+ Context.reportWarning(sw.toString(), filename, linep[0], null, 0);
+ }
+
+ /**
+ * Report an error using the error reporter for the current thread.
+ *
+ * @param message the error message to report
+ * @param sourceName a string describing the source, such as a filename
+ * @param lineno the starting line number
+ * @param lineSource the text of the line (may be null)
+ * @param lineOffset the offset into lineSource where problem was detected
+ * @see org.mozilla.javascript.ErrorReporter
+ */
+ public static void reportError(String message, String sourceName,
+ int lineno, String lineSource,
+ int lineOffset)
+ {
+ Context cx = getCurrentContext();
+ if (cx != null) {
+ cx.getErrorReporter().error(message, sourceName, lineno,
+ lineSource, lineOffset);
+ } else {
+ throw new EvaluatorException(message, sourceName, lineno,
+ lineSource, lineOffset);
+ }
+ }
+
+ /**
+ * Report an error using the error reporter for the current thread.
+ *
+ * @param message the error message to report
+ * @see org.mozilla.javascript.ErrorReporter
+ */
+ public static void reportError(String message)
+ {
+ int[] linep = { 0 };
+ String filename = getSourcePositionFromStack(linep);
+ Context.reportError(message, filename, linep[0], null, 0);
+ }
+
+ /**
+ * Report a runtime error using the error reporter for the current thread.
+ *
+ * @param message the error message to report
+ * @param sourceName a string describing the source, such as a filename
+ * @param lineno the starting line number
+ * @param lineSource the text of the line (may be null)
+ * @param lineOffset the offset into lineSource where problem was detected
+ * @return a runtime exception that will be thrown to terminate the
+ * execution of the script
+ * @see org.mozilla.javascript.ErrorReporter
+ */
+ public static EvaluatorException reportRuntimeError(String message,
+ String sourceName,
+ int lineno,
+ String lineSource,
+ int lineOffset)
+ {
+ Context cx = getCurrentContext();
+ if (cx != null) {
+ return cx.getErrorReporter().
+ runtimeError(message, sourceName, lineno,
+ lineSource, lineOffset);
+ } else {
+ throw new EvaluatorException(message, sourceName, lineno,
+ lineSource, lineOffset);
+ }
+ }
+
+ static EvaluatorException reportRuntimeError0(String messageId)
+ {
+ String msg = ScriptRuntime.getMessage0(messageId);
+ return reportRuntimeError(msg);
+ }
+
+ static EvaluatorException reportRuntimeError1(String messageId,
+ Object arg1)
+ {
+ String msg = ScriptRuntime.getMessage1(messageId, arg1);
+ return reportRuntimeError(msg);
+ }
+
+ static EvaluatorException reportRuntimeError2(String messageId,
+ Object arg1, Object arg2)
+ {
+ String msg = ScriptRuntime.getMessage2(messageId, arg1, arg2);
+ return reportRuntimeError(msg);
+ }
+
+ static EvaluatorException reportRuntimeError3(String messageId,
+ Object arg1, Object arg2,
+ Object arg3)
+ {
+ String msg = ScriptRuntime.getMessage3(messageId, arg1, arg2, arg3);
+ return reportRuntimeError(msg);
+ }
+
+ static EvaluatorException reportRuntimeError4(String messageId,
+ Object arg1, Object arg2,
+ Object arg3, Object arg4)
+ {
+ String msg
+ = ScriptRuntime.getMessage4(messageId, arg1, arg2, arg3, arg4);
+ return reportRuntimeError(msg);
+ }
+
+ /**
+ * Report a runtime error using the error reporter for the current thread.
+ *
+ * @param message the error message to report
+ * @see org.mozilla.javascript.ErrorReporter
+ */
+ public static EvaluatorException reportRuntimeError(String message)
+ {
+ int[] linep = { 0 };
+ String filename = getSourcePositionFromStack(linep);
+ return Context.reportRuntimeError(message, filename, linep[0], null, 0);
+ }
+
+ /**
+ * Initialize the standard objects.
+ *
+ * Creates instances of the standard objects and their constructors
+ * (Object, String, Number, Date, etc.), setting up 'scope' to act
+ * as a global object as in ECMA 15.1.<p>
+ *
+ * This method must be called to initialize a scope before scripts
+ * can be evaluated in that scope.<p>
+ *
+ * This method does not affect the Context it is called upon.
+ *
+ * @return the initialized scope
+ */
+ public final ScriptableObject initStandardObjects()
+ {
+ return initStandardObjects(null, false);
+ }
+
+ /**
+ * Initialize the standard objects.
+ *
+ * Creates instances of the standard objects and their constructors
+ * (Object, String, Number, Date, etc.), setting up 'scope' to act
+ * as a global object as in ECMA 15.1.<p>
+ *
+ * This method must be called to initialize a scope before scripts
+ * can be evaluated in that scope.<p>
+ *
+ * This method does not affect the Context it is called upon.
+ *
+ * @param scope the scope to initialize, or null, in which case a new
+ * object will be created to serve as the scope
+ * @return the initialized scope. The method returns the value of the scope
+ * argument if it is not null or newly allocated scope object which
+ * is an instance {@link ScriptableObject}.
+ */
+ public final Scriptable initStandardObjects(ScriptableObject scope)
+ {
+ return initStandardObjects(scope, false);
+ }
+
+ /**
+ * Initialize the standard objects.
+ *
+ * Creates instances of the standard objects and their constructors
+ * (Object, String, Number, Date, etc.), setting up 'scope' to act
+ * as a global object as in ECMA 15.1.<p>
+ *
+ * This method must be called to initialize a scope before scripts
+ * can be evaluated in that scope.<p>
+ *
+ * This method does not affect the Context it is called upon.<p>
+ *
+ * This form of the method also allows for creating "sealed" standard
+ * objects. An object that is sealed cannot have properties added, changed,
+ * or removed. This is useful to create a "superglobal" that can be shared
+ * among several top-level objects. Note that sealing is not allowed in
+ * the current ECMA/ISO language specification, but is likely for
+ * the next version.
+ *
+ * @param scope the scope to initialize, or null, in which case a new
+ * object will be created to serve as the scope
+ * @param sealed whether or not to create sealed standard objects that
+ * cannot be modified.
+ * @return the initialized scope. The method returns the value of the scope
+ * argument if it is not null or newly allocated scope object.
+ * @since 1.4R3
+ */
+ public ScriptableObject initStandardObjects(ScriptableObject scope,
+ boolean sealed)
+ {
+ return ScriptRuntime.initStandardObjects(this, scope, sealed);
+ }
+
+ /**
+ * Get the singleton object that represents the JavaScript Undefined value.
+ */
+ public static Object getUndefinedValue()
+ {
+ return Undefined.instance;
+ }
+
+ /**
+ * Evaluate a JavaScript source string.
+ *
+ * The provided source name and line number are used for error messages
+ * and for producing debug information.
+ *
+ * @param scope the scope to execute in
+ * @param source the JavaScript source
+ * @param sourceName a string describing the source, such as a filename
+ * @param lineno the starting line number
+ * @param securityDomain an arbitrary object that specifies security
+ * information about the origin or owner of the script. For
+ * implementations that don't care about security, this value
+ * may be null.
+ * @return the result of evaluating the string
+ * @see org.mozilla.javascript.SecurityController
+ */
+ public final Object evaluateString(Scriptable scope, String source,
+ String sourceName, int lineno,
+ Object securityDomain)
+ {
+ Script script = compileString(source, sourceName, lineno,
+ securityDomain);
+ if (script != null) {
+ return script.exec(this, scope);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Evaluate a reader as JavaScript source.
+ *
+ * All characters of the reader are consumed.
+ *
+ * @param scope the scope to execute in
+ * @param in the Reader to get JavaScript source from
+ * @param sourceName a string describing the source, such as a filename
+ * @param lineno the starting line number
+ * @param securityDomain an arbitrary object that specifies security
+ * information about the origin or owner of the script. For
+ * implementations that don't care about security, this value
+ * may be null.
+ * @return the result of evaluating the source
+ *
+ * @exception IOException if an IOException was generated by the Reader
+ */
+ public final Object evaluateReader(Scriptable scope, Reader in,
+ String sourceName, int lineno,
+ Object securityDomain)
+ throws IOException
+ {
+ Script script = compileReader(scope, in, sourceName, lineno,
+ securityDomain);
+ if (script != null) {
+ return script.exec(this, scope);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Execute script that may pause execution by capturing a continuation.
+ * Caller must be prepared to catch a ContinuationPending exception
+ * and resume execution by calling
+ * {@link #resumeContinuation(Object, Scriptable, Object)}.
+ * @param script The script to execute. Script must have been compiled
+ * with interpreted mode (optimization level -1)
+ * @param scope The scope to execute the script against
+ * @throws ContinuationPending if the script calls a function that results
+ * in a call to {@link #captureContinuation()}
+ * @since 1.7 Release 2
+ */
+ public Object executeScriptWithContinuations(Script script,
+ Scriptable scope)
+ throws ContinuationPending
+ {
+ if (!(script instanceof InterpretedFunction) ||
+ !((InterpretedFunction)script).isScript())
+ {
+ // Can only be applied to scripts
+ throw new IllegalArgumentException("Script argument was not" +
+ " a script or was not created by interpreted mode ");
+ }
+ return callFunctionWithContinuations((InterpretedFunction) script,
+ scope, ScriptRuntime.emptyArgs);
+ }
+
+ /**
+ * Call function that may pause execution by capturing a continuation.
+ * Caller must be prepared to catch a ContinuationPending exception
+ * and resume execution by calling
+ * {@link #resumeContinuation(Object, Scriptable, Object)}.
+ * @param function The function to call. The function must have been
+ * compiled with interpreted mode (optimization level -1)
+ * @param scope The scope to execute the script against
+ * @param args The arguments for the function
+ * @throws ContinuationPending if the script calls a function that results
+ * in a call to {@link #captureContinuation()}
+ * @since 1.7 Release 2
+ */
+ public Object callFunctionWithContinuations(Callable function,
+ Scriptable scope, Object[] args)
+ throws ContinuationPending
+ {
+ if (!(function instanceof InterpretedFunction)) {
+ // Can only be applied to scripts
+ throw new IllegalArgumentException("Function argument was not" +
+ " created by interpreted mode ");
+ }
+ if (ScriptRuntime.hasTopCall(this)) {
+ throw new IllegalStateException("Cannot have any pending top " +
+ "calls when executing a script with continuations");
+ }
+ // Annotate so we can check later to ensure no java code in
+ // intervening frames
+ isContinuationsTopCall = true;
+ return ScriptRuntime.doTopCall(function, this, scope, scope, args);
+ }
+
+ /**
+ * Capture a continuation from the current execution. The execution must
+ * have been started via a call to
+ * {@link #executeScriptWithContinuations(Script, Scriptable)} or
+ * {@link #callFunctionWithContinuations(Callable, Scriptable, Object[])}.
+ * This implies that the code calling
+ * this method must have been called as a function from the
+ * JavaScript script. Also, there cannot be any non-JavaScript code
+ * between the JavaScript frames (e.g., a call to eval()). The
+ * ContinuationPending exception returned must be thrown.
+ * @return A ContinuationPending exception that must be thrown
+ * @since 1.7 Release 2
+ */
+ public ContinuationPending captureContinuation() {
+ return new ContinuationPending(
+ Interpreter.captureContinuation(this));
+ }
+
+ /**
+ * Restarts execution of the JavaScript suspended at the call
+ * to {@link #captureContinuation()}. Execution of the code will resume
+ * with the functionResult as the result of the call that captured the
+ * continuation.
+ * Execution of the script will either conclude normally and the
+ * result returned, another continuation will be captured and
+ * thrown, or the script will terminate abnormally and throw an exception.
+ * @param continuation The value returned by
+ * {@link ContinuationPending#getContinuation()}
+ * @param functionResult This value will appear to the code being resumed
+ * as the result of the function that captured the continuation
+ * @throws ContinuationPending if another continuation is captured before
+ * the code terminates
+ * @since 1.7 Release 2
+ */
+ public Object resumeContinuation(Object continuation,
+ Scriptable scope, Object functionResult)
+ throws ContinuationPending
+ {
+ Object[] args = { functionResult };
+ return Interpreter.restartContinuation(
+ (org.mozilla.javascript.NativeContinuation) continuation,
+ this, scope, args);
+ }
+
+ /**
+ * Check whether a string is ready to be compiled.
+ * <p>
+ * stringIsCompilableUnit is intended to support interactive compilation of
+ * JavaScript. If compiling the string would result in an error
+ * that might be fixed by appending more source, this method
+ * returns false. In every other case, it returns true.
+ * <p>
+ * Interactive shells may accumulate source lines, using this
+ * method after each new line is appended to check whether the
+ * statement being entered is complete.
+ *
+ * @param source the source buffer to check
+ * @return whether the source is ready for compilation
+ * @since 1.4 Release 2
+ */
+ public final boolean stringIsCompilableUnit(String source)
+ {
+ boolean errorseen = false;
+ CompilerEnvirons compilerEnv = new CompilerEnvirons();
+ compilerEnv.initFromContext(this);
+ // no source name or source text manager, because we're just
+ // going to throw away the result.
+ compilerEnv.setGeneratingSource(false);
+ Parser p = new Parser(compilerEnv, DefaultErrorReporter.instance);
+ try {
+ p.parse(source, null, 1);
+ } catch (EvaluatorException ee) {
+ errorseen = true;
+ }
+ // Return false only if an error occurred as a result of reading past
+ // the end of the file, i.e. if the source could be fixed by
+ // appending more source.
+ if (errorseen && p.eof())
+ return false;
+ else
+ return true;
+ }
+
+ /**
+ * @deprecated
+ * @see #compileReader(Reader in, String sourceName, int lineno,
+ * Object securityDomain)
+ */
+ public final Script compileReader(Scriptable scope, Reader in,
+ String sourceName, int lineno,
+ Object securityDomain)
+ throws IOException
+ {
+ return compileReader(in, sourceName, lineno, securityDomain);
+ }
+
+ /**
+ * Compiles the source in the given reader.
+ * <p>
+ * Returns a script that may later be executed.
+ * Will consume all the source in the reader.
+ *
+ * @param in the input reader
+ * @param sourceName a string describing the source, such as a filename
+ * @param lineno the starting line number for reporting errors
+ * @param securityDomain an arbitrary object that specifies security
+ * information about the origin or owner of the script. For
+ * implementations that don't care about security, this value
+ * may be null.
+ * @return a script that may later be executed
+ * @exception IOException if an IOException was generated by the Reader
+ * @see org.mozilla.javascript.Script
+ */
+ public final Script compileReader(Reader in, String sourceName,
+ int lineno, Object securityDomain)
+ throws IOException
+ {
+ if (lineno < 0) {
+ // For compatibility IllegalArgumentException can not be thrown here
+ lineno = 0;
+ }
+ return (Script) compileImpl(null, in, null, sourceName, lineno,
+ securityDomain, false, null, null);
+ }
+
+ /**
+ * Compiles the source in the given string.
+ * <p>
+ * Returns a script that may later be executed.
+ *
+ * @param source the source string
+ * @param sourceName a string describing the source, such as a filename
+ * @param lineno the starting line number for reporting errors
+ * @param securityDomain an arbitrary object that specifies security
+ * information about the origin or owner of the script. For
+ * implementations that don't care about security, this value
+ * may be null.
+ * @return a script that may later be executed
+ * @see org.mozilla.javascript.Script
+ */
+ public final Script compileString(String source,
+ String sourceName, int lineno,
+ Object securityDomain)
+ {
+ if (lineno < 0) {
+ // For compatibility IllegalArgumentException can not be thrown here
+ lineno = 0;
+ }
+ return compileString(source, null, null, sourceName, lineno,
+ securityDomain);
+ }
+
+ final Script compileString(String source,
+ Evaluator compiler,
+ ErrorReporter compilationErrorReporter,
+ String sourceName, int lineno,
+ Object securityDomain)
+ {
+ try {
+ return (Script) compileImpl(null, null, source, sourceName, lineno,
+ securityDomain, false,
+ compiler, compilationErrorReporter);
+ } catch (IOException ex) {
+ // Should not happen when dealing with source as string
+ throw new RuntimeException();
+ }
+ }
+
+ /**
+ * Compile a JavaScript function.
+ * <p>
+ * The function source must be a function definition as defined by
+ * ECMA (e.g., "function f(a) { return a; }").
+ *
+ * @param scope the scope to compile relative to
+ * @param source the function definition source
+ * @param sourceName a string describing the source, such as a filename
+ * @param lineno the starting line number
+ * @param securityDomain an arbitrary object that specifies security
+ * information about the origin or owner of the script. For
+ * implementations that don't care about security, this value
+ * may be null.
+ * @return a Function that may later be called
+ * @see org.mozilla.javascript.Function
+ */
+ public final Function compileFunction(Scriptable scope, String source,
+ String sourceName, int lineno,
+ Object securityDomain)
+ {
+ return compileFunction(scope, source, null, null, sourceName, lineno,
+ securityDomain);
+ }
+
+ final Function compileFunction(Scriptable scope, String source,
+ Evaluator compiler,
+ ErrorReporter compilationErrorReporter,
+ String sourceName, int lineno,
+ Object securityDomain)
+ {
+ try {
+ return (Function) compileImpl(scope, null, source, sourceName,
+ lineno, securityDomain, true,
+ compiler, compilationErrorReporter);
+ }
+ catch (IOException ioe) {
+ // Should never happen because we just made the reader
+ // from a String
+ throw new RuntimeException();
+ }
+ }
+
+ /**
+ * Decompile the script.
+ * <p>
+ * The canonical source of the script is returned.
+ *
+ * @param script the script to decompile
+ * @param indent the number of spaces to indent the result
+ * @return a string representing the script source
+ */
+ public final String decompileScript(Script script, int indent)
+ {
+ NativeFunction scriptImpl = (NativeFunction) script;
+ return scriptImpl.decompile(indent, 0);
+ }
+
+ /**
+ * Decompile a JavaScript Function.
+ * <p>
+ * Decompiles a previously compiled JavaScript function object to
+ * canonical source.
+ * <p>
+ * Returns function body of '[native code]' if no decompilation
+ * information is available.
+ *
+ * @param fun the JavaScript function to decompile
+ * @param indent the number of spaces to indent the result
+ * @return a string representing the function source
+ */
+ public final String decompileFunction(Function fun, int indent)
+ {
+ if (fun instanceof BaseFunction)
+ return ((BaseFunction)fun).decompile(indent, 0);
+ else
+ return "function " + fun.getClassName() +
+ "() {\n\t[native code]\n}\n";
+ }
+
+ /**
+ * Decompile the body of a JavaScript Function.
+ * <p>
+ * Decompiles the body a previously compiled JavaScript Function
+ * object to canonical source, omitting the function header and
+ * trailing brace.
+ *
+ * Returns '[native code]' if no decompilation information is available.
+ *
+ * @param fun the JavaScript function to decompile
+ * @param indent the number of spaces to indent the result
+ * @return a string representing the function body source.
+ */
+ public final String decompileFunctionBody(Function fun, int indent)
+ {
+ if (fun instanceof BaseFunction) {
+ BaseFunction bf = (BaseFunction)fun;
+ return bf.decompile(indent, Decompiler.ONLY_BODY_FLAG);
+ }
+ // ALERT: not sure what the right response here is.
+ return "[native code]\n";
+ }
+
+ /**
+ * Create a new JavaScript object.
+ *
+ * Equivalent to evaluating "new Object()".
+ * @param scope the scope to search for the constructor and to evaluate
+ * against
+ * @return the new object
+ */
+ public final Scriptable newObject(Scriptable scope)
+ {
+ return newObject(scope, "Object", ScriptRuntime.emptyArgs);
+ }
+
+ /**
+ * Create a new JavaScript object by executing the named constructor.
+ *
+ * The call <code>newObject(scope, "Foo")</code> is equivalent to
+ * evaluating "new Foo()".
+ *
+ * @param scope the scope to search for the constructor and to evaluate against
+ * @param constructorName the name of the constructor to call
+ * @return the new object
+ */
+ public final Scriptable newObject(Scriptable scope, String constructorName)
+ {
+ return newObject(scope, constructorName, ScriptRuntime.emptyArgs);
+ }
+
+ /**
+ * Creates a new JavaScript object by executing the named constructor.
+ *
+ * Searches <code>scope</code> for the named constructor, calls it with
+ * the given arguments, and returns the result.<p>
+ *
+ * The code
+ * <pre>
+ * Object[] args = { "a", "b" };
+ * newObject(scope, "Foo", args)</pre>
+ * is equivalent to evaluating "new Foo('a', 'b')", assuming that the Foo
+ * constructor has been defined in <code>scope</code>.
+ *
+ * @param scope The scope to search for the constructor and to evaluate
+ * against
+ * @param constructorName the name of the constructor to call
+ * @param args the array of arguments for the constructor
+ * @return the new object
+ */
+ public final Scriptable newObject(Scriptable scope, String constructorName,
+ Object[] args)
+ {
+ scope = ScriptableObject.getTopLevelScope(scope);
+ Function ctor = ScriptRuntime.getExistingCtor(this, scope,
+ constructorName);
+ if (args == null) { args = ScriptRuntime.emptyArgs; }
+ return ctor.construct(this, scope, args);
+ }
+
+ /**
+ * Create an array with a specified initial length.
+ * <p>
+ * @param scope the scope to create the object in
+ * @param length the initial length (JavaScript arrays may have
+ * additional properties added dynamically).
+ * @return the new array object
+ */
+ public final Scriptable newArray(Scriptable scope, int length)
+ {
+ NativeArray result = new NativeArray(length);
+ ScriptRuntime.setObjectProtoAndParent(result, scope);
+ return result;
+ }
+
+ /**
+ * Create an array with a set of initial elements.
+ *
+ * @param scope the scope to create the object in.
+ * @param elements the initial elements. Each object in this array
+ * must be an acceptable JavaScript type and type
+ * of array should be exactly Object[], not
+ * SomeObjectSubclass[].
+ * @return the new array object.
+ */
+ public final Scriptable newArray(Scriptable scope, Object[] elements)
+ {
+ if (elements.getClass().getComponentType() != ScriptRuntime.ObjectClass)
+ throw new IllegalArgumentException();
+ NativeArray result = new NativeArray(elements);
+ ScriptRuntime.setObjectProtoAndParent(result, scope);
+ return result;
+ }
+
+ /**
+ * Get the elements of a JavaScript array.
+ * <p>
+ * If the object defines a length property convertible to double number,
+ * then the number is converted Uint32 value as defined in Ecma 9.6
+ * and Java array of that size is allocated.
+ * The array is initialized with the values obtained by
+ * calling get() on object for each value of i in [0,length-1]. If
+ * there is not a defined value for a property the Undefined value
+ * is used to initialize the corresponding element in the array. The
+ * Java array is then returned.
+ * If the object doesn't define a length property or it is not a number,
+ * empty array is returned.
+ * @param object the JavaScript array or array-like object
+ * @return a Java array of objects
+ * @since 1.4 release 2
+ */
+ public final Object[] getElements(Scriptable object)
+ {
+ return ScriptRuntime.getArrayElements(object);
+ }
+
+ /**
+ * Convert the value to a JavaScript boolean value.
+ * <p>
+ * See ECMA 9.2.
+ *
+ * @param value a JavaScript value
+ * @return the corresponding boolean value converted using
+ * the ECMA rules
+ */
+ public static boolean toBoolean(Object value)
+ {
+ return ScriptRuntime.toBoolean(value);
+ }
+
+ /**
+ * Convert the value to a JavaScript Number value.
+ * <p>
+ * Returns a Java double for the JavaScript Number.
+ * <p>
+ * See ECMA 9.3.
+ *
+ * @param value a JavaScript value
+ * @return the corresponding double value converted using
+ * the ECMA rules
+ */
+ public static double toNumber(Object value)
+ {
+ return ScriptRuntime.toNumber(value);
+ }
+
+ /**
+ * Convert the value to a JavaScript String value.
+ * <p>
+ * See ECMA 9.8.
+ * <p>
+ * @param value a JavaScript value
+ * @return the corresponding String value converted using
+ * the ECMA rules
+ */
+ public static String toString(Object value)
+ {
+ return ScriptRuntime.toString(value);
+ }
+
+ /**
+ * Convert the value to an JavaScript object value.
+ * <p>
+ * Note that a scope must be provided to look up the constructors
+ * for Number, Boolean, and String.
+ * <p>
+ * See ECMA 9.9.
+ * <p>
+ * Additionally, arbitrary Java objects and classes will be
+ * wrapped in a Scriptable object with its Java fields and methods
+ * reflected as JavaScript properties of the object.
+ *
+ * @param value any Java object
+ * @param scope global scope containing constructors for Number,
+ * Boolean, and String
+ * @return new JavaScript object
+ */
+ public static Scriptable toObject(Object value, Scriptable scope)
+ {
+ return ScriptRuntime.toObject(scope, value);
+ }
+
+ /**
+ * @deprecated
+ * @see #toObject(Object, Scriptable)
+ */
+ public static Scriptable toObject(Object value, Scriptable scope,
+ Class<?> staticType)
+ {
+ return ScriptRuntime.toObject(scope, value);
+ }
+
+ /**
+ * Convenient method to convert java value to its closest representation
+ * in JavaScript.
+ * <p>
+ * If value is an instance of String, Number, Boolean, Function or
+ * Scriptable, it is returned as it and will be treated as the corresponding
+ * JavaScript type of string, number, boolean, function and object.
+ * <p>
+ * Note that for Number instances during any arithmetic operation in
+ * JavaScript the engine will always use the result of
+ * <tt>Number.doubleValue()</tt> resulting in a precision loss if
+ * the number can not fit into double.
+ * <p>
+ * If value is an instance of Character, it will be converted to string of
+ * length 1 and its JavaScript type will be string.
+ * <p>
+ * The rest of values will be wrapped as LiveConnect objects
+ * by calling {@link WrapFactory#wrap(Context cx, Scriptable scope,
+ * Object obj, Class staticType)} as in:
+ * <pre>
+ * Context cx = Context.getCurrentContext();
+ * return cx.getWrapFactory().wrap(cx, scope, value, null);
+ * </pre>
+ *
+ * @param value any Java object
+ * @param scope top scope object
+ * @return value suitable to pass to any API that takes JavaScript values.
+ */
+ public static Object javaToJS(Object value, Scriptable scope)
+ {
+ if (value instanceof String || value instanceof Number
+ || value instanceof Boolean || value instanceof Scriptable)
+ {
+ return value;
+ } else if (value instanceof Character) {
+ return String.valueOf(((Character)value).charValue());
+ } else {
+ Context cx = Context.getContext();
+ return cx.getWrapFactory().wrap(cx, scope, value, null);
+ }
+ }
+
+ /**
+ * Convert a JavaScript value into the desired type.
+ * Uses the semantics defined with LiveConnect3 and throws an
+ * Illegal argument exception if the conversion cannot be performed.
+ * @param value the JavaScript value to convert
+ * @param desiredType the Java type to convert to. Primitive Java
+ * types are represented using the TYPE fields in the corresponding
+ * wrapper class in java.lang.
+ * @return the converted value
+ * @throws EvaluatorException if the conversion cannot be performed
+ */
+ public static Object jsToJava(Object value, Class<?> desiredType)
+ throws EvaluatorException
+ {
+ return NativeJavaObject.coerceTypeImpl(desiredType, value);
+ }
+
+ /**
+ * @deprecated
+ * @see #jsToJava(Object, Class)
+ * @throws IllegalArgumentException if the conversion cannot be performed.
+ * Note that {@link #jsToJava(Object, Class)} throws
+ * {@link EvaluatorException} instead.
+ */
+ public static Object toType(Object value, Class<?> desiredType)
+ throws IllegalArgumentException
+ {
+ try {
+ return jsToJava(value, desiredType);
+ } catch (EvaluatorException ex) {
+ IllegalArgumentException
+ ex2 = new IllegalArgumentException(ex.getMessage());
+ Kit.initCause(ex2, ex);
+ throw ex2;
+ }
+ }
+
+ /**
+ * Rethrow the exception wrapping it as the script runtime exception.
+ * Unless the exception is instance of {@link EcmaError} or
+ * {@link EvaluatorException} it will be wrapped as
+ * {@link WrappedException}, a subclass of {@link EvaluatorException}.
+ * The resulting exception object always contains
+ * source name and line number of script that triggered exception.
+ * <p>
+ * This method always throws an exception, its return value is provided
+ * only for convenience to allow a usage like:
+ * <pre>
+ * throw Context.throwAsScriptRuntimeEx(ex);
+ * </pre>
+ * to indicate that code after the method is unreachable.
+ * @throws EvaluatorException
+ * @throws EcmaError
+ */
+ public static RuntimeException throwAsScriptRuntimeEx(Throwable e)
+ {
+ while ((e instanceof InvocationTargetException)) {
+ e = ((InvocationTargetException) e).getTargetException();
+ }
+ // special handling of Error so scripts would not catch them
+ if (e instanceof Error) {
+ Context cx = getContext();
+ if (cx == null ||
+ !cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS))
+ {
+ throw (Error)e;
+ }
+ }
+ if (e instanceof RhinoException) {
+ throw (RhinoException)e;
+ }
+ throw new WrappedException(e);
+ }
+
+ /**
+ * Tell whether debug information is being generated.
+ * @since 1.3
+ */
+ public final boolean isGeneratingDebug()
+ {
+ return generatingDebug;
+ }
+
+ /**
+ * Specify whether or not debug information should be generated.
+ * <p>
+ * Setting the generation of debug information on will set the
+ * optimization level to zero.
+ * @since 1.3
+ */
+ public final void setGeneratingDebug(boolean generatingDebug)
+ {
+ if (sealed) onSealedMutation();
+ generatingDebugChanged = true;
+ if (generatingDebug && getOptimizationLevel() > 0)
+ setOptimizationLevel(0);
+ this.generatingDebug = generatingDebug;
+ }
+
+ /**
+ * Tell whether source information is being generated.
+ * @since 1.3
+ */
+ public final boolean isGeneratingSource()
+ {
+ return generatingSource;
+ }
+
+ /**
+ * Specify whether or not source information should be generated.
+ * <p>
+ * Without source information, evaluating the "toString" method
+ * on JavaScript functions produces only "[native code]" for
+ * the body of the function.
+ * Note that code generated without source is not fully ECMA
+ * conformant.
+ * @since 1.3
+ */
+ public final void setGeneratingSource(boolean generatingSource)
+ {
+ if (sealed) onSealedMutation();
+ this.generatingSource = generatingSource;
+ }
+
+ /**
+ * Get the current optimization level.
+ * <p>
+ * The optimization level is expressed as an integer between -1 and
+ * 9.
+ * @since 1.3
+ *
+ */
+ public final int getOptimizationLevel()
+ {
+ return optimizationLevel;
+ }
+
+ /**
+ * Set the current optimization level.
+ * <p>
+ * The optimization level is expected to be an integer between -1 and
+ * 9. Any negative values will be interpreted as -1, and any values
+ * greater than 9 will be interpreted as 9.
+ * An optimization level of -1 indicates that interpretive mode will
+ * always be used. Levels 0 through 9 indicate that class files may
+ * be generated. Higher optimization levels trade off compile time
+ * performance for runtime performance.
+ * The optimizer level can't be set greater than -1 if the optimizer
+ * package doesn't exist at run time.
+ * @param optimizationLevel an integer indicating the level of
+ * optimization to perform
+ * @since 1.3
+ *
+ */
+ public final void setOptimizationLevel(int optimizationLevel)
+ {
+ if (sealed) onSealedMutation();
+ if (optimizationLevel == -2) {
+ // To be compatible with Cocoon fork
+ optimizationLevel = -1;
+ }
+ checkOptimizationLevel(optimizationLevel);
+ if (codegenClass == null)
+ optimizationLevel = -1;
+ this.optimizationLevel = optimizationLevel;
+ }
+
+ public static boolean isValidOptimizationLevel(int optimizationLevel)
+ {
+ return -1 <= optimizationLevel && optimizationLevel <= 9;
+ }
+
+ public static void checkOptimizationLevel(int optimizationLevel)
+ {
+ if (isValidOptimizationLevel(optimizationLevel)) {
+ return;
+ }
+ throw new IllegalArgumentException(
+ "Optimization level outside [-1..9]: "+optimizationLevel);
+ }
+
+ /**
+ * Returns the maximum stack depth (in terms of number of call frames)
+ * allowed in a single invocation of interpreter. If the set depth would be
+ * exceeded, the interpreter will throw an EvaluatorException in the script.
+ * Defaults to Integer.MAX_VALUE. The setting only has effect for
+ * interpreted functions (those compiled with optimization level set to -1).
+ * As the interpreter doesn't use the Java stack but rather manages its own
+ * stack in the heap memory, a runaway recursion in interpreted code would
+ * eventually consume all available memory and cause OutOfMemoryError
+ * instead of a StackOverflowError limited to only a single thread. This
+ * setting helps prevent such situations.
+ *
+ * @return The current maximum interpreter stack depth.
+ */
+ public final int getMaximumInterpreterStackDepth()
+ {
+ return maximumInterpreterStackDepth;
+ }
+
+ /**
+ * Sets the maximum stack depth (in terms of number of call frames)
+ * allowed in a single invocation of interpreter. If the set depth would be
+ * exceeded, the interpreter will throw an EvaluatorException in the script.
+ * Defaults to Integer.MAX_VALUE. The setting only has effect for
+ * interpreted functions (those compiled with optimization level set to -1).
+ * As the interpreter doesn't use the Java stack but rather manages its own
+ * stack in the heap memory, a runaway recursion in interpreted code would
+ * eventually consume all available memory and cause OutOfMemoryError
+ * instead of a StackOverflowError limited to only a single thread. This
+ * setting helps prevent such situations.
+ *
+ * @param max the new maximum interpreter stack depth
+ * @throws IllegalStateException if this context's optimization level is not
+ * -1
+ * @throws IllegalArgumentException if the new depth is not at least 1
+ */
+ public final void setMaximumInterpreterStackDepth(int max)
+ {
+ if(sealed) onSealedMutation();
+ if(optimizationLevel != -1) {
+ throw new IllegalStateException("Cannot set maximumInterpreterStackDepth when optimizationLevel != -1");
+ }
+ if(max < 1) {
+ throw new IllegalArgumentException("Cannot set maximumInterpreterStackDepth to less than 1");
+ }
+ maximumInterpreterStackDepth = max;
+ }
+
+ /**
+ * Set the security controller for this context.
+ * <p> SecurityController may only be set if it is currently null
+ * and {@link SecurityController#hasGlobal()} is <tt>false</tt>.
+ * Otherwise a SecurityException is thrown.
+ * @param controller a SecurityController object
+ * @throws SecurityException if there is already a SecurityController
+ * object for this Context or globally installed.
+ * @see SecurityController#initGlobal(SecurityController controller)
+ * @see SecurityController#hasGlobal()
+ */
+ public final void setSecurityController(SecurityController controller)
+ {
+ if (sealed) onSealedMutation();
+ if (controller == null) throw new IllegalArgumentException();
+ if (securityController != null) {
+ throw new SecurityException("Can not overwrite existing SecurityController object");
+ }
+ if (SecurityController.hasGlobal()) {
+ throw new SecurityException("Can not overwrite existing global SecurityController object");
+ }
+ securityController = controller;
+ }
+
+ /**
+ * Set the LiveConnect access filter for this context.
+ * <p> {@link ClassShutter} may only be set if it is currently null.
+ * Otherwise a SecurityException is thrown.
+ * @param shutter a ClassShutter object
+ * @throws SecurityException if there is already a ClassShutter
+ * object for this Context
+ */
+ public final void setClassShutter(ClassShutter shutter)
+ {
+ if (sealed) onSealedMutation();
+ if (shutter == null) throw new IllegalArgumentException();
+ if (classShutter != null) {
+ throw new SecurityException("Cannot overwrite existing " +
+ "ClassShutter object");
+ }
+ classShutter = shutter;
+ }
+
+ final ClassShutter getClassShutter()
+ {
+ return classShutter;
+ }
+
+ /**
+ * Get a value corresponding to a key.
+ * <p>
+ * Since the Context is associated with a thread it can be
+ * used to maintain values that can be later retrieved using
+ * the current thread.
+ * <p>
+ * Note that the values are maintained with the Context, so
+ * if the Context is disassociated from the thread the values
+ * cannot be retrieved. Also, if private data is to be maintained
+ * in this manner the key should be a java.lang.Object
+ * whose reference is not divulged to untrusted code.
+ * @param key the key used to lookup the value
+ * @return a value previously stored using putThreadLocal.
+ */
+ public final Object getThreadLocal(Object key)
+ {
+ if (threadLocalMap == null)
+ return null;
+ return threadLocalMap.get(key);
+ }
+
+ /**
+ * Put a value that can later be retrieved using a given key.
+ * <p>
+ * @param key the key used to index the value
+ * @param value the value to save
+ */
+ public synchronized final void putThreadLocal(Object key, Object value)
+ {
+ if (sealed) onSealedMutation();
+ if (threadLocalMap == null)
+ threadLocalMap = new HashMap<Object,Object>();
+ threadLocalMap.put(key, value);
+ }
+
+ /**
+ * Remove values from thread-local storage.
+ * @param key the key for the entry to remove.
+ * @since 1.5 release 2
+ */
+ public final void removeThreadLocal(Object key)
+ {
+ if (sealed) onSealedMutation();
+ if (threadLocalMap == null)
+ return;
+ threadLocalMap.remove(key);
+ }
+
+ /**
+ * @deprecated
+ * @see #FEATURE_DYNAMIC_SCOPE
+ * @see #hasFeature(int)
+ */
+ public final boolean hasCompileFunctionsWithDynamicScope()
+ {
+ return compileFunctionsWithDynamicScopeFlag;
+ }
+
+ /**
+ * @deprecated
+ * @see #FEATURE_DYNAMIC_SCOPE
+ * @see #hasFeature(int)
+ */
+ public final void setCompileFunctionsWithDynamicScope(boolean flag)
+ {
+ if (sealed) onSealedMutation();
+ compileFunctionsWithDynamicScopeFlag = flag;
+ }
+
+ /**
+ * @deprecated
+ * @see ClassCache#get(Scriptable)
+ * @see ClassCache#setCachingEnabled(boolean)
+ */
+ public static void setCachingEnabled(boolean cachingEnabled)
+ {
+ }
+
+ /**
+ * Set a WrapFactory for this Context.
+ * <p>
+ * The WrapFactory allows custom object wrapping behavior for
+ * Java object manipulated with JavaScript.
+ * @see WrapFactory
+ * @since 1.5 Release 4
+ */
+ public final void setWrapFactory(WrapFactory wrapFactory)
+ {
+ if (sealed) onSealedMutation();
+ if (wrapFactory == null) throw new IllegalArgumentException();
+ this.wrapFactory = wrapFactory;
+ }
+
+ /**
+ * Return the current WrapFactory, or null if none is defined.
+ * @see WrapFactory
+ * @since 1.5 Release 4
+ */
+ public final WrapFactory getWrapFactory()
+ {
+ if (wrapFactory == null) {
+ wrapFactory = new WrapFactory();
+ }
+ return wrapFactory;
+ }
+
+ /**
+ * Return the current debugger.
+ * @return the debugger, or null if none is attached.
+ */
+ public final Debugger getDebugger()
+ {
+ return debugger;
+ }
+
+ /**
+ * Return the debugger context data associated with current context.
+ * @return the debugger data, or null if debugger is not attached
+ */
+ public final Object getDebuggerContextData()
+ {
+ return debuggerData;
+ }
+
+ /**
+ * Set the associated debugger.
+ * @param debugger the debugger to be used on callbacks from
+ * the engine.
+ * @param contextData arbitrary object that debugger can use to store
+ * per Context data.
+ */
+ public final void setDebugger(Debugger debugger, Object contextData)
+ {
+ if (sealed) onSealedMutation();
+ this.debugger = debugger;
+ debuggerData = contextData;
+ }
+
+ /**
+ * Return DebuggableScript instance if any associated with the script.
+ * If callable supports DebuggableScript implementation, the method
+ * returns it. Otherwise null is returned.
+ */
+ public static DebuggableScript getDebuggableView(Script script)
+ {
+ if (script instanceof NativeFunction) {
+ return ((NativeFunction)script).getDebuggableView();
+ }
+ return null;
+ }
+
+ /**
+ * Controls certain aspects of script semantics.
+ * Should be overwritten to alter default behavior.
+ * <p>
+ * The default implementation calls
+ * {@link ContextFactory#hasFeature(Context cx, int featureIndex)}
+ * that allows to customize Context behavior without introducing
+ * Context subclasses. {@link ContextFactory} documentation gives
+ * an example of hasFeature implementation.
+ *
+ * @param featureIndex feature index to check
+ * @return true if the <code>featureIndex</code> feature is turned on
+ * @see #FEATURE_NON_ECMA_GET_YEAR
+ * @see #FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME
+ * @see #FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER
+ * @see #FEATURE_TO_STRING_AS_SOURCE
+ * @see #FEATURE_PARENT_PROTO_PROPRTIES
+ * @see #FEATURE_E4X
+ * @see #FEATURE_DYNAMIC_SCOPE
+ * @see #FEATURE_STRICT_VARS
+ * @see #FEATURE_STRICT_EVAL
+ * @see #FEATURE_LOCATION_INFORMATION_IN_ERROR
+ * @see #FEATURE_STRICT_MODE
+ * @see #FEATURE_WARNING_AS_ERROR
+ * @see #FEATURE_ENHANCED_JAVA_ACCESS
+ */
+ public boolean hasFeature(int featureIndex)
+ {
+ ContextFactory f = getFactory();
+ return f.hasFeature(this, featureIndex);
+ }
+
+ /**
+ Returns an object which specifies an E4X implementation to use within
+ this <code>Context</code>. Note
+ that the XMLLib.Factory interface should be considered experimental.
+
+ The default implementation uses the implementation provided by this
+ <code>Context</code>'s {@link ContextFactory}.
+
+ @return An XMLLib.Factory. Should not return <code>null</code> if
+ {@link #FEATURE_E4X} is enabled. See {@link #hasFeature}.
+ */
+ public XMLLib.Factory getE4xImplementationFactory() {
+ return getFactory().getE4xImplementationFactory();
+ }
+
+ /**
+ * Get threshold of executed instructions counter that triggers call to
+ * <code>observeInstructionCount()</code>.
+ * When the threshold is zero, instruction counting is disabled,
+ * otherwise each time the run-time executes at least the threshold value
+ * of script instructions, <code>observeInstructionCount()</code> will
+ * be called.
+ */
+ public final int getInstructionObserverThreshold()
+ {
+ return instructionThreshold;
+ }
+
+ /**
+ * Set threshold of executed instructions counter that triggers call to
+ * <code>observeInstructionCount()</code>.
+ * When the threshold is zero, instruction counting is disabled,
+ * otherwise each time the run-time executes at least the threshold value
+ * of script instructions, <code>observeInstructionCount()</code> will
+ * be called.<p/>
+ * Note that the meaning of "instruction" is not guaranteed to be
+ * consistent between compiled and interpretive modes: executing a given
+ * script or function in the different modes will result in different
+ * instruction counts against the threshold.
+ * {@link #setGenerateObserverCount} is called with true if
+ * <code>threshold</code> is greater than zero, false otherwise.
+ * @param threshold The instruction threshold
+ */
+ public final void setInstructionObserverThreshold(int threshold)
+ {
+ if (sealed) onSealedMutation();
+ if (threshold < 0) throw new IllegalArgumentException();
+ instructionThreshold = threshold;
+ setGenerateObserverCount(threshold > 0);
+ }
+
+ /**
+ * Turn on or off generation of code with callbacks to
+ * track the count of executed instructions.
+ * Currently only affects JVM byte code generation: this slows down the
+ * generated code, but code generated without the callbacks will not
+ * be counted toward instruction thresholds. Rhino's interpretive
+ * mode does instruction counting without inserting callbacks, so
+ * there is no requirement to compile code differently.
+ * @param generateObserverCount if true, generated code will contain
+ * calls to accumulate an estimate of the instructions executed.
+ */
+ public void setGenerateObserverCount(boolean generateObserverCount) {
+ this.generateObserverCount = generateObserverCount;
+ }
+
+ /**
+ * Allow application to monitor counter of executed script instructions
+ * in Context subclasses.
+ * Run-time calls this when instruction counting is enabled and the counter
+ * reaches limit set by <code>setInstructionObserverThreshold()</code>.
+ * The method is useful to observe long running scripts and if necessary
+ * to terminate them.
+ * <p>
+ * The default implementation calls
+ * {@link ContextFactory#observeInstructionCount(Context cx,
+ * int instructionCount)}
+ * that allows to customize Context behavior without introducing
+ * Context subclasses.
+ *
+ * @param instructionCount amount of script instruction executed since
+ * last call to <code>observeInstructionCount</code>
+ * @throws Error to terminate the script
+ * @see #setOptimizationLevel(int)
+ */
+ protected void observeInstructionCount(int instructionCount)
+ {
+ ContextFactory f = getFactory();
+ f.observeInstructionCount(this, instructionCount);
+ }
+
+ /**
+ * Create class loader for generated classes.
+ * The method calls {@link ContextFactory#createClassLoader(ClassLoader)}
+ * using the result of {@link #getFactory()}.
+ */
+ public GeneratedClassLoader createClassLoader(ClassLoader parent)
+ {
+ ContextFactory f = getFactory();
+ return f.createClassLoader(parent);
+ }
+
+ public final ClassLoader getApplicationClassLoader()
+ {
+ if (applicationClassLoader == null) {
+ ContextFactory f = getFactory();
+ ClassLoader loader = f.getApplicationClassLoader();
+ if (loader == null) {
+ ClassLoader threadLoader
+ = VMBridge.instance.getCurrentThreadClassLoader();
+ if (threadLoader != null
+ && Kit.testIfCanLoadRhinoClasses(threadLoader))
+ {
+ // Thread.getContextClassLoader is not cached since
+ // its caching prevents it from GC which may lead to
+ // a memory leak and hides updates to
+ // Thread.getContextClassLoader
+ return threadLoader;
+ }
+ // Thread.getContextClassLoader can not load Rhino classes,
+ // try to use the loader of ContextFactory or Context
+ // subclasses.
+ Class<?> fClass = f.getClass();
+ if (fClass != ScriptRuntime.ContextFactoryClass) {
+ loader = fClass.getClassLoader();
+ } else {
+ loader = getClass().getClassLoader();
+ }
+ }
+ applicationClassLoader = loader;
+ }
+ return applicationClassLoader;
+ }
+
+ public final void setApplicationClassLoader(ClassLoader loader)
+ {
+ if (sealed) onSealedMutation();
+ if (loader == null) {
+ // restore default behaviour
+ applicationClassLoader = null;
+ return;
+ }
+ if (!Kit.testIfCanLoadRhinoClasses(loader)) {
+ throw new IllegalArgumentException(
+ "Loader can not resolve Rhino classes");
+ }
+ applicationClassLoader = loader;
+ }
+
+ /********** end of API **********/
+
+ /**
+ * Internal method that reports an error for missing calls to
+ * enter().
+ */
+ static Context getContext()
+ {
+ Context cx = getCurrentContext();
+ if (cx == null) {
+ throw new RuntimeException(
+ "No Context associated with current Thread");
+ }
+ return cx;
+ }
+
+ private Object compileImpl(Scriptable scope,
+ Reader sourceReader, String sourceString,
+ String sourceName, int lineno,
+ Object securityDomain, boolean returnFunction,
+ Evaluator compiler,
+ ErrorReporter compilationErrorReporter)
+ throws IOException
+ {
+ if(sourceName == null) {
+ sourceName = "unnamed script";
+ }
+ if (securityDomain != null && getSecurityController() == null) {
+ throw new IllegalArgumentException(
+ "securityDomain should be null if setSecurityController() was never called");
+ }
+
+ // One of sourceReader or sourceString has to be null
+ if (!(sourceReader == null ^ sourceString == null)) Kit.codeBug();
+ // scope should be given if and only if compiling function
+ if (!(scope == null ^ returnFunction)) Kit.codeBug();
+
+ CompilerEnvirons compilerEnv = new CompilerEnvirons();
+ compilerEnv.initFromContext(this);
+ if (compilationErrorReporter == null) {
+ compilationErrorReporter = compilerEnv.getErrorReporter();
+ }
+
+ if (debugger != null) {
+ if (sourceReader != null) {
+ sourceString = Kit.readReader(sourceReader);
+ sourceReader = null;
+ }
+ }
+
+ Parser p = new Parser(compilerEnv, compilationErrorReporter);
+ if (returnFunction) {
+ p.calledByCompileFunction = true;
+ }
+ ScriptOrFnNode tree;
+ if (sourceString != null) {
+ tree = p.parse(sourceString, sourceName, lineno);
+ } else {
+ tree = p.parse(sourceReader, sourceName, lineno);
+ }
+ if (returnFunction) {
+ if (!(tree.getFunctionCount() == 1
+ && tree.getFirstChild() != null
+ && tree.getFirstChild().getType() == Token.FUNCTION))
+ {
+ // XXX: the check just look for the first child
+ // and allows for more nodes after it for compatibility
+ // with sources like function() {};;;
+ throw new IllegalArgumentException(
+ "compileFunction only accepts source with single JS function: "+sourceString);
+ }
+ }
+
+ if (compiler == null) {
+ compiler = createCompiler();
+ }
+
+ String encodedSource = p.getEncodedSource();
+
+ Object bytecode = compiler.compile(compilerEnv,
+ tree, encodedSource,
+ returnFunction);
+
+ if (debugger != null) {
+ if (sourceString == null) Kit.codeBug();
+ if (bytecode instanceof DebuggableScript) {
+ DebuggableScript dscript = (DebuggableScript)bytecode;
+ notifyDebugger_r(this, dscript, sourceString);
+ } else {
+ throw new RuntimeException("NOT SUPPORTED");
+ }
+ }
+
+ Object result;
+ if (returnFunction) {
+ result = compiler.createFunctionObject(this, scope, bytecode, securityDomain);
+ } else {
+ result = compiler.createScriptObject(bytecode, securityDomain);
+ }
+
+ return result;
+ }
+
+ private static void notifyDebugger_r(Context cx, DebuggableScript dscript,
+ String debugSource)
+ {
+ cx.debugger.handleCompilationDone(cx, dscript, debugSource);
+ for (int i = 0; i != dscript.getFunctionCount(); ++i) {
+ notifyDebugger_r(cx, dscript.getFunction(i), debugSource);
+ }
+ }
+
+ private static Class<?> codegenClass = Kit.classOrNull(
+ "org.mozilla.javascript.optimizer.Codegen");
+ private static Class<?> interpreterClass = Kit.classOrNull(
+ "org.mozilla.javascript.Interpreter");
+
+ private Evaluator createCompiler()
+ {
+ Evaluator result = null;
+ if (optimizationLevel >= 0 && codegenClass != null) {
+ result = (Evaluator)Kit.newInstanceOrNull(codegenClass);
+ }
+ if (result == null) {
+ result = createInterpreter();
+ }
+ return result;
+ }
+
+ static Evaluator createInterpreter()
+ {
+ return (Evaluator)Kit.newInstanceOrNull(interpreterClass);
+ }
+
+ static String getSourcePositionFromStack(int[] linep)
+ {
+ Context cx = getCurrentContext();
+ if (cx == null)
+ return null;
+ if (cx.lastInterpreterFrame != null) {
+ Evaluator evaluator = createInterpreter();
+ if (evaluator != null)
+ return evaluator.getSourcePositionFromStack(cx, linep);
+ }
+ /**
+ * A bit of a hack, but the only way to get filename and line
+ * number from an enclosing frame.
+ */
+ CharArrayWriter writer = new CharArrayWriter();
+ RuntimeException re = new RuntimeException();
+ re.printStackTrace(new PrintWriter(writer));
+ String s = writer.toString();
+ int open = -1;
+ int close = -1;
+ int colon = -1;
+ for (int i=0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c == ':')
+ colon = i;
+ else if (c == '(')
+ open = i;
+ else if (c == ')')
+ close = i;
+ else if (c == '\n' && open != -1 && close != -1 && colon != -1 &&
+ open < colon && colon < close)
+ {
+ String fileStr = s.substring(open + 1, colon);
+ if (!fileStr.endsWith(".java")) {
+ String lineStr = s.substring(colon + 1, close);
+ try {
+ linep[0] = Integer.parseInt(lineStr);
+ if (linep[0] < 0) {
+ linep[0] = 0;
+ }
+ return fileStr;
+ }
+ catch (NumberFormatException e) {
+ // fall through
+ }
+ }
+ open = close = colon = -1;
+ }
+ }
+
+ return null;
+ }
+
+ RegExpProxy getRegExpProxy()
+ {
+ if (regExpProxy == null) {
+ Class<?> cl = Kit.classOrNull(
+ "org.mozilla.javascript.regexp.RegExpImpl");
+ if (cl != null) {
+ regExpProxy = (RegExpProxy)Kit.newInstanceOrNull(cl);
+ }
+ }
+ return regExpProxy;
+ }
+
+ final boolean isVersionECMA1()
+ {
+ return version == VERSION_DEFAULT || version >= VERSION_1_3;
+ }
+
+// The method must NOT be public or protected
+ SecurityController getSecurityController()
+ {
+ SecurityController global = SecurityController.global();
+ if (global != null) {
+ return global;
+ }
+ return securityController;
+ }
+
+ public final boolean isGeneratingDebugChanged()
+ {
+ return generatingDebugChanged;
+ }
+
+ /**
+ * Add a name to the list of names forcing the creation of real
+ * activation objects for functions.
+ *
+ * @param name the name of the object to add to the list
+ */
+ public void addActivationName(String name)
+ {
+ if (sealed) onSealedMutation();
+ if (activationNames == null)
+ activationNames = new HashSet<String>();
+ activationNames.add(name);
+ }
+
+ /**
+ * Check whether the name is in the list of names of objects
+ * forcing the creation of activation objects.
+ *
+ * @param name the name of the object to test
+ *
+ * @return true if an function activation object is needed.
+ */
+ public final boolean isActivationNeeded(String name)
+ {
+ return activationNames != null && activationNames.contains(name);
+ }
+
+ /**
+ * Remove a name from the list of names forcing the creation of real
+ * activation objects for functions.
+ *
+ * @param name the name of the object to remove from the list
+ */
+ public void removeActivationName(String name)
+ {
+ if (sealed) onSealedMutation();
+ if (activationNames != null)
+ activationNames.remove(name);
+ }
+
+ private static String implementationVersion;
+
+ private final ContextFactory factory;
+ private boolean sealed;
+ private Object sealKey;
+
+ Scriptable topCallScope;
+ boolean isContinuationsTopCall;
+ NativeCall currentActivationCall;
+ XMLLib cachedXMLLib;
+
+ // for Objects, Arrays to tag themselves as being printed out,
+ // so they don't print themselves out recursively.
+ // Use ObjToIntMap instead of java.util.HashSet for JDK 1.1 compatibility
+ ObjToIntMap iterating;
+
+ Object interpreterSecurityDomain;
+
+ int version;
+
+ private SecurityController securityController;
+ private ClassShutter classShutter;
+ private ErrorReporter errorReporter;
+ RegExpProxy regExpProxy;
+ private Locale locale;
+ private boolean generatingDebug;
+ private boolean generatingDebugChanged;
+ private boolean generatingSource=true;
+ boolean compileFunctionsWithDynamicScopeFlag;
+ boolean useDynamicScope;
+ private int optimizationLevel;
+ private int maximumInterpreterStackDepth;
+ private WrapFactory wrapFactory;
+ Debugger debugger;
+ private Object debuggerData;
+ private int enterCount;
+ private Object propertyListeners;
+ private Map<Object,Object> threadLocalMap;
+ private ClassLoader applicationClassLoader;
+
+ /**
+ * This is the list of names of objects forcing the creation of
+ * function activation records.
+ */
+ Set<String> activationNames;
+
+ // For the interpreter to store the last frame for error reports etc.
+ Object lastInterpreterFrame;
+
+ // For the interpreter to store information about previous invocations
+ // interpreter invocations
+ ObjArray previousInterpreterInvocations;
+
+ // For instruction counting (interpreter only)
+ int instructionCount;
+ int instructionThreshold;
+
+ // It can be used to return the second index-like result from function
+ int scratchIndex;
+
+ // It can be used to return the second uint32 result from function
+ long scratchUint32;
+
+ // It can be used to return the second Scriptable result from function
+ Scriptable scratchScriptable;
+
+ // Generate an observer count on compiled code
+ public boolean generateObserverCount = false;
+}
diff --git a/src/org/mozilla/javascript/ContextAction.java b/src/org/mozilla/javascript/ContextAction.java
new file mode 100644
index 0000000..6dc8d0d
--- /dev/null
+++ b/src/org/mozilla/javascript/ContextAction.java
@@ -0,0 +1,58 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov, igor at fastmail.fm
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * Interface to represent arbitrary action that requires to have Context
+ * object associated with the current thread for its execution.
+ */
+public interface ContextAction
+{
+ /**
+ * Execute action using the supplied Context instance.
+ * When Rhino runtime calls the method, <tt>cx</tt> will be associated
+ * with the current thread as active context.
+ *
+ * @see ContextFactory#call(ContextAction)
+ */
+ public Object run(Context cx);
+}
+
diff --git a/src/org/mozilla/javascript/ContextFactory.java b/src/org/mozilla/javascript/ContextFactory.java
new file mode 100644
index 0000000..0e834e6
--- /dev/null
+++ b/src/org/mozilla/javascript/ContextFactory.java
@@ -0,0 +1,590 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov, igor at fastmail.fm
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * Factory class that Rhino runtime uses to create new {@link Context}
+ * instances. A <code>ContextFactory</code> can also notify listeners
+ * about context creation and release.
+ * <p>
+ * When the Rhino runtime needs to create new {@link Context} instance during
+ * execution of {@link Context#enter()} or {@link Context}, it will call
+ * {@link #makeContext()} of the current global ContextFactory.
+ * See {@link #getGlobal()} and {@link #initGlobal(ContextFactory)}.
+ * <p>
+ * It is also possible to use explicit ContextFactory instances for Context
+ * creation. This is useful to have a set of independent Rhino runtime
+ * instances under single JVM. See {@link #call(ContextAction)}.
+ * <p>
+ * The following example demonstrates Context customization to terminate
+ * scripts running more then 10 seconds and to provide better compatibility
+ * with JavaScript code using MSIE-specific features.
+ * <pre>
+ * import org.mozilla.javascript.*;
+ *
+ * class MyFactory extends ContextFactory
+ * {
+ *
+ * // Custom {@link Context} to store execution time.
+ * private static class MyContext extends Context
+ * {
+ * long startTime;
+ * }
+ *
+ * static {
+ * // Initialize GlobalFactory with custom factory
+ * ContextFactory.initGlobal(new MyFactory());
+ * }
+ *
+ * // Override {@link #makeContext()}
+ * protected Context makeContext()
+ * {
+ * MyContext cx = new MyContext();
+ * // Make Rhino runtime to call observeInstructionCount
+ * // each 10000 bytecode instructions
+ * cx.setInstructionObserverThreshold(10000);
+ * return cx;
+ * }
+ *
+ * // Override {@link #hasFeature(Context, int)}
+ * public boolean hasFeature(Context cx, int featureIndex)
+ * {
+ * // Turn on maximum compatibility with MSIE scripts
+ * switch (featureIndex) {
+ * case {@link Context#FEATURE_NON_ECMA_GET_YEAR}:
+ * return true;
+ *
+ * case {@link Context#FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME}:
+ * return true;
+ *
+ * case {@link Context#FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER}:
+ * return true;
+ *
+ * case {@link Context#FEATURE_PARENT_PROTO_PROPERTIES}:
+ * return false;
+ * }
+ * return super.hasFeature(cx, featureIndex);
+ * }
+ *
+ * // Override {@link #observeInstructionCount(Context, int)}
+ * protected void observeInstructionCount(Context cx, int instructionCount)
+ * {
+ * MyContext mcx = (MyContext)cx;
+ * long currentTime = System.currentTimeMillis();
+ * if (currentTime - mcx.startTime > 10*1000) {
+ * // More then 10 seconds from Context creation time:
+ * // it is time to stop the script.
+ * // Throw Error instance to ensure that script will never
+ * // get control back through catch or finally.
+ * throw new Error();
+ * }
+ * }
+ *
+ * // Override {@link #doTopCall(Callable,
+ Context, Scriptable,
+ Scriptable, Object[])}
+ * protected Object doTopCall(Callable callable,
+ * Context cx, Scriptable scope,
+ * Scriptable thisObj, Object[] args)
+ * {
+ * MyContext mcx = (MyContext)cx;
+ * mcx.startTime = System.currentTimeMillis();
+ *
+ * return super.doTopCall(callable, cx, scope, thisObj, args);
+ * }
+ *
+ * }
+ *
+ * </pre>
+ */
+
+public class ContextFactory
+{
+ private static volatile boolean hasCustomGlobal;
+ private static ContextFactory global = new ContextFactory();
+
+ private volatile boolean sealed;
+
+ private final Object listenersLock = new Object();
+ private volatile Object listeners;
+ private boolean disabledListening;
+ private ClassLoader applicationClassLoader;
+
+ /**
+ * Listener of {@link Context} creation and release events.
+ */
+ public interface Listener
+ {
+ /**
+ * Notify about newly created {@link Context} object.
+ */
+ public void contextCreated(Context cx);
+
+ /**
+ * Notify that the specified {@link Context} instance is no longer
+ * associated with the current thread.
+ */
+ public void contextReleased(Context cx);
+ }
+
+ /**
+ * Get global ContextFactory.
+ *
+ * @see #hasExplicitGlobal()
+ * @see #initGlobal(ContextFactory)
+ */
+ public static ContextFactory getGlobal()
+ {
+ return global;
+ }
+
+ /**
+ * Check if global factory was set.
+ * Return true to indicate that {@link #initGlobal(ContextFactory)} was
+ * already called and false to indicate that the global factory was not
+ * explicitly set.
+ *
+ * @see #getGlobal()
+ * @see #initGlobal(ContextFactory)
+ */
+ public static boolean hasExplicitGlobal()
+ {
+ return hasCustomGlobal;
+ }
+
+ /**
+ * Set global ContextFactory.
+ * The method can only be called once.
+ *
+ * @see #getGlobal()
+ * @see #hasExplicitGlobal()
+ */
+ public synchronized static void initGlobal(ContextFactory factory)
+ {
+ if (factory == null) {
+ throw new IllegalArgumentException();
+ }
+ if (hasCustomGlobal) {
+ throw new IllegalStateException();
+ }
+ hasCustomGlobal = true;
+ global = factory;
+ }
+
+ /**
+ * Create new {@link Context} instance to be associated with the current
+ * thread.
+ * This is a callback method used by Rhino to create {@link Context}
+ * instance when it is necessary to associate one with the current
+ * execution thread. <tt>makeContext()</tt> is allowed to call
+ * {@link Context#seal(Object)} on the result to prevent
+ * {@link Context} changes by hostile scripts or applets.
+ */
+ protected Context makeContext()
+ {
+ return new Context(this);
+ }
+
+ /**
+ * Implementation of {@link Context#hasFeature(int featureIndex)}.
+ * This can be used to customize {@link Context} without introducing
+ * additional subclasses.
+ */
+ protected boolean hasFeature(Context cx, int featureIndex)
+ {
+ int version;
+ switch (featureIndex) {
+ case Context.FEATURE_NON_ECMA_GET_YEAR:
+ /*
+ * During the great date rewrite of 1.3, we tried to track the
+ * evolving ECMA standard, which then had a definition of
+ * getYear which always subtracted 1900. Which we
+ * implemented, not realizing that it was incompatible with
+ * the old behavior... now, rather than thrash the behavior
+ * yet again, we've decided to leave it with the - 1900
+ * behavior and point people to the getFullYear method. But
+ * we try to protect existing scripts that have specified a
+ * version...
+ */
+ version = cx.getLanguageVersion();
+ return (version == Context.VERSION_1_0
+ || version == Context.VERSION_1_1
+ || version == Context.VERSION_1_2);
+
+ case Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME:
+ return false;
+
+ case Context.FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER:
+ return false;
+
+ case Context.FEATURE_TO_STRING_AS_SOURCE:
+ version = cx.getLanguageVersion();
+ return version == Context.VERSION_1_2;
+
+ case Context.FEATURE_PARENT_PROTO_PROPERTIES:
+ return true;
+
+ case Context.FEATURE_E4X:
+ version = cx.getLanguageVersion();
+ return (version == Context.VERSION_DEFAULT
+ || version >= Context.VERSION_1_6);
+
+ case Context.FEATURE_DYNAMIC_SCOPE:
+ return false;
+
+ case Context.FEATURE_STRICT_VARS:
+ return false;
+
+ case Context.FEATURE_STRICT_EVAL:
+ return false;
+
+ case Context.FEATURE_LOCATION_INFORMATION_IN_ERROR:
+ return false;
+
+ case Context.FEATURE_STRICT_MODE:
+ return false;
+
+ case Context.FEATURE_WARNING_AS_ERROR:
+ return false;
+
+ case Context.FEATURE_ENHANCED_JAVA_ACCESS:
+ return false;
+ }
+ // It is a bug to call the method with unknown featureIndex
+ throw new IllegalArgumentException(String.valueOf(featureIndex));
+ }
+
+ private boolean isDom3Present() {
+ Class<?> nodeClass = Kit.classOrNull("org.w3c.dom.Node");
+ if (nodeClass == null) return false;
+ // Check to see whether DOM3 is present; use a new method defined in
+ // DOM3 that is vital to our implementation
+ try {
+ nodeClass.getMethod("getUserData", new Class<?>[] { String.class });
+ return true;
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Provides a default
+ * {@link org.mozilla.javascript.xml.XMLLib.Factory XMLLib.Factory}
+ * to be used by the <code>Context</code> instances produced by this
+ * factory. See {@link Context#getE4xImplementationFactory} for details.
+ *
+ * May return null, in which case E4X functionality is not supported in
+ * Rhino.
+ *
+ * The default implementation now prefers the DOM3 E4X implementation.
+ */
+ protected org.mozilla.javascript.xml.XMLLib.Factory
+ getE4xImplementationFactory()
+ {
+ // Must provide default implementation, rather than abstract method,
+ // so that past implementors of ContextFactory do not fail at runtime
+ // upon invocation of this method.
+ // Note that the default implementation returns null if we
+ // neither have XMLBeans nor a DOM3 implementation present.
+
+ if (isDom3Present()) {
+ return org.mozilla.javascript.xml.XMLLib.Factory.create(
+ "org.mozilla.javascript.xmlimpl.XMLLibImpl"
+ );
+ } else if (Kit.classOrNull("org.apache.xmlbeans.XmlCursor") != null) {
+ return org.mozilla.javascript.xml.XMLLib.Factory.create(
+ "org.mozilla.javascript.xml.impl.xmlbeans.XMLLibImpl"
+ );
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Create class loader for generated classes.
+ * This method creates an instance of the default implementation
+ * of {@link GeneratedClassLoader}. Rhino uses this interface to load
+ * generated JVM classes when no {@link SecurityController}
+ * is installed.
+ * Application can override the method to provide custom class loading.
+ */
+ protected GeneratedClassLoader createClassLoader(ClassLoader parent)
+ {
+ return new DefiningClassLoader(parent);
+ }
+
+ /**
+ * Get ClassLoader to use when searching for Java classes.
+ * Unless it was explicitly initialized with
+ * {@link #initApplicationClassLoader(ClassLoader)} the method returns
+ * null to indicate that Thread.getContextClassLoader() should be used.
+ */
+ public final ClassLoader getApplicationClassLoader()
+ {
+ return applicationClassLoader;
+ }
+
+ /**
+ * Set explicit class loader to use when searching for Java classes.
+ *
+ * @see #getApplicationClassLoader()
+ */
+ public final void initApplicationClassLoader(ClassLoader loader)
+ {
+ if (loader == null)
+ throw new IllegalArgumentException("loader is null");
+ if (!Kit.testIfCanLoadRhinoClasses(loader))
+ throw new IllegalArgumentException(
+ "Loader can not resolve Rhino classes");
+
+ if (this.applicationClassLoader != null)
+ throw new IllegalStateException(
+ "applicationClassLoader can only be set once");
+ checkNotSealed();
+
+ this.applicationClassLoader = loader;
+ }
+
+ /**
+ * Execute top call to script or function.
+ * When the runtime is about to execute a script or function that will
+ * create the first stack frame with scriptable code, it calls this method
+ * to perform the real call. In this way execution of any script
+ * happens inside this function.
+ */
+ protected Object doTopCall(Callable callable,
+ Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ return callable.call(cx, scope, thisObj, args);
+ }
+
+ /**
+ * Implementation of
+ * {@link Context#observeInstructionCount(int instructionCount)}.
+ * This can be used to customize {@link Context} without introducing
+ * additional subclasses.
+ */
+ protected void observeInstructionCount(Context cx, int instructionCount) {
+ }
+
+ protected void onContextCreated(Context cx)
+ {
+ Object listeners = this.listeners;
+ for (int i = 0; ; ++i) {
+ Listener l = (Listener)Kit.getListener(listeners, i);
+ if (l == null)
+ break;
+ l.contextCreated(cx);
+ }
+ }
+
+ protected void onContextReleased(Context cx)
+ {
+ Object listeners = this.listeners;
+ for (int i = 0; ; ++i) {
+ Listener l = (Listener)Kit.getListener(listeners, i);
+ if (l == null)
+ break;
+ l.contextReleased(cx);
+ }
+ }
+
+ public final void addListener(Listener listener)
+ {
+ checkNotSealed();
+ synchronized (listenersLock) {
+ if (disabledListening) {
+ throw new IllegalStateException();
+ }
+ listeners = Kit.addListener(listeners, listener);
+ }
+ }
+
+ public final void removeListener(Listener listener)
+ {
+ checkNotSealed();
+ synchronized (listenersLock) {
+ if (disabledListening) {
+ throw new IllegalStateException();
+ }
+ listeners = Kit.removeListener(listeners, listener);
+ }
+ }
+
+ /**
+ * The method is used only to implement
+ * Context.disableStaticContextListening()
+ */
+ final void disableContextListening()
+ {
+ checkNotSealed();
+ synchronized (listenersLock) {
+ disabledListening = true;
+ listeners = null;
+ }
+ }
+
+ /**
+ * Checks if this is a sealed ContextFactory.
+ * @see #seal()
+ */
+ public final boolean isSealed()
+ {
+ return sealed;
+ }
+
+ /**
+ * Seal this ContextFactory so any attempt to modify it like to add or
+ * remove its listeners will throw an exception.
+ * @see #isSealed()
+ */
+ public final void seal()
+ {
+ checkNotSealed();
+ sealed = true;
+ }
+
+ protected final void checkNotSealed()
+ {
+ if (sealed) throw new IllegalStateException();
+ }
+
+ /**
+ * Call {@link ContextAction#run(Context cx)}
+ * using the {@link Context} instance associated with the current thread.
+ * If no Context is associated with the thread, then
+ * {@link #makeContext()} will be called to construct
+ * new Context instance. The instance will be temporary associated
+ * with the thread during call to {@link ContextAction#run(Context)}.
+ *
+ * @see ContextFactory#call(ContextAction)
+ * @see Context#call(ContextFactory factory, Callable callable,
+ * Scriptable scope, Scriptable thisObj,
+ * Object[] args)
+ */
+ public final Object call(ContextAction action)
+ {
+ return Context.call(this, action);
+ }
+
+ /**
+ * Get a context associated with the current thread, creating one if need
+ * be. The Context stores the execution state of the JavaScript engine, so
+ * it is required that the context be entered before execution may begin.
+ * Once a thread has entered a Context, then getCurrentContext() may be
+ * called to find the context that is associated with the current thread.
+ * <p>
+ * Calling <code>enterContext()</code> will return either the Context
+ * currently associated with the thread, or will create a new context and
+ * associate it with the current thread. Each call to
+ * <code>enterContext()</code> must have a matching call to
+ * {@link Context#exit()}.
+ * <pre>
+ * Context cx = contextFactory.enterContext();
+ * try {
+ * ...
+ * cx.evaluateString(...);
+ * } finally {
+ * Context.exit();
+ * }
+ * </pre>
+ * Instead of using <tt>enterContext()</tt>, <tt>exit()</tt> pair consider
+ * using {@link #call(ContextAction)} which guarantees proper association
+ * of Context instances with the current thread.
+ * With this method the above example becomes:
+ * <pre>
+ * ContextFactory.call(new ContextAction() {
+ * public Object run(Context cx) {
+ * ...
+ * cx.evaluateString(...);
+ * return null;
+ * }
+ * });
+ * </pre>
+ * @return a Context associated with the current thread
+ * @see Context#getCurrentContext()
+ * @see Context#exit()
+ * @see #call(ContextAction)
+ */
+ public Context enterContext()
+ {
+ return enterContext(null);
+ }
+
+ /**
+ * @deprecated use {@link #enterContext()} instead
+ * @return a Context associated with the current thread
+ */
+ public final Context enter()
+ {
+ return enterContext(null);
+ }
+
+ /**
+ * @deprecated Use {@link Context#exit()} instead.
+ */
+ public final void exit()
+ {
+ Context.exit();
+ }
+
+ /**
+ * Get a Context associated with the current thread, using the given
+ * Context if need be.
+ * <p>
+ * The same as <code>enterContext()</code> except that <code>cx</code>
+ * is associated with the current thread and returned if the current thread
+ * has no associated context and <code>cx</code> is not associated with any
+ * other thread.
+ * @param cx a Context to associate with the thread if possible
+ * @return a Context associated with the current thread
+ * @see #enterContext()
+ * @see #call(ContextAction)
+ * @throws IllegalStateException if <code>cx</code> is already associated
+ * with a different thread
+ */
+ public final Context enterContext(Context cx)
+ {
+ return Context.enter(cx, this);
+ }
+}
\ No newline at end of file
diff --git a/src/org/mozilla/javascript/ContextListener.java b/src/org/mozilla/javascript/ContextListener.java
new file mode 100644
index 0000000..5e17145
--- /dev/null
+++ b/src/org/mozilla/javascript/ContextListener.java
@@ -0,0 +1,60 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * @deprecated Embeddings that wish to customize newly created
+ * {@link Context} instances should implement
+ * {@link ContextFactory.Listener}.
+ */
+public interface ContextListener extends ContextFactory.Listener
+{
+
+ /**
+ * @deprecated Rhino runtime never calls the method.
+ */
+ public void contextEntered(Context cx);
+
+ /**
+ * @deprecated Rhino runtime never calls the method.
+ */
+ public void contextExited(Context cx);
+}
diff --git a/src/org/mozilla/javascript/ContinuationPending.java b/src/org/mozilla/javascript/ContinuationPending.java
new file mode 100644
index 0000000..deff4f6
--- /dev/null
+++ b/src/org/mozilla/javascript/ContinuationPending.java
@@ -0,0 +1,99 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * Exception thrown by
+ * {@link org.mozilla.javascript.Context#executeScriptWithContinuations(Script, Scriptable)}
+ * and {@link org.mozilla.javascript.Context#callFunctionWithContinuations(Callable, Scriptable, Object[])}
+ * when execution encounters a continuation captured by
+ * {@link org.mozilla.javascript.Context#captureContinuation()}.
+ * Exception will contain the captured state needed to restart the continuation
+ * with {@link org.mozilla.javascript.Context#resumeContinuation(Object, Scriptable, Object)}.
+ * @author Norris Boyd
+ */
+public class ContinuationPending extends RuntimeException {
+ private static final long serialVersionUID = 4956008116771118856L;
+ private NativeContinuation continuationState;
+ private Object applicationState;
+
+ /**
+ * Construct a ContinuationPending exception. Internal call only;
+ * users of the API should get continuations created on their behalf by
+ * calling {@link org.mozilla.javascript.Context#executeScriptWithContinuations(Script, Scriptable)}
+ * and {@link org.mozilla.javascript.Context#callFunctionWithContinuations(Callable, Scriptable, Object[])}
+ * @param continuationState Internal Continuation object
+ */
+ ContinuationPending(NativeContinuation continuationState) {
+ this.continuationState = continuationState;
+ }
+
+ /**
+ * Get continuation object. The only
+ * use for this object is to be passed to
+ * {@link org.mozilla.javascript.Context#resumeContinuation(Object, Scriptable, Object)}.
+ * @return continuation object
+ */
+ public Object getContinuation() {
+ return continuationState;
+ }
+
+ /**
+ * @return internal continuation state
+ */
+ NativeContinuation getContinuationState() {
+ return continuationState;
+ }
+
+ /**
+ * Store an arbitrary object that applications can use to associate
+ * their state with the continuation.
+ * @param applicationState arbitrary application state
+ */
+ public void setApplicationState(Object applicationState) {
+ this.applicationState = applicationState;
+ }
+
+ /**
+ * @return arbitrary application state
+ */
+ public Object getApplicationState() {
+ return applicationState;
+ }
+}
diff --git a/src/org/mozilla/javascript/DToA.java b/src/org/mozilla/javascript/DToA.java
new file mode 100644
index 0000000..ad2a68a
--- /dev/null
+++ b/src/org/mozilla/javascript/DToA.java
@@ -0,0 +1,1271 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Waldemar Horwat
+ * Roger Lawrence
+ * Attila Szegedi
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/****************************************************************
+ *
+ * The author of this software is David M. Gay.
+ *
+ * Copyright (c) 1991, 2000, 2001 by Lucent Technologies.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ *
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ *
+ ***************************************************************/
+
+package org.mozilla.javascript;
+
+import java.math.BigInteger;
+
+class DToA {
+
+
+/* "-0.0000...(1073 zeros after decimal point)...0001\0" is the longest string that we could produce,
+ * which occurs when printing -5e-324 in binary. We could compute a better estimate of the size of
+ * the output string and malloc fewer bytes depending on d and base, but why bother? */
+
+ private static final int DTOBASESTR_BUFFER_SIZE = 1078;
+
+ private static char BASEDIGIT(int digit) {
+ return (char)((digit >= 10) ? 'a' - 10 + digit : '0' + digit);
+ }
+
+ static final int
+ DTOSTR_STANDARD = 0, /* Either fixed or exponential format; round-trip */
+ DTOSTR_STANDARD_EXPONENTIAL = 1, /* Always exponential format; round-trip */
+ DTOSTR_FIXED = 2, /* Round to <precision> digits after the decimal point; exponential if number is large */
+ DTOSTR_EXPONENTIAL = 3, /* Always exponential format; <precision> significant digits */
+ DTOSTR_PRECISION = 4; /* Either fixed or exponential format; <precision> significant digits */
+
+
+ private static final int Frac_mask = 0xfffff;
+ private static final int Exp_shift = 20;
+ private static final int Exp_msk1 = 0x100000;
+
+ private static final long Frac_maskL = 0xfffffffffffffL;
+ private static final int Exp_shiftL = 52;
+ private static final long Exp_msk1L = 0x10000000000000L;
+
+ private static final int Bias = 1023;
+ private static final int P = 53;
+
+ private static final int Exp_shift1 = 20;
+ private static final int Exp_mask = 0x7ff00000;
+ private static final int Exp_mask_shifted = 0x7ff;
+ private static final int Bndry_mask = 0xfffff;
+ private static final int Log2P = 1;
+
+ private static final int Sign_bit = 0x80000000;
+ private static final int Exp_11 = 0x3ff00000;
+ private static final int Ten_pmax = 22;
+ private static final int Quick_max = 14;
+ private static final int Bletch = 0x10;
+ private static final int Frac_mask1 = 0xfffff;
+ private static final int Int_max = 14;
+ private static final int n_bigtens = 5;
+
+
+ private static final double tens[] = {
+ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
+ 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
+ 1e20, 1e21, 1e22
+ };
+
+ private static final double bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 };
+
+ private static int lo0bits(int y)
+ {
+ int k;
+ int x = y;
+
+ if ((x & 7) != 0) {
+ if ((x & 1) != 0)
+ return 0;
+ if ((x & 2) != 0) {
+ return 1;
+ }
+ return 2;
+ }
+ k = 0;
+ if ((x & 0xffff) == 0) {
+ k = 16;
+ x >>>= 16;
+ }
+ if ((x & 0xff) == 0) {
+ k += 8;
+ x >>>= 8;
+ }
+ if ((x & 0xf) == 0) {
+ k += 4;
+ x >>>= 4;
+ }
+ if ((x & 0x3) == 0) {
+ k += 2;
+ x >>>= 2;
+ }
+ if ((x & 1) == 0) {
+ k++;
+ x >>>= 1;
+ if ((x & 1) == 0)
+ return 32;
+ }
+ return k;
+ }
+
+ /* Return the number (0 through 32) of most significant zero bits in x. */
+ private static int hi0bits(int x)
+ {
+ int k = 0;
+
+ if ((x & 0xffff0000) == 0) {
+ k = 16;
+ x <<= 16;
+ }
+ if ((x & 0xff000000) == 0) {
+ k += 8;
+ x <<= 8;
+ }
+ if ((x & 0xf0000000) == 0) {
+ k += 4;
+ x <<= 4;
+ }
+ if ((x & 0xc0000000) == 0) {
+ k += 2;
+ x <<= 2;
+ }
+ if ((x & 0x80000000) == 0) {
+ k++;
+ if ((x & 0x40000000) == 0)
+ return 32;
+ }
+ return k;
+ }
+
+ private static void stuffBits(byte bits[], int offset, int val)
+ {
+ bits[offset] = (byte)(val >> 24);
+ bits[offset + 1] = (byte)(val >> 16);
+ bits[offset + 2] = (byte)(val >> 8);
+ bits[offset + 3] = (byte)(val);
+ }
+
+ /* Convert d into the form b*2^e, where b is an odd integer. b is the returned
+ * Bigint and e is the returned binary exponent. Return the number of significant
+ * bits in b in bits. d must be finite and nonzero. */
+ private static BigInteger d2b(double d, int[] e, int[] bits)
+ {
+ byte dbl_bits[];
+ int i, k, y, z, de;
+ long dBits = Double.doubleToLongBits(d);
+ int d0 = (int)(dBits >>> 32);
+ int d1 = (int)(dBits);
+
+ z = d0 & Frac_mask;
+ d0 &= 0x7fffffff; /* clear sign bit, which we ignore */
+
+ if ((de = (d0 >>> Exp_shift)) != 0)
+ z |= Exp_msk1;
+
+ if ((y = d1) != 0) {
+ dbl_bits = new byte[8];
+ k = lo0bits(y);
+ y >>>= k;
+ if (k != 0) {
+ stuffBits(dbl_bits, 4, y | z << (32 - k));
+ z >>= k;
+ }
+ else
+ stuffBits(dbl_bits, 4, y);
+ stuffBits(dbl_bits, 0, z);
+ i = (z != 0) ? 2 : 1;
+ }
+ else {
+ // JS_ASSERT(z);
+ dbl_bits = new byte[4];
+ k = lo0bits(z);
+ z >>>= k;
+ stuffBits(dbl_bits, 0, z);
+ k += 32;
+ i = 1;
+ }
+ if (de != 0) {
+ e[0] = de - Bias - (P-1) + k;
+ bits[0] = P - k;
+ }
+ else {
+ e[0] = de - Bias - (P-1) + 1 + k;
+ bits[0] = 32*i - hi0bits(z);
+ }
+ return new BigInteger(dbl_bits);
+ }
+
+ static String JS_dtobasestr(int base, double d)
+ {
+ if (!(2 <= base && base <= 36))
+ throw new IllegalArgumentException("Bad base: "+base);
+
+ /* Check for Infinity and NaN */
+ if (Double.isNaN(d)) {
+ return "NaN";
+ } else if (Double.isInfinite(d)) {
+ return (d > 0.0) ? "Infinity" : "-Infinity";
+ } else if (d == 0) {
+ // ALERT: should it distinguish -0.0 from +0.0 ?
+ return "0";
+ }
+
+ boolean negative;
+ if (d >= 0.0) {
+ negative = false;
+ } else {
+ negative = true;
+ d = -d;
+ }
+
+ /* Get the integer part of d including '-' sign. */
+ String intDigits;
+
+ double dfloor = Math.floor(d);
+ long lfloor = (long)dfloor;
+ if (lfloor == dfloor) {
+ // int part fits long
+ intDigits = Long.toString((negative) ? -lfloor : lfloor, base);
+ } else {
+ // BigInteger should be used
+ long floorBits = Double.doubleToLongBits(dfloor);
+ int exp = (int)(floorBits >> Exp_shiftL) & Exp_mask_shifted;
+ long mantissa;
+ if (exp == 0) {
+ mantissa = (floorBits & Frac_maskL) << 1;
+ } else {
+ mantissa = (floorBits & Frac_maskL) | Exp_msk1L;
+ }
+ if (negative) {
+ mantissa = -mantissa;
+ }
+ exp -= 1075;
+ BigInteger x = BigInteger.valueOf(mantissa);
+ if (exp > 0) {
+ x = x.shiftLeft(exp);
+ } else if (exp < 0) {
+ x = x.shiftRight(-exp);
+ }
+ intDigits = x.toString(base);
+ }
+
+ if (d == dfloor) {
+ // No fraction part
+ return intDigits;
+ } else {
+ /* We have a fraction. */
+
+ char[] buffer; /* The output string */
+ int p; /* index to current position in the buffer */
+ int digit;
+ double df; /* The fractional part of d */
+ BigInteger b;
+
+ buffer = new char[DTOBASESTR_BUFFER_SIZE];
+ p = 0;
+ df = d - dfloor;
+
+ long dBits = Double.doubleToLongBits(d);
+ int word0 = (int)(dBits >> 32);
+ int word1 = (int)(dBits);
+
+ int[] e = new int[1];
+ int[] bbits = new int[1];
+
+ b = d2b(df, e, bbits);
+// JS_ASSERT(e < 0);
+ /* At this point df = b * 2^e. e must be less than zero because 0 < df < 1. */
+
+ int s2 = -(word0 >>> Exp_shift1 & Exp_mask >> Exp_shift1);
+ if (s2 == 0)
+ s2 = -1;
+ s2 += Bias + P;
+ /* 1/2^s2 = (nextDouble(d) - d)/2 */
+// JS_ASSERT(-s2 < e);
+ BigInteger mlo = BigInteger.valueOf(1);
+ BigInteger mhi = mlo;
+ if ((word1 == 0) && ((word0 & Bndry_mask) == 0)
+ && ((word0 & (Exp_mask & Exp_mask << 1)) != 0)) {
+ /* The special case. Here we want to be within a quarter of the last input
+ significant digit instead of one half of it when the output string's value is less than d. */
+ s2 += Log2P;
+ mhi = BigInteger.valueOf(1<<Log2P);
+ }
+
+ b = b.shiftLeft(e[0] + s2);
+ BigInteger s = BigInteger.valueOf(1);
+ s = s.shiftLeft(s2);
+ /* At this point we have the following:
+ * s = 2^s2;
+ * 1 > df = b/2^s2 > 0;
+ * (d - prevDouble(d))/2 = mlo/2^s2;
+ * (nextDouble(d) - d)/2 = mhi/2^s2. */
+ BigInteger bigBase = BigInteger.valueOf(base);
+
+ boolean done = false;
+ do {
+ b = b.multiply(bigBase);
+ BigInteger[] divResult = b.divideAndRemainder(s);
+ b = divResult[1];
+ digit = (char)(divResult[0].intValue());
+ if (mlo == mhi)
+ mlo = mhi = mlo.multiply(bigBase);
+ else {
+ mlo = mlo.multiply(bigBase);
+ mhi = mhi.multiply(bigBase);
+ }
+
+ /* Do we yet have the shortest string that will round to d? */
+ int j = b.compareTo(mlo);
+ /* j is b/2^s2 compared with mlo/2^s2. */
+ BigInteger delta = s.subtract(mhi);
+ int j1 = (delta.signum() <= 0) ? 1 : b.compareTo(delta);
+ /* j1 is b/2^s2 compared with 1 - mhi/2^s2. */
+ if (j1 == 0 && ((word1 & 1) == 0)) {
+ if (j > 0)
+ digit++;
+ done = true;
+ } else
+ if (j < 0 || (j == 0 && ((word1 & 1) == 0))) {
+ if (j1 > 0) {
+ /* Either dig or dig+1 would work here as the least significant digit.
+ Use whichever would produce an output value closer to d. */
+ b = b.shiftLeft(1);
+ j1 = b.compareTo(s);
+ if (j1 > 0) /* The even test (|| (j1 == 0 && (digit & 1))) is not here because it messes up odd base output
+ * such as 3.5 in base 3. */
+ digit++;
+ }
+ done = true;
+ } else if (j1 > 0) {
+ digit++;
+ done = true;
+ }
+// JS_ASSERT(digit < (uint32)base);
+ buffer[p++] = BASEDIGIT(digit);
+ } while (!done);
+
+ StringBuffer sb = new StringBuffer(intDigits.length() + 1 + p);
+ sb.append(intDigits);
+ sb.append('.');
+ sb.append(buffer, 0, p);
+ return sb.toString();
+ }
+
+ }
+
+ /* dtoa for IEEE arithmetic (dmg): convert double to ASCII string.
+ *
+ * Inspired by "How to Print Floating-Point Numbers Accurately" by
+ * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 92-101].
+ *
+ * Modifications:
+ * 1. Rather than iterating, we use a simple numeric overestimate
+ * to determine k = floor(log10(d)). We scale relevant
+ * quantities using O(log2(k)) rather than O(k) multiplications.
+ * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't
+ * try to generate digits strictly left to right. Instead, we
+ * compute with fewer bits and propagate the carry if necessary
+ * when rounding the final digit up. This is often faster.
+ * 3. Under the assumption that input will be rounded nearest,
+ * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22.
+ * That is, we allow equality in stopping tests when the
+ * round-nearest rule will give the same floating-point value
+ * as would satisfaction of the stopping test with strict
+ * inequality.
+ * 4. We remove common factors of powers of 2 from relevant
+ * quantities.
+ * 5. When converting floating-point integers less than 1e16,
+ * we use floating-point arithmetic rather than resorting
+ * to multiple-precision integers.
+ * 6. When asked to produce fewer than 15 digits, we first try
+ * to get by with floating-point arithmetic; we resort to
+ * multiple-precision integer arithmetic only if we cannot
+ * guarantee that the floating-point calculation has given
+ * the correctly rounded result. For k requested digits and
+ * "uniformly" distributed input, the probability is
+ * something like 10^(k-15) that we must resort to the Long
+ * calculation.
+ */
+
+ static int word0(double d)
+ {
+ long dBits = Double.doubleToLongBits(d);
+ return (int)(dBits >> 32);
+ }
+
+ static double setWord0(double d, int i)
+ {
+ long dBits = Double.doubleToLongBits(d);
+ dBits = ((long)i << 32) | (dBits & 0x0FFFFFFFFL);
+ return Double.longBitsToDouble(dBits);
+ }
+
+ static int word1(double d)
+ {
+ long dBits = Double.doubleToLongBits(d);
+ return (int)(dBits);
+ }
+
+ /* Return b * 5^k. k must be nonnegative. */
+ // XXXX the C version built a cache of these
+ static BigInteger pow5mult(BigInteger b, int k)
+ {
+ return b.multiply(BigInteger.valueOf(5).pow(k));
+ }
+
+ static boolean roundOff(StringBuffer buf)
+ {
+ int i = buf.length();
+ while (i != 0) {
+ --i;
+ char c = buf.charAt(i);
+ if (c != '9') {
+ buf.setCharAt(i, (char)(c + 1));
+ buf.setLength(i + 1);
+ return false;
+ }
+ }
+ buf.setLength(0);
+ return true;
+ }
+
+ /* Always emits at least one digit. */
+ /* If biasUp is set, then rounding in modes 2 and 3 will round away from zero
+ * when the number is exactly halfway between two representable values. For example,
+ * rounding 2.5 to zero digits after the decimal point will return 3 and not 2.
+ * 2.49 will still round to 2, and 2.51 will still round to 3. */
+ /* bufsize should be at least 20 for modes 0 and 1. For the other modes,
+ * bufsize should be two greater than the maximum number of output characters expected. */
+ static int
+ JS_dtoa(double d, int mode, boolean biasUp, int ndigits,
+ boolean[] sign, StringBuffer buf)
+ {
+ /* Arguments ndigits, decpt, sign are similar to those
+ of ecvt and fcvt; trailing zeros are suppressed from
+ the returned string. If not null, *rve is set to point
+ to the end of the return value. If d is +-Infinity or NaN,
+ then *decpt is set to 9999.
+
+ mode:
+ 0 ==> shortest string that yields d when read in
+ and rounded to nearest.
+ 1 ==> like 0, but with Steele & White stopping rule;
+ e.g. with IEEE P754 arithmetic , mode 0 gives
+ 1e23 whereas mode 1 gives 9.999999999999999e22.
+ 2 ==> max(1,ndigits) significant digits. This gives a
+ return value similar to that of ecvt, except
+ that trailing zeros are suppressed.
+ 3 ==> through ndigits past the decimal point. This
+ gives a return value similar to that from fcvt,
+ except that trailing zeros are suppressed, and
+ ndigits can be negative.
+ 4-9 should give the same return values as 2-3, i.e.,
+ 4 <= mode <= 9 ==> same return as mode
+ 2 + (mode & 1). These modes are mainly for
+ debugging; often they run slower but sometimes
+ faster than modes 2-3.
+ 4,5,8,9 ==> left-to-right digit generation.
+ 6-9 ==> don't try fast floating-point estimate
+ (if applicable).
+
+ Values of mode other than 0-9 are treated as mode 0.
+
+ Sufficient space is allocated to the return value
+ to hold the suppressed trailing zeros.
+ */
+
+ int b2, b5, i, ieps, ilim, ilim0, ilim1,
+ j, j1, k, k0, m2, m5, s2, s5;
+ char dig;
+ long L;
+ long x;
+ BigInteger b, b1, delta, mlo, mhi, S;
+ int[] be = new int[1];
+ int[] bbits = new int[1];
+ double d2, ds, eps;
+ boolean spec_case, denorm, k_check, try_quick, leftright;
+
+ if ((word0(d) & Sign_bit) != 0) {
+ /* set sign for everything, including 0's and NaNs */
+ sign[0] = true;
+ // word0(d) &= ~Sign_bit; /* clear sign bit */
+ d = setWord0(d, word0(d) & ~Sign_bit);
+ }
+ else
+ sign[0] = false;
+
+ if ((word0(d) & Exp_mask) == Exp_mask) {
+ /* Infinity or NaN */
+ buf.append(((word1(d) == 0) && ((word0(d) & Frac_mask) == 0)) ? "Infinity" : "NaN");
+ return 9999;
+ }
+ if (d == 0) {
+// no_digits:
+ buf.setLength(0);
+ buf.append('0'); /* copy "0" to buffer */
+ return 1;
+ }
+
+ b = d2b(d, be, bbits);
+ if ((i = (word0(d) >>> Exp_shift1 & (Exp_mask>>Exp_shift1))) != 0) {
+ d2 = setWord0(d, (word0(d) & Frac_mask1) | Exp_11);
+ /* log(x) ~=~ log(1.5) + (x-1.5)/1.5
+ * log10(x) = log(x) / log(10)
+ * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10))
+ * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2)
+ *
+ * This suggests computing an approximation k to log10(d) by
+ *
+ * k = (i - Bias)*0.301029995663981
+ * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 );
+ *
+ * We want k to be too large rather than too small.
+ * The error in the first-order Taylor series approximation
+ * is in our favor, so we just round up the constant enough
+ * to compensate for any error in the multiplication of
+ * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077,
+ * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14,
+ * adding 1e-13 to the constant term more than suffices.
+ * Hence we adjust the constant term to 0.1760912590558.
+ * (We could get a more accurate k by invoking log10,
+ * but this is probably not worthwhile.)
+ */
+ i -= Bias;
+ denorm = false;
+ }
+ else {
+ /* d is denormalized */
+ i = bbits[0] + be[0] + (Bias + (P-1) - 1);
+ x = (i > 32) ? word0(d) << (64 - i) | word1(d) >>> (i - 32) : word1(d) << (32 - i);
+// d2 = x;
+// word0(d2) -= 31*Exp_msk1; /* adjust exponent */
+ d2 = setWord0(x, word0(x) - 31*Exp_msk1);
+ i -= (Bias + (P-1) - 1) + 1;
+ denorm = true;
+ }
+ /* At this point d = f*2^i, where 1 <= f < 2. d2 is an approximation of f. */
+ ds = (d2-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981;
+ k = (int)ds;
+ if (ds < 0.0 && ds != k)
+ k--; /* want k = floor(ds) */
+ k_check = true;
+ if (k >= 0 && k <= Ten_pmax) {
+ if (d < tens[k])
+ k--;
+ k_check = false;
+ }
+ /* At this point floor(log10(d)) <= k <= floor(log10(d))+1.
+ If k_check is zero, we're guaranteed that k = floor(log10(d)). */
+ j = bbits[0] - i - 1;
+ /* At this point d = b/2^j, where b is an odd integer. */
+ if (j >= 0) {
+ b2 = 0;
+ s2 = j;
+ }
+ else {
+ b2 = -j;
+ s2 = 0;
+ }
+ if (k >= 0) {
+ b5 = 0;
+ s5 = k;
+ s2 += k;
+ }
+ else {
+ b2 -= k;
+ b5 = -k;
+ s5 = 0;
+ }
+ /* At this point d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5), where b is an odd integer,
+ b2 >= 0, b5 >= 0, s2 >= 0, and s5 >= 0. */
+ if (mode < 0 || mode > 9)
+ mode = 0;
+ try_quick = true;
+ if (mode > 5) {
+ mode -= 4;
+ try_quick = false;
+ }
+ leftright = true;
+ ilim = ilim1 = 0;
+ switch(mode) {
+ case 0:
+ case 1:
+ ilim = ilim1 = -1;
+ i = 18;
+ ndigits = 0;
+ break;
+ case 2:
+ leftright = false;
+ /* no break */
+ case 4:
+ if (ndigits <= 0)
+ ndigits = 1;
+ ilim = ilim1 = i = ndigits;
+ break;
+ case 3:
+ leftright = false;
+ /* no break */
+ case 5:
+ i = ndigits + k + 1;
+ ilim = i;
+ ilim1 = i - 1;
+ if (i <= 0)
+ i = 1;
+ }
+ /* ilim is the maximum number of significant digits we want, based on k and ndigits. */
+ /* ilim1 is the maximum number of significant digits we want, based on k and ndigits,
+ when it turns out that k was computed too high by one. */
+
+ boolean fast_failed = false;
+ if (ilim >= 0 && ilim <= Quick_max && try_quick) {
+
+ /* Try to get by with floating-point arithmetic. */
+
+ i = 0;
+ d2 = d;
+ k0 = k;
+ ilim0 = ilim;
+ ieps = 2; /* conservative */
+ /* Divide d by 10^k, keeping track of the roundoff error and avoiding overflows. */
+ if (k > 0) {
+ ds = tens[k&0xf];
+ j = k >> 4;
+ if ((j & Bletch) != 0) {
+ /* prevent overflows */
+ j &= Bletch - 1;
+ d /= bigtens[n_bigtens-1];
+ ieps++;
+ }
+ for(; (j != 0); j >>= 1, i++)
+ if ((j & 1) != 0) {
+ ieps++;
+ ds *= bigtens[i];
+ }
+ d /= ds;
+ }
+ else if ((j1 = -k) != 0) {
+ d *= tens[j1 & 0xf];
+ for(j = j1 >> 4; (j != 0); j >>= 1, i++)
+ if ((j & 1) != 0) {
+ ieps++;
+ d *= bigtens[i];
+ }
+ }
+ /* Check that k was computed correctly. */
+ if (k_check && d < 1.0 && ilim > 0) {
+ if (ilim1 <= 0)
+ fast_failed = true;
+ else {
+ ilim = ilim1;
+ k--;
+ d *= 10.;
+ ieps++;
+ }
+ }
+ /* eps bounds the cumulative error. */
+// eps = ieps*d + 7.0;
+// word0(eps) -= (P-1)*Exp_msk1;
+ eps = ieps*d + 7.0;
+ eps = setWord0(eps, word0(eps) - (P-1)*Exp_msk1);
+ if (ilim == 0) {
+ S = mhi = null;
+ d -= 5.0;
+ if (d > eps) {
+ buf.append('1');
+ k++;
+ return k + 1;
+ }
+ if (d < -eps) {
+ buf.setLength(0);
+ buf.append('0'); /* copy "0" to buffer */
+ return 1;
+ }
+ fast_failed = true;
+ }
+ if (!fast_failed) {
+ fast_failed = true;
+ if (leftright) {
+ /* Use Steele & White method of only
+ * generating digits needed.
+ */
+ eps = 0.5/tens[ilim-1] - eps;
+ for(i = 0;;) {
+ L = (long)d;
+ d -= L;
+ buf.append((char)('0' + L));
+ if (d < eps) {
+ return k + 1;
+ }
+ if (1.0 - d < eps) {
+// goto bump_up;
+ char lastCh;
+ while (true) {
+ lastCh = buf.charAt(buf.length() - 1);
+ buf.setLength(buf.length() - 1);
+ if (lastCh != '9') break;
+ if (buf.length() == 0) {
+ k++;
+ lastCh = '0';
+ break;
+ }
+ }
+ buf.append((char)(lastCh + 1));
+ return k + 1;
+ }
+ if (++i >= ilim)
+ break;
+ eps *= 10.0;
+ d *= 10.0;
+ }
+ }
+ else {
+ /* Generate ilim digits, then fix them up. */
+ eps *= tens[ilim-1];
+ for(i = 1;; i++, d *= 10.0) {
+ L = (long)d;
+ d -= L;
+ buf.append((char)('0' + L));
+ if (i == ilim) {
+ if (d > 0.5 + eps) {
+// goto bump_up;
+ char lastCh;
+ while (true) {
+ lastCh = buf.charAt(buf.length() - 1);
+ buf.setLength(buf.length() - 1);
+ if (lastCh != '9') break;
+ if (buf.length() == 0) {
+ k++;
+ lastCh = '0';
+ break;
+ }
+ }
+ buf.append((char)(lastCh + 1));
+ return k + 1;
+ }
+ else
+ if (d < 0.5 - eps) {
+ stripTrailingZeroes(buf);
+// while(*--s == '0') ;
+// s++;
+ return k + 1;
+ }
+ break;
+ }
+ }
+ }
+ }
+ if (fast_failed) {
+ buf.setLength(0);
+ d = d2;
+ k = k0;
+ ilim = ilim0;
+ }
+ }
+
+ /* Do we have a "small" integer? */
+
+ if (be[0] >= 0 && k <= Int_max) {
+ /* Yes. */
+ ds = tens[k];
+ if (ndigits < 0 && ilim <= 0) {
+ S = mhi = null;
+ if (ilim < 0 || d < 5*ds || (!biasUp && d == 5*ds)) {
+ buf.setLength(0);
+ buf.append('0'); /* copy "0" to buffer */
+ return 1;
+ }
+ buf.append('1');
+ k++;
+ return k + 1;
+ }
+ for(i = 1;; i++) {
+ L = (long) (d / ds);
+ d -= L*ds;
+ buf.append((char)('0' + L));
+ if (i == ilim) {
+ d += d;
+ if ((d > ds) || (d == ds && (((L & 1) != 0) || biasUp))) {
+// bump_up:
+// while(*--s == '9')
+// if (s == buf) {
+// k++;
+// *s = '0';
+// break;
+// }
+// ++*s++;
+ char lastCh;
+ while (true) {
+ lastCh = buf.charAt(buf.length() - 1);
+ buf.setLength(buf.length() - 1);
+ if (lastCh != '9') break;
+ if (buf.length() == 0) {
+ k++;
+ lastCh = '0';
+ break;
+ }
+ }
+ buf.append((char)(lastCh + 1));
+ }
+ break;
+ }
+ d *= 10.0;
+ if (d == 0)
+ break;
+ }
+ return k + 1;
+ }
+
+ m2 = b2;
+ m5 = b5;
+ mhi = mlo = null;
+ if (leftright) {
+ if (mode < 2) {
+ i = (denorm) ? be[0] + (Bias + (P-1) - 1 + 1) : 1 + P - bbits[0];
+ /* i is 1 plus the number of trailing zero bits in d's significand. Thus,
+ (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 lsb of d)/10^k. */
+ }
+ else {
+ j = ilim - 1;
+ if (m5 >= j)
+ m5 -= j;
+ else {
+ s5 += j -= m5;
+ b5 += j;
+ m5 = 0;
+ }
+ if ((i = ilim) < 0) {
+ m2 -= i;
+ i = 0;
+ }
+ /* (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 * 10^(1-ilim))/10^k. */
+ }
+ b2 += i;
+ s2 += i;
+ mhi = BigInteger.valueOf(1);
+ /* (mhi * 2^m2 * 5^m5) / (2^s2 * 5^s5) = one-half of last printed (when mode >= 2) or
+ input (when mode < 2) significant digit, divided by 10^k. */
+ }
+ /* We still have d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5). Reduce common factors in
+ b2, m2, and s2 without changing the equalities. */
+ if (m2 > 0 && s2 > 0) {
+ i = (m2 < s2) ? m2 : s2;
+ b2 -= i;
+ m2 -= i;
+ s2 -= i;
+ }
+
+ /* Fold b5 into b and m5 into mhi. */
+ if (b5 > 0) {
+ if (leftright) {
+ if (m5 > 0) {
+ mhi = pow5mult(mhi, m5);
+ b1 = mhi.multiply(b);
+ b = b1;
+ }
+ if ((j = b5 - m5) != 0)
+ b = pow5mult(b, j);
+ }
+ else
+ b = pow5mult(b, b5);
+ }
+ /* Now we have d/10^k = (b * 2^b2) / (2^s2 * 5^s5) and
+ (mhi * 2^m2) / (2^s2 * 5^s5) = one-half of last printed or input significant digit, divided by 10^k. */
+
+ S = BigInteger.valueOf(1);
+ if (s5 > 0)
+ S = pow5mult(S, s5);
+ /* Now we have d/10^k = (b * 2^b2) / (S * 2^s2) and
+ (mhi * 2^m2) / (S * 2^s2) = one-half of last printed or input significant digit, divided by 10^k. */
+
+ /* Check for special case that d is a normalized power of 2. */
+ spec_case = false;
+ if (mode < 2) {
+ if ( (word1(d) == 0) && ((word0(d) & Bndry_mask) == 0)
+ && ((word0(d) & (Exp_mask & Exp_mask << 1)) != 0)
+ ) {
+ /* The special case. Here we want to be within a quarter of the last input
+ significant digit instead of one half of it when the decimal output string's value is less than d. */
+ b2 += Log2P;
+ s2 += Log2P;
+ spec_case = true;
+ }
+ }
+
+ /* Arrange for convenient computation of quotients:
+ * shift left if necessary so divisor has 4 leading 0 bits.
+ *
+ * Perhaps we should just compute leading 28 bits of S once
+ * and for all and pass them and a shift to quorem, so it
+ * can do shifts and ors to compute the numerator for q.
+ */
+ byte [] S_bytes = S.toByteArray();
+ int S_hiWord = 0;
+ for (int idx = 0; idx < 4; idx++) {
+ S_hiWord = (S_hiWord << 8);
+ if (idx < S_bytes.length)
+ S_hiWord |= (S_bytes[idx] & 0xFF);
+ }
+ if ((i = (((s5 != 0) ? 32 - hi0bits(S_hiWord) : 1) + s2) & 0x1f) != 0)
+ i = 32 - i;
+ /* i is the number of leading zero bits in the most significant word of S*2^s2. */
+ if (i > 4) {
+ i -= 4;
+ b2 += i;
+ m2 += i;
+ s2 += i;
+ }
+ else if (i < 4) {
+ i += 28;
+ b2 += i;
+ m2 += i;
+ s2 += i;
+ }
+ /* Now S*2^s2 has exactly four leading zero bits in its most significant word. */
+ if (b2 > 0)
+ b = b.shiftLeft(b2);
+ if (s2 > 0)
+ S = S.shiftLeft(s2);
+ /* Now we have d/10^k = b/S and
+ (mhi * 2^m2) / S = maximum acceptable error, divided by 10^k. */
+ if (k_check) {
+ if (b.compareTo(S) < 0) {
+ k--;
+ b = b.multiply(BigInteger.valueOf(10)); /* we botched the k estimate */
+ if (leftright)
+ mhi = mhi.multiply(BigInteger.valueOf(10));
+ ilim = ilim1;
+ }
+ }
+ /* At this point 1 <= d/10^k = b/S < 10. */
+
+ if (ilim <= 0 && mode > 2) {
+ /* We're doing fixed-mode output and d is less than the minimum nonzero output in this mode.
+ Output either zero or the minimum nonzero output depending on which is closer to d. */
+ if ((ilim < 0 )
+ || ((i = b.compareTo(S = S.multiply(BigInteger.valueOf(5)))) < 0)
+ || ((i == 0 && !biasUp))) {
+ /* Always emit at least one digit. If the number appears to be zero
+ using the current mode, then emit one '0' digit and set decpt to 1. */
+ /*no_digits:
+ k = -1 - ndigits;
+ goto ret; */
+ buf.setLength(0);
+ buf.append('0'); /* copy "0" to buffer */
+ return 1;
+// goto no_digits;
+ }
+// one_digit:
+ buf.append('1');
+ k++;
+ return k + 1;
+ }
+ if (leftright) {
+ if (m2 > 0)
+ mhi = mhi.shiftLeft(m2);
+
+ /* Compute mlo -- check for special case
+ * that d is a normalized power of 2.
+ */
+
+ mlo = mhi;
+ if (spec_case) {
+ mhi = mlo;
+ mhi = mhi.shiftLeft(Log2P);
+ }
+ /* mlo/S = maximum acceptable error, divided by 10^k, if the output is less than d. */
+ /* mhi/S = maximum acceptable error, divided by 10^k, if the output is greater than d. */
+
+ for(i = 1;;i++) {
+ BigInteger[] divResult = b.divideAndRemainder(S);
+ b = divResult[1];
+ dig = (char)(divResult[0].intValue() + '0');
+ /* Do we yet have the shortest decimal string
+ * that will round to d?
+ */
+ j = b.compareTo(mlo);
+ /* j is b/S compared with mlo/S. */
+ delta = S.subtract(mhi);
+ j1 = (delta.signum() <= 0) ? 1 : b.compareTo(delta);
+ /* j1 is b/S compared with 1 - mhi/S. */
+ if ((j1 == 0) && (mode == 0) && ((word1(d) & 1) == 0)) {
+ if (dig == '9') {
+ buf.append('9');
+ if (roundOff(buf)) {
+ k++;
+ buf.append('1');
+ }
+ return k + 1;
+// goto round_9_up;
+ }
+ if (j > 0)
+ dig++;
+ buf.append(dig);
+ return k + 1;
+ }
+ if ((j < 0)
+ || ((j == 0)
+ && (mode == 0)
+ && ((word1(d) & 1) == 0)
+ )) {
+ if (j1 > 0) {
+ /* Either dig or dig+1 would work here as the least significant decimal digit.
+ Use whichever would produce a decimal value closer to d. */
+ b = b.shiftLeft(1);
+ j1 = b.compareTo(S);
+ if (((j1 > 0) || (j1 == 0 && (((dig & 1) == 1) || biasUp)))
+ && (dig++ == '9')) {
+ buf.append('9');
+ if (roundOff(buf)) {
+ k++;
+ buf.append('1');
+ }
+ return k + 1;
+// goto round_9_up;
+ }
+ }
+ buf.append(dig);
+ return k + 1;
+ }
+ if (j1 > 0) {
+ if (dig == '9') { /* possible if i == 1 */
+// round_9_up:
+// *s++ = '9';
+// goto roundoff;
+ buf.append('9');
+ if (roundOff(buf)) {
+ k++;
+ buf.append('1');
+ }
+ return k + 1;
+ }
+ buf.append((char)(dig + 1));
+ return k + 1;
+ }
+ buf.append(dig);
+ if (i == ilim)
+ break;
+ b = b.multiply(BigInteger.valueOf(10));
+ if (mlo == mhi)
+ mlo = mhi = mhi.multiply(BigInteger.valueOf(10));
+ else {
+ mlo = mlo.multiply(BigInteger.valueOf(10));
+ mhi = mhi.multiply(BigInteger.valueOf(10));
+ }
+ }
+ }
+ else
+ for(i = 1;; i++) {
+// (char)(dig = quorem(b,S) + '0');
+ BigInteger[] divResult = b.divideAndRemainder(S);
+ b = divResult[1];
+ dig = (char)(divResult[0].intValue() + '0');
+ buf.append(dig);
+ if (i >= ilim)
+ break;
+ b = b.multiply(BigInteger.valueOf(10));
+ }
+
+ /* Round off last digit */
+
+ b = b.shiftLeft(1);
+ j = b.compareTo(S);
+ if ((j > 0) || (j == 0 && (((dig & 1) == 1) || biasUp))) {
+// roundoff:
+// while(*--s == '9')
+// if (s == buf) {
+// k++;
+// *s++ = '1';
+// goto ret;
+// }
+// ++*s++;
+ if (roundOff(buf)) {
+ k++;
+ buf.append('1');
+ return k + 1;
+ }
+ }
+ else {
+ stripTrailingZeroes(buf);
+// while(*--s == '0') ;
+// s++;
+ }
+// ret:
+// Bfree(S);
+// if (mhi) {
+// if (mlo && mlo != mhi)
+// Bfree(mlo);
+// Bfree(mhi);
+// }
+// ret1:
+// Bfree(b);
+// JS_ASSERT(s < buf + bufsize);
+ return k + 1;
+ }
+
+ private static void
+ stripTrailingZeroes(StringBuffer buf)
+ {
+// while(*--s == '0') ;
+// s++;
+ int bl = buf.length();
+ while(bl-->0 && buf.charAt(bl) == '0') {
+ // empty
+ }
+ buf.setLength(bl + 1);
+ }
+
+ /* Mapping of JSDToStrMode -> JS_dtoa mode */
+ private static final int dtoaModes[] = {
+ 0, /* DTOSTR_STANDARD */
+ 0, /* DTOSTR_STANDARD_EXPONENTIAL, */
+ 3, /* DTOSTR_FIXED, */
+ 2, /* DTOSTR_EXPONENTIAL, */
+ 2}; /* DTOSTR_PRECISION */
+
+ static void
+ JS_dtostr(StringBuffer buffer, int mode, int precision, double d)
+ {
+ int decPt; /* Position of decimal point relative to first digit returned by JS_dtoa */
+ boolean[] sign = new boolean[1]; /* true if the sign bit was set in d */
+ int nDigits; /* Number of significand digits returned by JS_dtoa */
+
+// JS_ASSERT(bufferSize >= (size_t)(mode <= DTOSTR_STANDARD_EXPONENTIAL ? DTOSTR_STANDARD_BUFFER_SIZE :
+// DTOSTR_VARIABLE_BUFFER_SIZE(precision)));
+
+ if (mode == DTOSTR_FIXED && (d >= 1e21 || d <= -1e21))
+ mode = DTOSTR_STANDARD; /* Change mode here rather than below because the buffer may not be large enough to hold a large integer. */
+
+ decPt = JS_dtoa(d, dtoaModes[mode], mode >= DTOSTR_FIXED, precision, sign, buffer);
+ nDigits = buffer.length();
+
+ /* If Infinity, -Infinity, or NaN, return the string regardless of the mode. */
+ if (decPt != 9999) {
+ boolean exponentialNotation = false;
+ int minNDigits = 0; /* Minimum number of significand digits required by mode and precision */
+ int p;
+
+ switch (mode) {
+ case DTOSTR_STANDARD:
+ if (decPt < -5 || decPt > 21)
+ exponentialNotation = true;
+ else
+ minNDigits = decPt;
+ break;
+
+ case DTOSTR_FIXED:
+ if (precision >= 0)
+ minNDigits = decPt + precision;
+ else
+ minNDigits = decPt;
+ break;
+
+ case DTOSTR_EXPONENTIAL:
+// JS_ASSERT(precision > 0);
+ minNDigits = precision;
+ /* Fall through */
+ case DTOSTR_STANDARD_EXPONENTIAL:
+ exponentialNotation = true;
+ break;
+
+ case DTOSTR_PRECISION:
+// JS_ASSERT(precision > 0);
+ minNDigits = precision;
+ if (decPt < -5 || decPt > precision)
+ exponentialNotation = true;
+ break;
+ }
+
+ /* If the number has fewer than minNDigits, pad it with zeros at the end */
+ if (nDigits < minNDigits) {
+ p = minNDigits;
+ nDigits = minNDigits;
+ do {
+ buffer.append('0');
+ } while (buffer.length() != p);
+ }
+
+ if (exponentialNotation) {
+ /* Insert a decimal point if more than one significand digit */
+ if (nDigits != 1) {
+ buffer.insert(1, '.');
+ }
+ buffer.append('e');
+ if ((decPt - 1) >= 0)
+ buffer.append('+');
+ buffer.append(decPt - 1);
+// JS_snprintf(numEnd, bufferSize - (numEnd - buffer), "e%+d", decPt-1);
+ } else if (decPt != nDigits) {
+ /* Some kind of a fraction in fixed notation */
+// JS_ASSERT(decPt <= nDigits);
+ if (decPt > 0) {
+ /* dd...dd . dd...dd */
+ buffer.insert(decPt, '.');
+ } else {
+ /* 0 . 00...00dd...dd */
+ for (int i = 0; i < 1 - decPt; i++)
+ buffer.insert(0, '0');
+ buffer.insert(1, '.');
+ }
+ }
+ }
+
+ /* If negative and neither -0.0 nor NaN, output a leading '-'. */
+ if (sign[0] &&
+ !(word0(d) == Sign_bit && word1(d) == 0) &&
+ !((word0(d) & Exp_mask) == Exp_mask &&
+ ((word1(d) != 0) || ((word0(d) & Frac_mask) != 0)))) {
+ buffer.insert(0, '-');
+ }
+ }
+
+}
+
diff --git a/src/org/mozilla/javascript/Decompiler.java b/src/org/mozilla/javascript/Decompiler.java
new file mode 100644
index 0000000..8547d37
--- /dev/null
+++ b/src/org/mozilla/javascript/Decompiler.java
@@ -0,0 +1,918 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Mike Ang
+ * Igor Bukanov
+ * Bob Jervis
+ * Mike McCabe
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * The following class save decompilation information about the source.
+ * Source information is returned from the parser as a String
+ * associated with function nodes and with the toplevel script. When
+ * saved in the constant pool of a class, this string will be UTF-8
+ * encoded, and token values will occupy a single byte.
+
+ * Source is saved (mostly) as token numbers. The tokens saved pretty
+ * much correspond to the token stream of a 'canonical' representation
+ * of the input program, as directed by the parser. (There were a few
+ * cases where tokens could have been left out where decompiler could
+ * easily reconstruct them, but I left them in for clarity). (I also
+ * looked adding source collection to TokenStream instead, where I
+ * could have limited the changes to a few lines in getToken... but
+ * this wouldn't have saved any space in the resulting source
+ * representation, and would have meant that I'd have to duplicate
+ * parser logic in the decompiler to disambiguate situations where
+ * newlines are important.) The function decompile expands the
+ * tokens back into their string representations, using simple
+ * lookahead to correct spacing and indentation.
+ *
+ * Assignments are saved as two-token pairs (Token.ASSIGN, op). Number tokens
+ * are stored inline, as a NUMBER token, a character representing the type, and
+ * either 1 or 4 characters representing the bit-encoding of the number. String
+ * types NAME, STRING and OBJECT are currently stored as a token type,
+ * followed by a character giving the length of the string (assumed to
+ * be less than 2^16), followed by the characters of the string
+ * inlined into the source string. Changing this to some reference to
+ * to the string in the compiled class' constant pool would probably
+ * save a lot of space... but would require some method of deriving
+ * the final constant pool entry from information available at parse
+ * time.
+ */
+public class Decompiler
+{
+ /**
+ * Flag to indicate that the decompilation should omit the
+ * function header and trailing brace.
+ */
+ public static final int ONLY_BODY_FLAG = 1 << 0;
+
+ /**
+ * Flag to indicate that the decompilation generates toSource result.
+ */
+ public static final int TO_SOURCE_FLAG = 1 << 1;
+
+ /**
+ * Decompilation property to specify initial ident value.
+ */
+ public static final int INITIAL_INDENT_PROP = 1;
+
+ /**
+ * Decompilation property to specify default identation offset.
+ */
+ public static final int INDENT_GAP_PROP = 2;
+
+ /**
+ * Decompilation property to specify identation offset for case labels.
+ */
+ public static final int CASE_GAP_PROP = 3;
+
+ // Marker to denote the last RC of function so it can be distinguished from
+ // the last RC of object literals in case of function expressions
+ private static final int FUNCTION_END = Token.LAST_TOKEN + 1;
+
+ String getEncodedSource()
+ {
+ return sourceToString(0);
+ }
+
+ int getCurrentOffset()
+ {
+ return sourceTop;
+ }
+
+ int markFunctionStart(int functionType)
+ {
+ int savedOffset = getCurrentOffset();
+ addToken(Token.FUNCTION);
+ append((char)functionType);
+ return savedOffset;
+ }
+
+ int markFunctionEnd(int functionStart)
+ {
+ int offset = getCurrentOffset();
+ append((char)FUNCTION_END);
+ return offset;
+ }
+
+ void addToken(int token)
+ {
+ if (!(0 <= token && token <= Token.LAST_TOKEN))
+ throw new IllegalArgumentException();
+
+ append((char)token);
+ }
+
+ void addEOL(int token)
+ {
+ if (!(0 <= token && token <= Token.LAST_TOKEN))
+ throw new IllegalArgumentException();
+
+ append((char)token);
+ append((char)Token.EOL);
+ }
+
+ void addName(String str)
+ {
+ addToken(Token.NAME);
+ appendString(str);
+ }
+
+ void addString(String str)
+ {
+ addToken(Token.STRING);
+ appendString(str);
+ }
+
+ void addRegexp(String regexp, String flags)
+ {
+ addToken(Token.REGEXP);
+ appendString('/' + regexp + '/' + flags);
+ }
+
+ void addNumber(double n)
+ {
+ addToken(Token.NUMBER);
+
+ /* encode the number in the source stream.
+ * Save as NUMBER type (char | char char char char)
+ * where type is
+ * 'D' - double, 'S' - short, 'J' - long.
+
+ * We need to retain float vs. integer type info to keep the
+ * behavior of liveconnect type-guessing the same after
+ * decompilation. (Liveconnect tries to present 1.0 to Java
+ * as a float/double)
+ * OPT: This is no longer true. We could compress the format.
+
+ * This may not be the most space-efficient encoding;
+ * the chars created below may take up to 3 bytes in
+ * constant pool UTF-8 encoding, so a Double could take
+ * up to 12 bytes.
+ */
+
+ long lbits = (long)n;
+ if (lbits != n) {
+ // if it's floating point, save as a Double bit pattern.
+ // (12/15/97 our scanner only returns Double for f.p.)
+ lbits = Double.doubleToLongBits(n);
+ append('D');
+ append((char)(lbits >> 48));
+ append((char)(lbits >> 32));
+ append((char)(lbits >> 16));
+ append((char)lbits);
+ }
+ else {
+ // we can ignore negative values, bc they're already prefixed
+ // by NEG
+ if (lbits < 0) Kit.codeBug();
+
+ // will it fit in a char?
+ // this gives a short encoding for integer values up to 2^16.
+ if (lbits <= Character.MAX_VALUE) {
+ append('S');
+ append((char)lbits);
+ }
+ else { // Integral, but won't fit in a char. Store as a long.
+ append('J');
+ append((char)(lbits >> 48));
+ append((char)(lbits >> 32));
+ append((char)(lbits >> 16));
+ append((char)lbits);
+ }
+ }
+ }
+
+ private void appendString(String str)
+ {
+ int L = str.length();
+ int lengthEncodingSize = 1;
+ if (L >= 0x8000) {
+ lengthEncodingSize = 2;
+ }
+ int nextTop = sourceTop + lengthEncodingSize + L;
+ if (nextTop > sourceBuffer.length) {
+ increaseSourceCapacity(nextTop);
+ }
+ if (L >= 0x8000) {
+ // Use 2 chars to encode strings exceeding 32K, were the highest
+ // bit in the first char indicates presence of the next byte
+ sourceBuffer[sourceTop] = (char)(0x8000 | (L >>> 16));
+ ++sourceTop;
+ }
+ sourceBuffer[sourceTop] = (char)L;
+ ++sourceTop;
+ str.getChars(0, L, sourceBuffer, sourceTop);
+ sourceTop = nextTop;
+ }
+
+ private void append(char c)
+ {
+ if (sourceTop == sourceBuffer.length) {
+ increaseSourceCapacity(sourceTop + 1);
+ }
+ sourceBuffer[sourceTop] = c;
+ ++sourceTop;
+ }
+
+ private void increaseSourceCapacity(int minimalCapacity)
+ {
+ // Call this only when capacity increase is must
+ if (minimalCapacity <= sourceBuffer.length) Kit.codeBug();
+ int newCapacity = sourceBuffer.length * 2;
+ if (newCapacity < minimalCapacity) {
+ newCapacity = minimalCapacity;
+ }
+ char[] tmp = new char[newCapacity];
+ System.arraycopy(sourceBuffer, 0, tmp, 0, sourceTop);
+ sourceBuffer = tmp;
+ }
+
+ private String sourceToString(int offset)
+ {
+ if (offset < 0 || sourceTop < offset) Kit.codeBug();
+ return new String(sourceBuffer, offset, sourceTop - offset);
+ }
+
+ /**
+ * Decompile the source information associated with this js
+ * function/script back into a string. For the most part, this
+ * just means translating tokens back to their string
+ * representations; there's a little bit of lookahead logic to
+ * decide the proper spacing/indentation. Most of the work in
+ * mapping the original source to the prettyprinted decompiled
+ * version is done by the parser.
+ *
+ * @param source encoded source tree presentation
+ *
+ * @param flags flags to select output format
+ *
+ * @param properties indentation properties
+ *
+ */
+ public static String decompile(String source, int flags,
+ UintMap properties)
+ {
+ int length = source.length();
+ if (length == 0) { return ""; }
+
+ int indent = properties.getInt(INITIAL_INDENT_PROP, 0);
+ if (indent < 0) throw new IllegalArgumentException();
+ int indentGap = properties.getInt(INDENT_GAP_PROP, 4);
+ if (indentGap < 0) throw new IllegalArgumentException();
+ int caseGap = properties.getInt(CASE_GAP_PROP, 2);
+ if (caseGap < 0) throw new IllegalArgumentException();
+
+ StringBuffer result = new StringBuffer();
+ boolean justFunctionBody = (0 != (flags & Decompiler.ONLY_BODY_FLAG));
+ boolean toSource = (0 != (flags & Decompiler.TO_SOURCE_FLAG));
+
+ // Spew tokens in source, for debugging.
+ // as TYPE number char
+ if (printSource) {
+ System.err.println("length:" + length);
+ for (int i = 0; i < length; ++i) {
+ // Note that tokenToName will fail unless Context.printTrees
+ // is true.
+ String tokenname = null;
+ if (Token.printNames) {
+ tokenname = Token.name(source.charAt(i));
+ }
+ if (tokenname == null) {
+ tokenname = "---";
+ }
+ String pad = tokenname.length() > 7
+ ? "\t"
+ : "\t\t";
+ System.err.println
+ (tokenname
+ + pad + (int)source.charAt(i)
+ + "\t'" + ScriptRuntime.escapeString
+ (source.substring(i, i+1))
+ + "'");
+ }
+ System.err.println();
+ }
+
+ int braceNesting = 0;
+ boolean afterFirstEOL = false;
+ int i = 0;
+ int topFunctionType;
+ if (source.charAt(i) == Token.SCRIPT) {
+ ++i;
+ topFunctionType = -1;
+ } else {
+ topFunctionType = source.charAt(i + 1);
+ }
+
+ if (!toSource) {
+ // add an initial newline to exactly match js.
+ result.append('\n');
+ for (int j = 0; j < indent; j++)
+ result.append(' ');
+ } else {
+ if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) {
+ result.append('(');
+ }
+ }
+
+ while (i < length) {
+ switch(source.charAt(i)) {
+ case Token.GET:
+ case Token.SET:
+ result.append(source.charAt(i) == Token.GET ? "get " : "set ");
+ ++i;
+ i = printSourceString(source, i + 1, false, result);
+ // Now increment one more to get past the FUNCTION token
+ ++i;
+ break;
+
+ case Token.NAME:
+ case Token.REGEXP: // re-wrapped in '/'s in parser...
+ i = printSourceString(source, i + 1, false, result);
+ continue;
+
+ case Token.STRING:
+ i = printSourceString(source, i + 1, true, result);
+ continue;
+
+ case Token.NUMBER:
+ i = printSourceNumber(source, i + 1, result);
+ continue;
+
+ case Token.TRUE:
+ result.append("true");
+ break;
+
+ case Token.FALSE:
+ result.append("false");
+ break;
+
+ case Token.NULL:
+ result.append("null");
+ break;
+
+ case Token.THIS:
+ result.append("this");
+ break;
+
+ case Token.FUNCTION:
+ ++i; // skip function type
+ result.append("function ");
+ break;
+
+ case FUNCTION_END:
+ // Do nothing
+ break;
+
+ case Token.COMMA:
+ result.append(", ");
+ break;
+
+ case Token.LC:
+ ++braceNesting;
+ if (Token.EOL == getNext(source, length, i))
+ indent += indentGap;
+ result.append('{');
+ break;
+
+ case Token.RC: {
+ --braceNesting;
+ /* don't print the closing RC if it closes the
+ * toplevel function and we're called from
+ * decompileFunctionBody.
+ */
+ if (justFunctionBody && braceNesting == 0)
+ break;
+
+ result.append('}');
+ switch (getNext(source, length, i)) {
+ case Token.EOL:
+ case FUNCTION_END:
+ indent -= indentGap;
+ break;
+ case Token.WHILE:
+ case Token.ELSE:
+ indent -= indentGap;
+ result.append(' ');
+ break;
+ }
+ break;
+ }
+ case Token.LP:
+ result.append('(');
+ break;
+
+ case Token.RP:
+ result.append(')');
+ if (Token.LC == getNext(source, length, i))
+ result.append(' ');
+ break;
+
+ case Token.LB:
+ result.append('[');
+ break;
+
+ case Token.RB:
+ result.append(']');
+ break;
+
+ case Token.EOL: {
+ if (toSource) break;
+ boolean newLine = true;
+ if (!afterFirstEOL) {
+ afterFirstEOL = true;
+ if (justFunctionBody) {
+ /* throw away just added 'function name(...) {'
+ * and restore the original indent
+ */
+ result.setLength(0);
+ indent -= indentGap;
+ newLine = false;
+ }
+ }
+ if (newLine) {
+ result.append('\n');
+ }
+
+ /* add indent if any tokens remain,
+ * less setback if next token is
+ * a label, case or default.
+ */
+ if (i + 1 < length) {
+ int less = 0;
+ int nextToken = source.charAt(i + 1);
+ if (nextToken == Token.CASE
+ || nextToken == Token.DEFAULT)
+ {
+ less = indentGap - caseGap;
+ } else if (nextToken == Token.RC) {
+ less = indentGap;
+ }
+
+ /* elaborate check against label... skip past a
+ * following inlined NAME and look for a COLON.
+ */
+ else if (nextToken == Token.NAME) {
+ int afterName = getSourceStringEnd(source, i + 2);
+ if (source.charAt(afterName) == Token.COLON)
+ less = indentGap;
+ }
+
+ for (; less < indent; less++)
+ result.append(' ');
+ }
+ break;
+ }
+ case Token.DOT:
+ result.append('.');
+ break;
+
+ case Token.NEW:
+ result.append("new ");
+ break;
+
+ case Token.DELPROP:
+ result.append("delete ");
+ break;
+
+ case Token.IF:
+ result.append("if ");
+ break;
+
+ case Token.ELSE:
+ result.append("else ");
+ break;
+
+ case Token.FOR:
+ result.append("for ");
+ break;
+
+ case Token.IN:
+ result.append(" in ");
+ break;
+
+ case Token.WITH:
+ result.append("with ");
+ break;
+
+ case Token.WHILE:
+ result.append("while ");
+ break;
+
+ case Token.DO:
+ result.append("do ");
+ break;
+
+ case Token.TRY:
+ result.append("try ");
+ break;
+
+ case Token.CATCH:
+ result.append("catch ");
+ break;
+
+ case Token.FINALLY:
+ result.append("finally ");
+ break;
+
+ case Token.THROW:
+ result.append("throw ");
+ break;
+
+ case Token.SWITCH:
+ result.append("switch ");
+ break;
+
+ case Token.BREAK:
+ result.append("break");
+ if (Token.NAME == getNext(source, length, i))
+ result.append(' ');
+ break;
+
+ case Token.CONTINUE:
+ result.append("continue");
+ if (Token.NAME == getNext(source, length, i))
+ result.append(' ');
+ break;
+
+ case Token.CASE:
+ result.append("case ");
+ break;
+
+ case Token.DEFAULT:
+ result.append("default");
+ break;
+
+ case Token.RETURN:
+ result.append("return");
+ if (Token.SEMI != getNext(source, length, i))
+ result.append(' ');
+ break;
+
+ case Token.VAR:
+ result.append("var ");
+ break;
+
+ case Token.LET:
+ result.append("let ");
+ break;
+
+ case Token.SEMI:
+ result.append(';');
+ if (Token.EOL != getNext(source, length, i)) {
+ // separators in FOR
+ result.append(' ');
+ }
+ break;
+
+ case Token.ASSIGN:
+ result.append(" = ");
+ break;
+
+ case Token.ASSIGN_ADD:
+ result.append(" += ");
+ break;
+
+ case Token.ASSIGN_SUB:
+ result.append(" -= ");
+ break;
+
+ case Token.ASSIGN_MUL:
+ result.append(" *= ");
+ break;
+
+ case Token.ASSIGN_DIV:
+ result.append(" /= ");
+ break;
+
+ case Token.ASSIGN_MOD:
+ result.append(" %= ");
+ break;
+
+ case Token.ASSIGN_BITOR:
+ result.append(" |= ");
+ break;
+
+ case Token.ASSIGN_BITXOR:
+ result.append(" ^= ");
+ break;
+
+ case Token.ASSIGN_BITAND:
+ result.append(" &= ");
+ break;
+
+ case Token.ASSIGN_LSH:
+ result.append(" <<= ");
+ break;
+
+ case Token.ASSIGN_RSH:
+ result.append(" >>= ");
+ break;
+
+ case Token.ASSIGN_URSH:
+ result.append(" >>>= ");
+ break;
+
+ case Token.HOOK:
+ result.append(" ? ");
+ break;
+
+ case Token.OBJECTLIT:
+ // pun OBJECTLIT to mean colon in objlit property
+ // initialization.
+ // This needs to be distinct from COLON in the general case
+ // to distinguish from the colon in a ternary... which needs
+ // different spacing.
+ result.append(':');
+ break;
+
+ case Token.COLON:
+ if (Token.EOL == getNext(source, length, i))
+ // it's the end of a label
+ result.append(':');
+ else
+ // it's the middle part of a ternary
+ result.append(" : ");
+ break;
+
+ case Token.OR:
+ result.append(" || ");
+ break;
+
+ case Token.AND:
+ result.append(" && ");
+ break;
+
+ case Token.BITOR:
+ result.append(" | ");
+ break;
+
+ case Token.BITXOR:
+ result.append(" ^ ");
+ break;
+
+ case Token.BITAND:
+ result.append(" & ");
+ break;
+
+ case Token.SHEQ:
+ result.append(" === ");
+ break;
+
+ case Token.SHNE:
+ result.append(" !== ");
+ break;
+
+ case Token.EQ:
+ result.append(" == ");
+ break;
+
+ case Token.NE:
+ result.append(" != ");
+ break;
+
+ case Token.LE:
+ result.append(" <= ");
+ break;
+
+ case Token.LT:
+ result.append(" < ");
+ break;
+
+ case Token.GE:
+ result.append(" >= ");
+ break;
+
+ case Token.GT:
+ result.append(" > ");
+ break;
+
+ case Token.INSTANCEOF:
+ result.append(" instanceof ");
+ break;
+
+ case Token.LSH:
+ result.append(" << ");
+ break;
+
+ case Token.RSH:
+ result.append(" >> ");
+ break;
+
+ case Token.URSH:
+ result.append(" >>> ");
+ break;
+
+ case Token.TYPEOF:
+ result.append("typeof ");
+ break;
+
+ case Token.VOID:
+ result.append("void ");
+ break;
+
+ case Token.CONST:
+ result.append("const ");
+ break;
+
+ case Token.YIELD:
+ result.append("yield ");
+ break;
+
+ case Token.NOT:
+ result.append('!');
+ break;
+
+ case Token.BITNOT:
+ result.append('~');
+ break;
+
+ case Token.POS:
+ result.append('+');
+ break;
+
+ case Token.NEG:
+ result.append('-');
+ break;
+
+ case Token.INC:
+ result.append("++");
+ break;
+
+ case Token.DEC:
+ result.append("--");
+ break;
+
+ case Token.ADD:
+ result.append(" + ");
+ break;
+
+ case Token.SUB:
+ result.append(" - ");
+ break;
+
+ case Token.MUL:
+ result.append(" * ");
+ break;
+
+ case Token.DIV:
+ result.append(" / ");
+ break;
+
+ case Token.MOD:
+ result.append(" % ");
+ break;
+
+ case Token.COLONCOLON:
+ result.append("::");
+ break;
+
+ case Token.DOTDOT:
+ result.append("..");
+ break;
+
+ case Token.DOTQUERY:
+ result.append(".(");
+ break;
+
+ case Token.XMLATTR:
+ result.append('@');
+ break;
+
+ default:
+ // If we don't know how to decompile it, raise an exception.
+ throw new RuntimeException("Token: " +
+ Token.name(source.charAt(i)));
+ }
+ ++i;
+ }
+
+ if (!toSource) {
+ // add that trailing newline if it's an outermost function.
+ if (!justFunctionBody)
+ result.append('\n');
+ } else {
+ if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) {
+ result.append(')');
+ }
+ }
+
+ return result.toString();
+ }
+
+ private static int getNext(String source, int length, int i)
+ {
+ return (i + 1 < length) ? source.charAt(i + 1) : Token.EOF;
+ }
+
+ private static int getSourceStringEnd(String source, int offset)
+ {
+ return printSourceString(source, offset, false, null);
+ }
+
+ private static int printSourceString(String source, int offset,
+ boolean asQuotedString,
+ StringBuffer sb)
+ {
+ int length = source.charAt(offset);
+ ++offset;
+ if ((0x8000 & length) != 0) {
+ length = ((0x7FFF & length) << 16) | source.charAt(offset);
+ ++offset;
+ }
+ if (sb != null) {
+ String str = source.substring(offset, offset + length);
+ if (!asQuotedString) {
+ sb.append(str);
+ } else {
+ sb.append('"');
+ sb.append(ScriptRuntime.escapeString(str));
+ sb.append('"');
+ }
+ }
+ return offset + length;
+ }
+
+ private static int printSourceNumber(String source, int offset,
+ StringBuffer sb)
+ {
+ double number = 0.0;
+ char type = source.charAt(offset);
+ ++offset;
+ if (type == 'S') {
+ if (sb != null) {
+ int ival = source.charAt(offset);
+ number = ival;
+ }
+ ++offset;
+ } else if (type == 'J' || type == 'D') {
+ if (sb != null) {
+ long lbits;
+ lbits = (long)source.charAt(offset) << 48;
+ lbits |= (long)source.charAt(offset + 1) << 32;
+ lbits |= (long)source.charAt(offset + 2) << 16;
+ lbits |= source.charAt(offset + 3);
+ if (type == 'J') {
+ number = lbits;
+ } else {
+ number = Double.longBitsToDouble(lbits);
+ }
+ }
+ offset += 4;
+ } else {
+ // Bad source
+ throw new RuntimeException();
+ }
+ if (sb != null) {
+ sb.append(ScriptRuntime.numberToString(number, 10));
+ }
+ return offset;
+ }
+
+ private char[] sourceBuffer = new char[128];
+
+// Per script/function source buffer top: parent source does not include a
+// nested functions source and uses function index as a reference instead.
+ private int sourceTop;
+
+// whether to do a debug print of the source information, when decompiling.
+ private static final boolean printSource = false;
+
+}
diff --git a/src/org/mozilla/javascript/DefaultErrorReporter.java b/src/org/mozilla/javascript/DefaultErrorReporter.java
new file mode 100644
index 0000000..c7d93d4
--- /dev/null
+++ b/src/org/mozilla/javascript/DefaultErrorReporter.java
@@ -0,0 +1,113 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * This is the default error reporter for JavaScript.
+ *
+ * @author Norris Boyd
+ */
+class DefaultErrorReporter implements ErrorReporter
+{
+ static final DefaultErrorReporter instance = new DefaultErrorReporter();
+
+ private boolean forEval;
+ private ErrorReporter chainedReporter;
+
+ private DefaultErrorReporter() { }
+
+ static ErrorReporter forEval(ErrorReporter reporter)
+ {
+ DefaultErrorReporter r = new DefaultErrorReporter();
+ r.forEval = true;
+ r.chainedReporter = reporter;
+ return r;
+ }
+
+ public void warning(String message, String sourceURI, int line,
+ String lineText, int lineOffset)
+ {
+ if (chainedReporter != null) {
+ chainedReporter.warning(
+ message, sourceURI, line, lineText, lineOffset);
+ } else {
+ // Do nothing
+ }
+ }
+
+ public void error(String message, String sourceURI, int line,
+ String lineText, int lineOffset)
+ {
+ if (forEval) {
+ // Assume error message strings that start with "TypeError: "
+ // should become TypeError exceptions. A bit of a hack, but we
+ // don't want to change the ErrorReporter interface.
+ String error = "SyntaxError";
+ final String TYPE_ERROR_NAME = "TypeError";
+ final String DELIMETER = ": ";
+ final String prefix = TYPE_ERROR_NAME + DELIMETER;
+ if (message.startsWith(prefix)) {
+ error = TYPE_ERROR_NAME;
+ message = message.substring(prefix.length());
+ }
+ throw ScriptRuntime.constructError(error, message, sourceURI,
+ line, lineText, lineOffset);
+ }
+ if (chainedReporter != null) {
+ chainedReporter.error(
+ message, sourceURI, line, lineText, lineOffset);
+ } else {
+ throw runtimeError(
+ message, sourceURI, line, lineText, lineOffset);
+ }
+ }
+
+ public EvaluatorException runtimeError(String message, String sourceURI,
+ int line, String lineText,
+ int lineOffset)
+ {
+ if (chainedReporter != null) {
+ return chainedReporter.runtimeError(
+ message, sourceURI, line, lineText, lineOffset);
+ } else {
+ return new EvaluatorException(
+ message, sourceURI, line, lineText, lineOffset);
+ }
+ }
+}
diff --git a/src/org/mozilla/javascript/DefiningClassLoader.java b/src/org/mozilla/javascript/DefiningClassLoader.java
new file mode 100644
index 0000000..9723d9c
--- /dev/null
+++ b/src/org/mozilla/javascript/DefiningClassLoader.java
@@ -0,0 +1,89 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Roger Lawrence
+ * Patrick Beard
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * Load generated classes.
+ *
+ * @author Norris Boyd
+ */
+public class DefiningClassLoader extends ClassLoader
+ implements GeneratedClassLoader
+{
+ public DefiningClassLoader() {
+ this.parentLoader = getClass().getClassLoader();
+ }
+
+ public DefiningClassLoader(ClassLoader parentLoader) {
+ this.parentLoader = parentLoader;
+ }
+
+ public Class<?> defineClass(String name, byte[] data) {
+ // Use our own protection domain for the generated classes.
+ // TODO: we might want to use a separate protection domain for classes
+ // compiled from scripts, based on where the script was loaded from.
+ return super.defineClass(name, data, 0, data.length,
+ SecurityUtilities.getProtectionDomain(getClass()));
+ }
+
+ public void linkClass(Class<?> cl) {
+ resolveClass(cl);
+ }
+
+ @Override
+ public Class<?> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ Class<?> cl = findLoadedClass(name);
+ if (cl == null) {
+ if (parentLoader != null) {
+ cl = parentLoader.loadClass(name);
+ } else {
+ cl = findSystemClass(name);
+ }
+ }
+ if (resolve) {
+ resolveClass(cl);
+ }
+ return cl;
+ }
+
+ private final ClassLoader parentLoader;
+}
diff --git a/src/org/mozilla/javascript/Delegator.java b/src/org/mozilla/javascript/Delegator.java
new file mode 100644
index 0000000..0056f8c
--- /dev/null
+++ b/src/org/mozilla/javascript/Delegator.java
@@ -0,0 +1,266 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Delegator.java, released
+ * Sep 27, 2000.
+ *
+ * The Initial Developer of the Original Code is
+ * Matthias Radestock. <matthias at sorted.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * This is a helper class for implementing wrappers around Scriptable
+ * objects. It implements the Function interface and delegates all
+ * invocations to a delegee Scriptable object. The normal use of this
+ * class involves creating a sub-class and overriding one or more of
+ * the methods.
+ *
+ * A useful application is the implementation of interceptors,
+ * pre/post conditions, debugging.
+ *
+ * @see Function
+ * @see Scriptable
+ * @author Matthias Radestock
+ */
+
+public class Delegator implements Function {
+
+ protected Scriptable obj = null;
+
+ /**
+ * Create a Delegator prototype.
+ *
+ * This constructor should only be used for creating prototype
+ * objects of Delegator.
+ *
+ * @see org.mozilla.javascript.Delegator#construct
+ */
+ public Delegator() {
+ }
+
+ /**
+ * Create a new Delegator that forwards requests to a delegee
+ * Scriptable object.
+ *
+ * @param obj the delegee
+ * @see org.mozilla.javascript.Scriptable
+ */
+ public Delegator(Scriptable obj) {
+ this.obj = obj;
+ }
+
+ /**
+ * Crete new Delegator instance.
+ * The default implementation calls this.getClass().newInstance().
+ *
+ * @see #construct(Context cx, Scriptable scope, Object[] args)
+ */
+ protected Delegator newInstance()
+ {
+ try {
+ return this.getClass().newInstance();
+ } catch (Exception ex) {
+ throw Context.throwAsScriptRuntimeEx(ex);
+ }
+ }
+
+ /**
+ * Retrieve the delegee.
+ *
+ * @return the delegee
+ */
+ public Scriptable getDelegee() {
+ return obj;
+ }
+ /**
+ * Set the delegee.
+ *
+ * @param obj the delegee
+ * @see org.mozilla.javascript.Scriptable
+ */
+ public void setDelegee(Scriptable obj) {
+ this.obj = obj;
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#getClassName
+ */
+ public String getClassName() {
+ return obj.getClassName();
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#get(String, Scriptable)
+ */
+ public Object get(String name, Scriptable start) {
+ return obj.get(name,start);
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#get(int, Scriptable)
+ */
+ public Object get(int index, Scriptable start) {
+ return obj.get(index,start);
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#has(String, Scriptable)
+ */
+ public boolean has(String name, Scriptable start) {
+ return obj.has(name,start);
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#has(int, Scriptable)
+ */
+ public boolean has(int index, Scriptable start) {
+ return obj.has(index,start);
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object)
+ */
+ public void put(String name, Scriptable start, Object value) {
+ obj.put(name,start,value);
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#put(int, Scriptable, Object)
+ */
+ public void put(int index, Scriptable start, Object value) {
+ obj.put(index,start,value);
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#delete(String)
+ */
+ public void delete(String name) {
+ obj.delete(name);
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#delete(int)
+ */
+ public void delete(int index) {
+ obj.delete(index);
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#getPrototype
+ */
+ public Scriptable getPrototype() {
+ return obj.getPrototype();
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#setPrototype
+ */
+ public void setPrototype(Scriptable prototype) {
+ obj.setPrototype(prototype);
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#getParentScope
+ */
+ public Scriptable getParentScope() {
+ return obj.getParentScope();
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#setParentScope
+ */
+ public void setParentScope(Scriptable parent) {
+ obj.setParentScope(parent);
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#getIds
+ */
+ public Object[] getIds() {
+ return obj.getIds();
+ }
+ /**
+ * Note that this method does not get forwarded to the delegee if
+ * the <code>hint</code> parameter is null,
+ * <code>ScriptRuntime.ScriptableClass</code> or
+ * <code>ScriptRuntime.FunctionClass</code>. Instead the object
+ * itself is returned.
+ *
+ * @param hint the type hint
+ * @return the default value
+ *
+ * @see org.mozilla.javascript.Scriptable#getDefaultValue
+ */
+ public Object getDefaultValue(Class<?> hint) {
+ return (hint == null ||
+ hint == ScriptRuntime.ScriptableClass ||
+ hint == ScriptRuntime.FunctionClass) ?
+ this : obj.getDefaultValue(hint);
+ }
+ /**
+ * @see org.mozilla.javascript.Scriptable#hasInstance
+ */
+ public boolean hasInstance(Scriptable instance) {
+ return obj.hasInstance(instance);
+ }
+ /**
+ * @see org.mozilla.javascript.Function#call
+ */
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ return ((Function)obj).call(cx,scope,thisObj,args);
+ }
+
+ /**
+ * Note that if the <code>delegee</code> is <code>null</code>,
+ * this method creates a new instance of the Delegator itself
+ * rathert than forwarding the call to the
+ * <code>delegee</code>. This permits the use of Delegator
+ * prototypes.
+ *
+ * @param cx the current Context for this thread
+ * @param scope an enclosing scope of the caller except
+ * when the function is called from a closure.
+ * @param args the array of arguments
+ * @return the allocated object
+ *
+ * @see Function#construct(Context, Scriptable, Object[])
+ */
+ public Scriptable construct(Context cx, Scriptable scope, Object[] args)
+ {
+ if (obj == null) {
+ //this little trick allows us to declare prototype objects for
+ //Delegators
+ Delegator n = newInstance();
+ Scriptable delegee;
+ if (args.length == 0) {
+ delegee = new NativeObject();
+ } else {
+ delegee = ScriptRuntime.toObject(cx, scope, args[0]);
+ }
+ n.setDelegee(delegee);
+ return n;
+ }
+ else {
+ return ((Function)obj).construct(cx,scope,args);
+ }
+ }
+}
diff --git a/src/org/mozilla/javascript/EcmaError.java b/src/org/mozilla/javascript/EcmaError.java
new file mode 100644
index 0000000..b8ac416
--- /dev/null
+++ b/src/org/mozilla/javascript/EcmaError.java
@@ -0,0 +1,161 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * The class of exceptions raised by the engine as described in
+ * ECMA edition 3. See section 15.11.6 in particular.
+ */
+public class EcmaError extends RhinoException
+{
+ static final long serialVersionUID = -6261226256957286699L;
+
+ private String errorName;
+ private String errorMessage;
+
+ /**
+ * Create an exception with the specified detail message.
+ *
+ * Errors internal to the JavaScript engine will simply throw a
+ * RuntimeException.
+ *
+ * @param sourceName the name of the source responsible for the error
+ * @param lineNumber the line number of the source
+ * @param columnNumber the columnNumber of the source (may be zero if
+ * unknown)
+ * @param lineSource the source of the line containing the error (may be
+ * null if unknown)
+ */
+ EcmaError(String errorName, String errorMessage,
+ String sourceName, int lineNumber,
+ String lineSource, int columnNumber)
+ {
+ recordErrorOrigin(sourceName, lineNumber, lineSource, columnNumber);
+ this.errorName = errorName;
+ this.errorMessage = errorMessage;
+ }
+
+ /**
+ * @deprecated EcmaError error instances should not be constructed
+ * explicitly since they are generated by the engine.
+ */
+ public EcmaError(Scriptable nativeError, String sourceName,
+ int lineNumber, int columnNumber, String lineSource)
+ {
+ this("InternalError", ScriptRuntime.toString(nativeError),
+ sourceName, lineNumber, lineSource, columnNumber);
+ }
+
+ @Override
+ public String details()
+ {
+ return errorName+": "+errorMessage;
+ }
+
+ /**
+ * Gets the name of the error.
+ *
+ * ECMA edition 3 defines the following
+ * errors: EvalError, RangeError, ReferenceError,
+ * SyntaxError, TypeError, and URIError. Additional error names
+ * may be added in the future.
+ *
+ * See ECMA edition 3, 15.11.7.9.
+ *
+ * @return the name of the error.
+ */
+ public String getName()
+ {
+ return errorName;
+ }
+
+ /**
+ * Gets the message corresponding to the error.
+ *
+ * See ECMA edition 3, 15.11.7.10.
+ *
+ * @return an implementation-defined string describing the error.
+ */
+ public String getErrorMessage()
+ {
+ return errorMessage;
+ }
+
+ /**
+ * @deprecated Use {@link RhinoException#sourceName()} from the super class.
+ */
+ public String getSourceName()
+ {
+ return sourceName();
+ }
+
+ /**
+ * @deprecated Use {@link RhinoException#lineNumber()} from the super class.
+ */
+ public int getLineNumber()
+ {
+ return lineNumber();
+ }
+
+ /**
+ * @deprecated
+ * Use {@link RhinoException#columnNumber()} from the super class.
+ */
+ public int getColumnNumber() {
+ return columnNumber();
+ }
+
+ /**
+ * @deprecated Use {@link RhinoException#lineSource()} from the super class.
+ */
+ public String getLineSource() {
+ return lineSource();
+ }
+
+ /**
+ * @deprecated
+ * Always returns <b>null</b>.
+ */
+ public Scriptable getErrorObject()
+ {
+ return null;
+ }
+}
diff --git a/src/org/mozilla/javascript/ErrorReporter.java b/src/org/mozilla/javascript/ErrorReporter.java
new file mode 100644
index 0000000..4649370
--- /dev/null
+++ b/src/org/mozilla/javascript/ErrorReporter.java
@@ -0,0 +1,106 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * This is interface defines a protocol for the reporting of
+ * errors during JavaScript translation or execution.
+ *
+ * @author Norris Boyd
+ */
+
+public interface ErrorReporter {
+
+ /**
+ * Report a warning.
+ *
+ * The implementing class may choose to ignore the warning
+ * if it desires.
+ *
+ * @param message a String describing the warning
+ * @param sourceName a String describing the JavaScript source
+ * where the warning occured; typically a filename or URL
+ * @param line the line number associated with the warning
+ * @param lineSource the text of the line (may be null)
+ * @param lineOffset the offset into lineSource where problem was detected
+ */
+ void warning(String message, String sourceName, int line,
+ String lineSource, int lineOffset);
+
+ /**
+ * Report an error.
+ *
+ * The implementing class is free to throw an exception if
+ * it desires.
+ *
+ * If execution has not yet begun, the JavaScript engine is
+ * free to find additional errors rather than terminating
+ * the translation. It will not execute a script that had
+ * errors, however.
+ *
+ * @param message a String describing the error
+ * @param sourceName a String describing the JavaScript source
+ * where the error occured; typically a filename or URL
+ * @param line the line number associated with the error
+ * @param lineSource the text of the line (may be null)
+ * @param lineOffset the offset into lineSource where problem was detected
+ */
+ void error(String message, String sourceName, int line,
+ String lineSource, int lineOffset);
+
+ /**
+ * Creates an EvaluatorException that may be thrown.
+ *
+ * runtimeErrors, unlike errors, will always terminate the
+ * current script.
+ *
+ * @param message a String describing the error
+ * @param sourceName a String describing the JavaScript source
+ * where the error occured; typically a filename or URL
+ * @param line the line number associated with the error
+ * @param lineSource the text of the line (may be null)
+ * @param lineOffset the offset into lineSource where problem was detected
+ * @return an EvaluatorException that will be thrown.
+ */
+ EvaluatorException runtimeError(String message, String sourceName,
+ int line, String lineSource,
+ int lineOffset);
+}
diff --git a/src/org/mozilla/javascript/Evaluator.java b/src/org/mozilla/javascript/Evaluator.java
new file mode 100644
index 0000000..09a6881
--- /dev/null
+++ b/src/org/mozilla/javascript/Evaluator.java
@@ -0,0 +1,118 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.util.List;
+
+/**
+ * Abstraction of evaluation, which can be implemented either by an
+ * interpreter or compiler.
+ */
+public interface Evaluator {
+
+ /**
+ * Compile the script or function from intermediate representation
+ * tree into an executable form.
+ *
+ * @param compilerEnv Compiler environment
+ * @param tree intermediate representation
+ * @param encodedSource encoding of the source code for decompilation
+ * @param returnFunction if true, compiling a function
+ * @return an opaque object that can be passed to either
+ * createFunctionObject or createScriptObject, depending on the
+ * value of returnFunction
+ */
+ public Object compile(CompilerEnvirons compilerEnv,
+ ScriptOrFnNode tree,
+ String encodedSource,
+ boolean returnFunction);
+
+ /**
+ * Create a function object.
+ *
+ * @param cx Current context
+ * @param scope scope of the function
+ * @param bytecode opaque object returned by compile
+ * @param staticSecurityDomain security domain
+ * @return Function object that can be called
+ */
+ public Function createFunctionObject(Context cx, Scriptable scope,
+ Object bytecode, Object staticSecurityDomain);
+
+ /**
+ * Create a script object.
+ *
+ * @param bytecode opaque object returned by compile
+ * @param staticSecurityDomain security domain
+ * @return Script object that can be evaluated
+ */
+ public Script createScriptObject(Object bytecode,
+ Object staticSecurityDomain);
+
+ /**
+ * Capture stack information from the given exception.
+ * @param ex an exception thrown during execution
+ */
+ public void captureStackInfo(RhinoException ex);
+
+ /**
+ * Get the source position information by examining the stack.
+ * @param cx Context
+ * @param linep Array object of length >= 1; getSourcePositionFromStack
+ * will assign the line number to linep[0].
+ * @return the name of the file or other source container
+ */
+ public String getSourcePositionFromStack(Context cx, int[] linep);
+
+ /**
+ * Given a native stack trace, patch it with script-specific source
+ * and line information
+ * @param ex exception
+ * @param nativeStackTrace the native stack trace
+ * @return patched stack trace
+ */
+ public String getPatchedStack(RhinoException ex,
+ String nativeStackTrace);
+
+ /**
+ * Get the script stack for the given exception
+ * @param ex exception from execution
+ * @return list of strings for the stack trace
+ */
+ public List<String> getScriptStack(RhinoException ex);
+
+ /**
+ * Mark the given script to indicate it was created by a call to
+ * eval() or to a Function constructor.
+ * @param script script to mark as from eval
+ */
+ public void setEvalScriptFlag(Script script);
+}
diff --git a/src/org/mozilla/javascript/EvaluatorException.java b/src/org/mozilla/javascript/EvaluatorException.java
new file mode 100644
index 0000000..7b4e7cc
--- /dev/null
+++ b/src/org/mozilla/javascript/EvaluatorException.java
@@ -0,0 +1,123 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+package org.mozilla.javascript;
+
+/**
+ * The class of exceptions thrown by the JavaScript engine.
+ */
+public class EvaluatorException extends RhinoException
+{
+ static final long serialVersionUID = -8743165779676009808L;
+
+ public EvaluatorException(String detail)
+ {
+ super(detail);
+ }
+
+ /**
+ * Create an exception with the specified detail message.
+ *
+ * Errors internal to the JavaScript engine will simply throw a
+ * RuntimeException.
+ *
+ * @param detail the error message
+ * @param sourceName the name of the source reponsible for the error
+ * @param lineNumber the line number of the source
+ */
+ public EvaluatorException(String detail, String sourceName,
+ int lineNumber)
+ {
+ this(detail, sourceName, lineNumber, null, 0);
+ }
+
+ /**
+ * Create an exception with the specified detail message.
+ *
+ * Errors internal to the JavaScript engine will simply throw a
+ * RuntimeException.
+ *
+ * @param detail the error message
+ * @param sourceName the name of the source responsible for the error
+ * @param lineNumber the line number of the source
+ * @param columnNumber the columnNumber of the source (may be zero if
+ * unknown)
+ * @param lineSource the source of the line containing the error (may be
+ * null if unknown)
+ */
+ public EvaluatorException(String detail, String sourceName, int lineNumber,
+ String lineSource, int columnNumber)
+ {
+ super(detail);
+ recordErrorOrigin(sourceName, lineNumber, lineSource, columnNumber);
+ }
+
+ /**
+ * @deprecated Use {@link RhinoException#sourceName()} from the super class.
+ */
+ public String getSourceName()
+ {
+ return sourceName();
+ }
+
+ /**
+ * @deprecated Use {@link RhinoException#lineNumber()} from the super class.
+ */
+ public int getLineNumber()
+ {
+ return lineNumber();
+ }
+
+ /**
+ * @deprecated Use {@link RhinoException#columnNumber()} from the super class.
+ */
+ public int getColumnNumber()
+ {
+ return columnNumber();
+ }
+
+ /**
+ * @deprecated Use {@link RhinoException#lineSource()} from the super class.
+ */
+ public String getLineSource()
+ {
+ return lineSource();
+ }
+
+}
diff --git a/src/org/mozilla/javascript/Function.java b/src/org/mozilla/javascript/Function.java
new file mode 100644
index 0000000..a4377e6
--- /dev/null
+++ b/src/org/mozilla/javascript/Function.java
@@ -0,0 +1,84 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * This is interface that all functions in JavaScript must implement.
+ * The interface provides for calling functions and constructors.
+ *
+ * @see org.mozilla.javascript.Scriptable
+ * @author Norris Boyd
+ */
+
+public interface Function extends Scriptable, Callable
+{
+ /**
+ * Call the function.
+ *
+ * Note that the array of arguments is not guaranteed to have
+ * length greater than 0.
+ *
+ * @param cx the current Context for this thread
+ * @param scope the scope to execute the function relative to. This is
+ * set to the value returned by getParentScope() except
+ * when the function is called from a closure.
+ * @param thisObj the JavaScript <code>this</code> object
+ * @param args the array of arguments
+ * @return the result of the call
+ */
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args);
+
+ /**
+ * Call the function as a constructor.
+ *
+ * This method is invoked by the runtime in order to satisfy a use
+ * of the JavaScript <code>new</code> operator. This method is
+ * expected to create a new object and return it.
+ *
+ * @param cx the current Context for this thread
+ * @param scope an enclosing scope of the caller except
+ * when the function is called from a closure.
+ * @param args the array of arguments
+ * @return the allocated object
+ */
+ public Scriptable construct(Context cx, Scriptable scope, Object[] args);
+}
diff --git a/src/org/mozilla/javascript/FunctionNode.java b/src/org/mozilla/javascript/FunctionNode.java
new file mode 100644
index 0000000..2790953
--- /dev/null
+++ b/src/org/mozilla/javascript/FunctionNode.java
@@ -0,0 +1,117 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class FunctionNode extends ScriptOrFnNode {
+
+ public FunctionNode(String name) {
+ super(Token.FUNCTION);
+ functionName = name;
+ }
+
+ public String getFunctionName() {
+ return functionName;
+ }
+
+ public boolean requiresActivation() {
+ return itsNeedsActivation;
+ }
+
+ public boolean getIgnoreDynamicScope() {
+ return itsIgnoreDynamicScope;
+ }
+
+ public boolean isGenerator() {
+ return itsIsGenerator;
+ }
+
+ public void addResumptionPoint(Node target) {
+ if (generatorResumePoints == null)
+ generatorResumePoints = new ArrayList<Node>();
+ generatorResumePoints.add(target);
+ }
+
+ public ArrayList<Node> getResumptionPoints() {
+ return generatorResumePoints;
+ }
+
+ public HashMap<Node,int[]> getLiveLocals() {
+ return liveLocals;
+ }
+
+ public void addLiveLocals(Node node, int[] locals) {
+ if (liveLocals == null)
+ liveLocals = new HashMap<Node,int[]>();
+ liveLocals.put(node, locals);
+ }
+
+ /**
+ * There are three types of functions that can be defined. The first
+ * is a function statement. This is a function appearing as a top-level
+ * statement (i.e., not nested inside some other statement) in either a
+ * script or a function.
+ *
+ * The second is a function expression, which is a function appearing in
+ * an expression except for the third type, which is...
+ *
+ * The third type is a function expression where the expression is the
+ * top-level expression in an expression statement.
+ *
+ * The three types of functions have different treatment and must be
+ * distinguished.
+ */
+ public static final int FUNCTION_STATEMENT = 1;
+ public static final int FUNCTION_EXPRESSION = 2;
+ public static final int FUNCTION_EXPRESSION_STATEMENT = 3;
+
+ public int getFunctionType() {
+ return itsFunctionType;
+ }
+
+ String functionName;
+ int itsFunctionType;
+ boolean itsNeedsActivation;
+ boolean itsIgnoreDynamicScope;
+ boolean itsIsGenerator;
+ ArrayList<Node> generatorResumePoints;
+ HashMap<Node,int[]> liveLocals;
+}
diff --git a/src/org/mozilla/javascript/FunctionObject.java b/src/org/mozilla/javascript/FunctionObject.java
new file mode 100644
index 0000000..1d6c752
--- /dev/null
+++ b/src/org/mozilla/javascript/FunctionObject.java
@@ -0,0 +1,572 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * David C. Navas
+ * Ted Neward
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.*;
+import java.io.*;
+
+public class FunctionObject extends BaseFunction
+{
+ static final long serialVersionUID = -5332312783643935019L;
+
+ /**
+ * Create a JavaScript function object from a Java method.
+ *
+ * <p>The <code>member</code> argument must be either a java.lang.reflect.Method
+ * or a java.lang.reflect.Constructor and must match one of two forms.<p>
+ *
+ * The first form is a member with zero or more parameters
+ * of the following types: Object, String, boolean, Scriptable,
+ * int, or double. The Long type is not supported
+ * because the double representation of a long (which is the
+ * EMCA-mandated storage type for Numbers) may lose precision.
+ * If the member is a Method, the return value must be void or one
+ * of the types allowed for parameters.<p>
+ *
+ * The runtime will perform appropriate conversions based
+ * upon the type of the parameter. A parameter type of
+ * Object specifies that no conversions are to be done. A parameter
+ * of type String will use Context.toString to convert arguments.
+ * Similarly, parameters of type double, boolean, and Scriptable
+ * will cause Context.toNumber, Context.toBoolean, and
+ * Context.toObject, respectively, to be called.<p>
+ *
+ * If the method is not static, the Java 'this' value will
+ * correspond to the JavaScript 'this' value. Any attempt
+ * to call the function with a 'this' value that is not
+ * of the right Java type will result in an error.<p>
+ *
+ * The second form is the variable arguments (or "varargs")
+ * form. If the FunctionObject will be used as a constructor,
+ * the member must have the following parameters
+ * <pre>
+ * (Context cx, Object[] args, Function ctorObj,
+ * boolean inNewExpr)</pre>
+ * and if it is a Method, be static and return an Object result.<p>
+ *
+ * Otherwise, if the FunctionObject will <i>not</i> be used to define a
+ * constructor, the member must be a static Method with parameters
+ * (Context cx, Scriptable thisObj, Object[] args,
+ * Function funObj) </pre>
+ * <pre>
+ * and an Object result.<p>
+ *
+ * When the function varargs form is called as part of a function call,
+ * the <code>args</code> parameter contains the
+ * arguments, with <code>thisObj</code>
+ * set to the JavaScript 'this' value. <code>funObj</code>
+ * is the function object for the invoked function.<p>
+ *
+ * When the constructor varargs form is called or invoked while evaluating
+ * a <code>new</code> expression, <code>args</code> contains the
+ * arguments, <code>ctorObj</code> refers to this FunctionObject, and
+ * <code>inNewExpr</code> is true if and only if a <code>new</code>
+ * expression caused the call. This supports defining a function that
+ * has different behavior when called as a constructor than when
+ * invoked as a normal function call. (For example, the Boolean
+ * constructor, when called as a function,
+ * will convert to boolean rather than creating a new object.)<p>
+ *
+ * @param name the name of the function
+ * @param methodOrConstructor a java.lang.reflect.Method or a java.lang.reflect.Constructor
+ * that defines the object
+ * @param scope enclosing scope of function
+ * @see org.mozilla.javascript.Scriptable
+ */
+ public FunctionObject(String name, Member methodOrConstructor,
+ Scriptable scope)
+ {
+ if (methodOrConstructor instanceof Constructor) {
+ member = new MemberBox((Constructor<?>) methodOrConstructor);
+ isStatic = true; // well, doesn't take a 'this'
+ } else {
+ member = new MemberBox((Method) methodOrConstructor);
+ isStatic = member.isStatic();
+ }
+ String methodName = member.getName();
+ this.functionName = name;
+ Class<?>[] types = member.argTypes;
+ int arity = types.length;
+ if (arity == 4 && (types[1].isArray() || types[2].isArray())) {
+ // Either variable args or an error.
+ if (types[1].isArray()) {
+ if (!isStatic ||
+ types[0] != ScriptRuntime.ContextClass ||
+ types[1].getComponentType() != ScriptRuntime.ObjectClass ||
+ types[2] != ScriptRuntime.FunctionClass ||
+ types[3] != Boolean.TYPE)
+ {
+ throw Context.reportRuntimeError1(
+ "msg.varargs.ctor", methodName);
+ }
+ parmsLength = VARARGS_CTOR;
+ } else {
+ if (!isStatic ||
+ types[0] != ScriptRuntime.ContextClass ||
+ types[1] != ScriptRuntime.ScriptableClass ||
+ types[2].getComponentType() != ScriptRuntime.ObjectClass ||
+ types[3] != ScriptRuntime.FunctionClass)
+ {
+ throw Context.reportRuntimeError1(
+ "msg.varargs.fun", methodName);
+ }
+ parmsLength = VARARGS_METHOD;
+ }
+ } else {
+ parmsLength = arity;
+ if (arity > 0) {
+ typeTags = new byte[arity];
+ for (int i = 0; i != arity; ++i) {
+ int tag = getTypeTag(types[i]);
+ if (tag == JAVA_UNSUPPORTED_TYPE) {
+ throw Context.reportRuntimeError2(
+ "msg.bad.parms", types[i].getName(), methodName);
+ }
+ typeTags[i] = (byte)tag;
+ }
+ }
+ }
+
+ if (member.isMethod()) {
+ Method method = member.method();
+ Class<?> returnType = method.getReturnType();
+ if (returnType == Void.TYPE) {
+ hasVoidReturn = true;
+ } else {
+ returnTypeTag = getTypeTag(returnType);
+ }
+ } else {
+ Class<?> ctorType = member.getDeclaringClass();
+ if (!ScriptRuntime.ScriptableClass.isAssignableFrom(ctorType)) {
+ throw Context.reportRuntimeError1(
+ "msg.bad.ctor.return", ctorType.getName());
+ }
+ }
+
+ ScriptRuntime.setFunctionProtoAndParent(this, scope);
+ }
+
+ /**
+ * @return One of <tt>JAVA_*_TYPE</tt> constants to indicate desired type
+ * or {@link #JAVA_UNSUPPORTED_TYPE} if the convertion is not
+ * possible
+ */
+ public static int getTypeTag(Class<?> type)
+ {
+ if (type == ScriptRuntime.StringClass)
+ return JAVA_STRING_TYPE;
+ if (type == ScriptRuntime.IntegerClass || type == Integer.TYPE)
+ return JAVA_INT_TYPE;
+ if (type == ScriptRuntime.BooleanClass || type == Boolean.TYPE)
+ return JAVA_BOOLEAN_TYPE;
+ if (type == ScriptRuntime.DoubleClass || type == Double.TYPE)
+ return JAVA_DOUBLE_TYPE;
+ if (ScriptRuntime.ScriptableClass.isAssignableFrom(type))
+ return JAVA_SCRIPTABLE_TYPE;
+ if (type == ScriptRuntime.ObjectClass)
+ return JAVA_OBJECT_TYPE;
+
+ // Note that the long type is not supported; see the javadoc for
+ // the constructor for this class
+
+ return JAVA_UNSUPPORTED_TYPE;
+ }
+
+ public static Object convertArg(Context cx, Scriptable scope,
+ Object arg, int typeTag)
+ {
+ switch (typeTag) {
+ case JAVA_STRING_TYPE:
+ if (arg instanceof String)
+ return arg;
+ return ScriptRuntime.toString(arg);
+ case JAVA_INT_TYPE:
+ if (arg instanceof Integer)
+ return arg;
+ return new Integer(ScriptRuntime.toInt32(arg));
+ case JAVA_BOOLEAN_TYPE:
+ if (arg instanceof Boolean)
+ return arg;
+ return ScriptRuntime.toBoolean(arg) ? Boolean.TRUE
+ : Boolean.FALSE;
+ case JAVA_DOUBLE_TYPE:
+ if (arg instanceof Double)
+ return arg;
+ return new Double(ScriptRuntime.toNumber(arg));
+ case JAVA_SCRIPTABLE_TYPE:
+ return ScriptRuntime.toObjectOrNull(cx, arg, scope);
+ case JAVA_OBJECT_TYPE:
+ return arg;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Return the value defined by the method used to construct the object
+ * (number of parameters of the method, or 1 if the method is a "varargs"
+ * form).
+ */
+ @Override
+ public int getArity() {
+ return parmsLength < 0 ? 1 : parmsLength;
+ }
+
+ /**
+ * Return the same value as {@link #getArity()}.
+ */
+ @Override
+ public int getLength() {
+ return getArity();
+ }
+
+ @Override
+ public String getFunctionName()
+ {
+ return (functionName == null) ? "" : functionName;
+ }
+
+ /**
+ * Get Java method or constructor this function represent.
+ */
+ public Member getMethodOrConstructor()
+ {
+ if (member.isMethod()) {
+ return member.method();
+ } else {
+ return member.ctor();
+ }
+ }
+
+ static Method findSingleMethod(Method[] methods, String name)
+ {
+ Method found = null;
+ for (int i = 0, N = methods.length; i != N; ++i) {
+ Method method = methods[i];
+ if (method != null && name.equals(method.getName())) {
+ if (found != null) {
+ throw Context.reportRuntimeError2(
+ "msg.no.overload", name,
+ method.getDeclaringClass().getName());
+ }
+ found = method;
+ }
+ }
+ return found;
+ }
+
+ /**
+ * Returns all public methods declared by the specified class. This excludes
+ * inherited methods.
+ *
+ * @param clazz the class from which to pull public declared methods
+ * @return the public methods declared in the specified class
+ * @see Class#getDeclaredMethods()
+ */
+ static Method[] getMethodList(Class<?> clazz) {
+ Method[] methods = null;
+ try {
+ // getDeclaredMethods may be rejected by the security manager
+ // but getMethods is more expensive
+ if (!sawSecurityException)
+ methods = clazz.getDeclaredMethods();
+ } catch (SecurityException e) {
+ // If we get an exception once, give up on getDeclaredMethods
+ sawSecurityException = true;
+ }
+ if (methods == null) {
+ methods = clazz.getMethods();
+ }
+ int count = 0;
+ for (int i=0; i < methods.length; i++) {
+ if (sawSecurityException
+ ? methods[i].getDeclaringClass() != clazz
+ : !Modifier.isPublic(methods[i].getModifiers()))
+ {
+ methods[i] = null;
+ } else {
+ count++;
+ }
+ }
+ Method[] result = new Method[count];
+ int j=0;
+ for (int i=0; i < methods.length; i++) {
+ if (methods[i] != null)
+ result[j++] = methods[i];
+ }
+ return result;
+ }
+
+ /**
+ * Define this function as a JavaScript constructor.
+ * <p>
+ * Sets up the "prototype" and "constructor" properties. Also
+ * calls setParent and setPrototype with appropriate values.
+ * Then adds the function object as a property of the given scope, using
+ * <code>prototype.getClassName()</code>
+ * as the name of the property.
+ *
+ * @param scope the scope in which to define the constructor (typically
+ * the global object)
+ * @param prototype the prototype object
+ * @see org.mozilla.javascript.Scriptable#setParentScope
+ * @see org.mozilla.javascript.Scriptable#setPrototype
+ * @see org.mozilla.javascript.Scriptable#getClassName
+ */
+ public void addAsConstructor(Scriptable scope, Scriptable prototype)
+ {
+ initAsConstructor(scope, prototype);
+ defineProperty(scope, prototype.getClassName(),
+ this, ScriptableObject.DONTENUM);
+ }
+
+ void initAsConstructor(Scriptable scope, Scriptable prototype)
+ {
+ ScriptRuntime.setFunctionProtoAndParent(this, scope);
+ setImmunePrototypeProperty(prototype);
+
+ prototype.setParentScope(this);
+
+ defineProperty(prototype, "constructor", this,
+ ScriptableObject.DONTENUM |
+ ScriptableObject.PERMANENT |
+ ScriptableObject.READONLY);
+ setParentScope(scope);
+ }
+
+ /**
+ * @deprecated Use {@link #getTypeTag(Class)}
+ * and {@link #convertArg(Context, Scriptable, Object, int)}
+ * for type conversion.
+ */
+ public static Object convertArg(Context cx, Scriptable scope,
+ Object arg, Class<?> desired)
+ {
+ int tag = getTypeTag(desired);
+ if (tag == JAVA_UNSUPPORTED_TYPE) {
+ throw Context.reportRuntimeError1
+ ("msg.cant.convert", desired.getName());
+ }
+ return convertArg(cx, scope, arg, tag);
+ }
+
+ /**
+ * Performs conversions on argument types if needed and
+ * invokes the underlying Java method or constructor.
+ * <p>
+ * Implements Function.call.
+ *
+ * @see org.mozilla.javascript.Function#call(
+ * Context, Scriptable, Scriptable, Object[])
+ */
+ @Override
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ Object result;
+ boolean checkMethodResult = false;
+
+ if (parmsLength < 0) {
+ if (parmsLength == VARARGS_METHOD) {
+ Object[] invokeArgs = { cx, thisObj, args, this };
+ result = member.invoke(null, invokeArgs);
+ checkMethodResult = true;
+ } else {
+ boolean inNewExpr = (thisObj == null);
+ Boolean b = inNewExpr ? Boolean.TRUE : Boolean.FALSE;
+ Object[] invokeArgs = { cx, args, this, b };
+ result = (member.isCtor())
+ ? member.newInstance(invokeArgs)
+ : member.invoke(null, invokeArgs);
+ }
+
+ } else {
+ if (!isStatic) {
+ Class<?> clazz = member.getDeclaringClass();
+ if (!clazz.isInstance(thisObj)) {
+ boolean compatible = false;
+ if (thisObj == scope) {
+ Scriptable parentScope = getParentScope();
+ if (scope != parentScope) {
+ // Call with dynamic scope for standalone function,
+ // use parentScope as thisObj
+ compatible = clazz.isInstance(parentScope);
+ if (compatible) {
+ thisObj = parentScope;
+ }
+ }
+ }
+ if (!compatible) {
+ // Couldn't find an object to call this on.
+ throw ScriptRuntime.typeError1("msg.incompat.call",
+ functionName);
+ }
+ }
+ }
+
+ Object[] invokeArgs;
+ if (parmsLength == args.length) {
+ // Do not allocate new argument array if java arguments are
+ // the same as the original js ones.
+ invokeArgs = args;
+ for (int i = 0; i != parmsLength; ++i) {
+ Object arg = args[i];
+ Object converted = convertArg(cx, scope, arg, typeTags[i]);
+ if (arg != converted) {
+ if (invokeArgs == args) {
+ invokeArgs = args.clone();
+ }
+ invokeArgs[i] = converted;
+ }
+ }
+ } else if (parmsLength == 0) {
+ invokeArgs = ScriptRuntime.emptyArgs;
+ } else {
+ invokeArgs = new Object[parmsLength];
+ for (int i = 0; i != parmsLength; ++i) {
+ Object arg = (i < args.length)
+ ? args[i]
+ : Undefined.instance;
+ invokeArgs[i] = convertArg(cx, scope, arg, typeTags[i]);
+ }
+ }
+
+ if (member.isMethod()) {
+ result = member.invoke(thisObj, invokeArgs);
+ checkMethodResult = true;
+ } else {
+ result = member.newInstance(invokeArgs);
+ }
+
+ }
+
+ if (checkMethodResult) {
+ if (hasVoidReturn) {
+ result = Undefined.instance;
+ } else if (returnTypeTag == JAVA_UNSUPPORTED_TYPE) {
+ result = cx.getWrapFactory().wrap(cx, scope, result, null);
+ }
+ // XXX: the code assumes that if returnTypeTag == JAVA_OBJECT_TYPE
+ // then the Java method did a proper job of converting the
+ // result to JS primitive or Scriptable to avoid
+ // potentially costly Context.javaToJS call.
+ }
+
+ return result;
+ }
+
+ /**
+ * Return new {@link Scriptable} instance using the default
+ * constructor for the class of the underlying Java method.
+ * Return null to indicate that the call method should be used to create
+ * new objects.
+ */
+ @Override
+ public Scriptable createObject(Context cx, Scriptable scope) {
+ if (member.isCtor() || parmsLength == VARARGS_CTOR) {
+ return null;
+ }
+ Scriptable result;
+ try {
+ result = (Scriptable) member.getDeclaringClass().newInstance();
+ } catch (Exception ex) {
+ throw Context.throwAsScriptRuntimeEx(ex);
+ }
+
+ result.setPrototype(getClassPrototype());
+ result.setParentScope(getParentScope());
+ return result;
+ }
+
+ boolean isVarArgsMethod() {
+ return parmsLength == VARARGS_METHOD;
+ }
+
+ boolean isVarArgsConstructor() {
+ return parmsLength == VARARGS_CTOR;
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+ if (parmsLength > 0) {
+ Class<?>[] types = member.argTypes;
+ typeTags = new byte[parmsLength];
+ for (int i = 0; i != parmsLength; ++i) {
+ typeTags[i] = (byte)getTypeTag(types[i]);
+ }
+ }
+ if (member.isMethod()) {
+ Method method = member.method();
+ Class<?> returnType = method.getReturnType();
+ if (returnType == Void.TYPE) {
+ hasVoidReturn = true;
+ } else {
+ returnTypeTag = getTypeTag(returnType);
+ }
+ }
+ }
+
+ private static final short VARARGS_METHOD = -1;
+ private static final short VARARGS_CTOR = -2;
+
+ private static boolean sawSecurityException;
+
+ public static final int JAVA_UNSUPPORTED_TYPE = 0;
+ public static final int JAVA_STRING_TYPE = 1;
+ public static final int JAVA_INT_TYPE = 2;
+ public static final int JAVA_BOOLEAN_TYPE = 3;
+ public static final int JAVA_DOUBLE_TYPE = 4;
+ public static final int JAVA_SCRIPTABLE_TYPE = 5;
+ public static final int JAVA_OBJECT_TYPE = 6;
+
+ MemberBox member;
+ private String functionName;
+ private transient byte[] typeTags;
+ private int parmsLength;
+ private transient boolean hasVoidReturn;
+ private transient int returnTypeTag;
+ private boolean isStatic;
+}
diff --git a/src/org/mozilla/javascript/GeneratedClassLoader.java b/src/org/mozilla/javascript/GeneratedClassLoader.java
new file mode 100644
index 0000000..fb5ecf6
--- /dev/null
+++ b/src/org/mozilla/javascript/GeneratedClassLoader.java
@@ -0,0 +1,66 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * Interface to define classes from generated byte code.
+ */
+public interface GeneratedClassLoader {
+
+ /**
+ * Define a new Java class.
+ * Classes created via this method should have the same class loader.
+ *
+ * @param name fully qualified class name
+ * @param data class byte code
+ * @return new class object
+ */
+ public Class<?> defineClass(String name, byte[] data);
+
+ /**
+ * Link the given class.
+ *
+ * @param cl Class instance returned from the previous call to
+ * {@link #defineClass(String, byte[])}
+ * @see java.lang.ClassLoader
+ */
+ public void linkClass(Class<?> cl);
+}
diff --git a/src/org/mozilla/javascript/IRFactory.java b/src/org/mozilla/javascript/IRFactory.java
new file mode 100644
index 0000000..401c911
--- /dev/null
+++ b/src/org/mozilla/javascript/IRFactory.java
@@ -0,0 +1,1607 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Ethan Hugg
+ * Bob Jervis
+ * Terry Lucas
+ * Milen Nankov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * This class allows the creation of nodes, and follows the Factory pattern.
+ *
+ * @see Node
+ * @author Mike McCabe
+ * @author Norris Boyd
+ */
+final class IRFactory
+{
+ IRFactory(Parser parser)
+ {
+ this.parser = parser;
+ }
+
+ ScriptOrFnNode createScript()
+ {
+ return new ScriptOrFnNode(Token.SCRIPT);
+ }
+
+ /**
+ * Script (for associating file/url names with toplevel scripts.)
+ */
+ void initScript(ScriptOrFnNode scriptNode, Node body)
+ {
+ Node children = body.getFirstChild();
+ if (children != null) { scriptNode.addChildrenToBack(children); }
+ }
+
+ /**
+ * Leaf
+ */
+ Node createLeaf(int nodeType)
+ {
+ return new Node(nodeType);
+ }
+
+ /**
+ * Statement leaf nodes.
+ */
+
+ Node createSwitch(Node expr, int lineno)
+ {
+ //
+ // The switch will be rewritten from:
+ //
+ // switch (expr) {
+ // case test1: statements1;
+ // ...
+ // default: statementsDefault;
+ // ...
+ // case testN: statementsN;
+ // }
+ //
+ // to:
+ //
+ // {
+ // switch (expr) {
+ // case test1: goto label1;
+ // ...
+ // case testN: goto labelN;
+ // }
+ // goto labelDefault;
+ // label1:
+ // statements1;
+ // ...
+ // labelDefault:
+ // statementsDefault;
+ // ...
+ // labelN:
+ // statementsN;
+ // breakLabel:
+ // }
+ //
+ // where inside switch each "break;" without label will be replaced
+ // by "goto breakLabel".
+ //
+ // If the original switch does not have the default label, then
+ // the transformed code would contain after the switch instead of
+ // goto labelDefault;
+ // the following goto:
+ // goto breakLabel;
+ //
+
+ Node.Jump switchNode = new Node.Jump(Token.SWITCH, expr, lineno);
+ Node block = new Node(Token.BLOCK, switchNode);
+ return block;
+ }
+
+ /**
+ * If caseExpression argument is null it indicate default label.
+ */
+ void addSwitchCase(Node switchBlock, Node caseExpression, Node statements)
+ {
+ if (switchBlock.getType() != Token.BLOCK) throw Kit.codeBug();
+ Node.Jump switchNode = (Node.Jump)switchBlock.getFirstChild();
+ if (switchNode.getType() != Token.SWITCH) throw Kit.codeBug();
+
+ Node gotoTarget = Node.newTarget();
+ if (caseExpression != null) {
+ Node.Jump caseNode = new Node.Jump(Token.CASE, caseExpression);
+ caseNode.target = gotoTarget;
+ switchNode.addChildToBack(caseNode);
+ } else {
+ switchNode.setDefault(gotoTarget);
+ }
+ switchBlock.addChildToBack(gotoTarget);
+ switchBlock.addChildToBack(statements);
+ }
+
+ void closeSwitch(Node switchBlock)
+ {
+ if (switchBlock.getType() != Token.BLOCK) throw Kit.codeBug();
+ Node.Jump switchNode = (Node.Jump)switchBlock.getFirstChild();
+ if (switchNode.getType() != Token.SWITCH) throw Kit.codeBug();
+
+ Node switchBreakTarget = Node.newTarget();
+ // switchNode.target is only used by NodeTransformer
+ // to detect switch end
+ switchNode.target = switchBreakTarget;
+
+ Node defaultTarget = switchNode.getDefault();
+ if (defaultTarget == null) {
+ defaultTarget = switchBreakTarget;
+ }
+
+ switchBlock.addChildAfter(makeJump(Token.GOTO, defaultTarget),
+ switchNode);
+ switchBlock.addChildToBack(switchBreakTarget);
+ }
+
+ Node createVariables(int token, int lineno)
+ {
+ return new Node(token, lineno);
+ }
+
+ Node createExprStatement(Node expr, int lineno)
+ {
+ int type;
+ if (parser.insideFunction()) {
+ type = Token.EXPR_VOID;
+ } else {
+ type = Token.EXPR_RESULT;
+ }
+ return new Node(type, expr, lineno);
+ }
+
+ Node createExprStatementNoReturn(Node expr, int lineno)
+ {
+ return new Node(Token.EXPR_VOID, expr, lineno);
+ }
+
+ Node createDefaultNamespace(Node expr, int lineno)
+ {
+ // default xml namespace requires activation
+ setRequiresActivation();
+ Node n = createUnary(Token.DEFAULTNAMESPACE, expr);
+ Node result = createExprStatement(n, lineno);
+ return result;
+ }
+
+ /**
+ * Name
+ */
+ Node createName(String name)
+ {
+ checkActivationName(name, Token.NAME);
+ return Node.newString(Token.NAME, name);
+ }
+
+ private Node createName(int type, String name, Node child)
+ {
+ Node result = createName(name);
+ result.setType(type);
+ if (child != null)
+ result.addChildToBack(child);
+ return result;
+ }
+
+ /**
+ * String (for literals)
+ */
+ Node createString(String string)
+ {
+ return Node.newString(string);
+ }
+
+ /**
+ * Number (for literals)
+ */
+ Node createNumber(double number)
+ {
+ return Node.newNumber(number);
+ }
+
+ /**
+ * Catch clause of try/catch/finally
+ * @param varName the name of the variable to bind to the exception
+ * @param catchCond the condition under which to catch the exception.
+ * May be null if no condition is given.
+ * @param stmts the statements in the catch clause
+ * @param lineno the starting line number of the catch clause
+ */
+ Node createCatch(String varName, Node catchCond, Node stmts, int lineno)
+ {
+ if (catchCond == null) {
+ catchCond = new Node(Token.EMPTY);
+ }
+ return new Node(Token.CATCH, createName(varName),
+ catchCond, stmts, lineno);
+ }
+
+ /**
+ * Throw
+ */
+ Node createThrow(Node expr, int lineno)
+ {
+ return new Node(Token.THROW, expr, lineno);
+ }
+
+ /**
+ * Return
+ */
+ Node createReturn(Node expr, int lineno)
+ {
+ return expr == null
+ ? new Node(Token.RETURN, lineno)
+ : new Node(Token.RETURN, expr, lineno);
+ }
+
+ /**
+ * Debugger
+ */
+ Node createDebugger(int lineno)
+ {
+ return new Node(Token.DEBUGGER, lineno);
+ }
+
+ /**
+ * Label
+ */
+ Node createLabel(int lineno)
+ {
+ return new Node.Jump(Token.LABEL, lineno);
+ }
+
+ Node getLabelLoop(Node label)
+ {
+ return ((Node.Jump)label).getLoop();
+ }
+
+ /**
+ * Label
+ */
+ Node createLabeledStatement(Node labelArg, Node statement)
+ {
+ Node.Jump label = (Node.Jump)labelArg;
+
+ // Make a target and put it _after_ the statement
+ // node. And in the LABEL node, so breaks get the
+ // right target.
+
+ Node breakTarget = Node.newTarget();
+ Node block = new Node(Token.BLOCK, label, statement, breakTarget);
+ label.target = breakTarget;
+
+ return block;
+ }
+
+ /**
+ * Break (possibly labeled)
+ */
+ Node createBreak(Node breakStatement, int lineno)
+ {
+ Node.Jump n = new Node.Jump(Token.BREAK, lineno);
+ Node.Jump jumpStatement;
+ int t = breakStatement.getType();
+ if (t == Token.LOOP || t == Token.LABEL) {
+ jumpStatement = (Node.Jump)breakStatement;
+ } else if (t == Token.BLOCK
+ && breakStatement.getFirstChild().getType() == Token.SWITCH)
+ {
+ jumpStatement = (Node.Jump)breakStatement.getFirstChild();
+ } else {
+ throw Kit.codeBug();
+ }
+ n.setJumpStatement(jumpStatement);
+ return n;
+ }
+
+ /**
+ * Continue (possibly labeled)
+ */
+ Node createContinue(Node loop, int lineno)
+ {
+ if (loop.getType() != Token.LOOP) Kit.codeBug();
+ Node.Jump n = new Node.Jump(Token.CONTINUE, lineno);
+ n.setJumpStatement((Node.Jump)loop);
+ return n;
+ }
+
+ /**
+ * Statement block
+ * Creates the empty statement block
+ * Must make subsequent calls to add statements to the node
+ */
+ Node createBlock(int lineno)
+ {
+ return new Node(Token.BLOCK, lineno);
+ }
+
+ FunctionNode createFunction(String name)
+ {
+ return new FunctionNode(name);
+ }
+
+ Node initFunction(FunctionNode fnNode, int functionIndex,
+ Node statements, int functionType)
+ {
+ fnNode.itsFunctionType = functionType;
+ fnNode.addChildToBack(statements);
+
+ int functionCount = fnNode.getFunctionCount();
+ if (functionCount != 0) {
+ // Functions containing other functions require activation objects
+ fnNode.itsNeedsActivation = true;
+ }
+
+ if (functionType == FunctionNode.FUNCTION_EXPRESSION) {
+ String name = fnNode.getFunctionName();
+ if (name != null && name.length() != 0) {
+ // A function expression needs to have its name as a
+ // variable (if it isn't already allocated as a variable).
+ // See ECMA Ch. 13. We add code to the beginning of the
+ // function to initialize a local variable of the
+ // function's name to the function value.
+ Node setFn = new Node(Token.EXPR_VOID,
+ new Node(Token.SETNAME,
+ Node.newString(Token.BINDNAME, name),
+ new Node(Token.THISFN)));
+ statements.addChildrenToFront(setFn);
+ }
+ }
+
+ // Add return to end if needed.
+ Node lastStmt = statements.getLastChild();
+ if (lastStmt == null || lastStmt.getType() != Token.RETURN) {
+ statements.addChildToBack(new Node(Token.RETURN));
+ }
+
+ Node result = Node.newString(Token.FUNCTION,
+ fnNode.getFunctionName());
+ result.putIntProp(Node.FUNCTION_PROP, functionIndex);
+ return result;
+ }
+
+ /**
+ * Add a child to the back of the given node. This function
+ * breaks the Factory abstraction, but it removes a requirement
+ * from implementors of Node.
+ */
+ void addChildToBack(Node parent, Node child)
+ {
+ parent.addChildToBack(child);
+ }
+
+ /**
+ * Create a node that can be used to hold lexically scoped variable
+ * definitions (via let declarations).
+ *
+ * @param token the token of the node to create
+ * @param lineno line number of source
+ * @return the created node
+ */
+ Node createScopeNode(int token, int lineno) {
+ return new Node.Scope(token, lineno);
+ }
+
+ /**
+ * Create loop node. The parser will later call
+ * createWhile|createDoWhile|createFor|createForIn
+ * to finish loop generation.
+ */
+ Node createLoopNode(Node loopLabel, int lineno)
+ {
+ Node.Jump result = new Node.Scope(Token.LOOP, lineno);
+ if (loopLabel != null) {
+ ((Node.Jump)loopLabel).setLoop(result);
+ }
+ return result;
+ }
+
+ /**
+ * While
+ */
+ Node createWhile(Node loop, Node cond, Node body)
+ {
+ return createLoop((Node.Jump)loop, LOOP_WHILE, body, cond,
+ null, null);
+ }
+
+ /**
+ * DoWhile
+ */
+ Node createDoWhile(Node loop, Node body, Node cond)
+ {
+ return createLoop((Node.Jump)loop, LOOP_DO_WHILE, body, cond,
+ null, null);
+ }
+
+ /**
+ * For
+ */
+ Node createFor(Node loop, Node init, Node test, Node incr, Node body)
+ {
+ if (init.getType() == Token.LET) {
+ // rewrite "for (let i=s; i < N; i++)..." as
+ // "let (i=s) { for (; i < N; i++)..." so that "s" is evaluated
+ // outside the scope of the for.
+ Node.Scope let = Node.Scope.splitScope((Node.Scope)loop);
+ let.setType(Token.LET);
+ let.addChildrenToBack(init);
+ let.addChildToBack(createLoop((Node.Jump)loop, LOOP_FOR, body, test,
+ new Node(Token.EMPTY), incr));
+ return let;
+ }
+ return createLoop((Node.Jump)loop, LOOP_FOR, body, test, init, incr);
+ }
+
+ private Node createLoop(Node.Jump loop, int loopType, Node body, Node cond,
+ Node init, Node incr)
+ {
+ Node bodyTarget = Node.newTarget();
+ Node condTarget = Node.newTarget();
+ if (loopType == LOOP_FOR && cond.getType() == Token.EMPTY) {
+ cond = new Node(Token.TRUE);
+ }
+ Node.Jump IFEQ = new Node.Jump(Token.IFEQ, cond);
+ IFEQ.target = bodyTarget;
+ Node breakTarget = Node.newTarget();
+
+ loop.addChildToBack(bodyTarget);
+ loop.addChildrenToBack(body);
+ if (loopType == LOOP_WHILE || loopType == LOOP_FOR) {
+ // propagate lineno to condition
+ loop.addChildrenToBack(new Node(Token.EMPTY, loop.getLineno()));
+ }
+ loop.addChildToBack(condTarget);
+ loop.addChildToBack(IFEQ);
+ loop.addChildToBack(breakTarget);
+
+ loop.target = breakTarget;
+ Node continueTarget = condTarget;
+
+ if (loopType == LOOP_WHILE || loopType == LOOP_FOR) {
+ // Just add a GOTO to the condition in the do..while
+ loop.addChildToFront(makeJump(Token.GOTO, condTarget));
+
+ if (loopType == LOOP_FOR) {
+ int initType = init.getType();
+ if (initType != Token.EMPTY) {
+ if (initType != Token.VAR && initType != Token.LET) {
+ init = new Node(Token.EXPR_VOID, init);
+ }
+ loop.addChildToFront(init);
+ }
+ Node incrTarget = Node.newTarget();
+ loop.addChildAfter(incrTarget, body);
+ if (incr.getType() != Token.EMPTY) {
+ incr = new Node(Token.EXPR_VOID, incr);
+ loop.addChildAfter(incr, incrTarget);
+ }
+ continueTarget = incrTarget;
+ }
+ }
+
+ loop.setContinue(continueTarget);
+
+ return loop;
+ }
+
+ /**
+ * For .. In
+ *
+ */
+ Node createForIn(int declType, Node loop, Node lhs, Node obj, Node body,
+ boolean isForEach)
+ {
+ int destructuring = -1;
+ int destructuringLen = 0;
+ Node lvalue;
+ int type = lhs.getType();
+ if (type == Token.VAR || type == Token.LET) {
+ Node lastChild = lhs.getLastChild();
+ if (lhs.getFirstChild() != lastChild) {
+ /*
+ * check that there was only one variable given.
+ * we can't do this in the parser, because then the
+ * parser would have to know something about the
+ * 'init' node of the for-in loop.
+ */
+ parser.reportError("msg.mult.index");
+ }
+ if (lastChild.getType() == Token.ARRAYLIT ||
+ lastChild.getType() == Token.OBJECTLIT)
+ {
+ type = destructuring = lastChild.getType();
+ lvalue = lastChild;
+ destructuringLen = lastChild.getIntProp(
+ Node.DESTRUCTURING_ARRAY_LENGTH, 0);
+ } else if (lastChild.getType() == Token.NAME) {
+ lvalue = Node.newString(Token.NAME, lastChild.getString());
+ } else {
+ parser.reportError("msg.bad.for.in.lhs");
+ return obj;
+ }
+ } else if (type == Token.ARRAYLIT || type == Token.OBJECTLIT) {
+ destructuring = type;
+ lvalue = lhs;
+ destructuringLen = lhs.getIntProp(Node.DESTRUCTURING_ARRAY_LENGTH, 0);
+ } else {
+ lvalue = makeReference(lhs);
+ if (lvalue == null) {
+ parser.reportError("msg.bad.for.in.lhs");
+ return obj;
+ }
+ }
+
+ Node localBlock = new Node(Token.LOCAL_BLOCK);
+ int initType = (isForEach) ? Token.ENUM_INIT_VALUES :
+ (destructuring != -1) ? Token.ENUM_INIT_ARRAY :
+ Token.ENUM_INIT_KEYS;
+ Node init = new Node(initType, obj);
+ init.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
+ Node cond = new Node(Token.ENUM_NEXT);
+ cond.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
+ Node id = new Node(Token.ENUM_ID);
+ id.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
+
+ Node newBody = new Node(Token.BLOCK);
+ Node assign;
+ if (destructuring != -1) {
+ assign = createDestructuringAssignment(declType, lvalue, id);
+ if (!isForEach && (destructuring == Token.OBJECTLIT ||
+ destructuringLen != 2))
+ {
+ // destructuring assignment is only allowed in for..each or
+ // with an array type of length 2 (to hold key and value)
+ parser.reportError("msg.bad.for.in.destruct");
+ }
+ } else {
+ assign = simpleAssignment(lvalue, id);
+ }
+ newBody.addChildToBack(new Node(Token.EXPR_VOID, assign));
+ newBody.addChildToBack(body);
+
+ loop = createWhile(loop, cond, newBody);
+ loop.addChildToFront(init);
+ if (type == Token.VAR || type == Token.LET)
+ loop.addChildToFront(lhs);
+ localBlock.addChildToBack(loop);
+
+ return localBlock;
+ }
+
+ /**
+ * Try/Catch/Finally
+ *
+ * The IRFactory tries to express as much as possible in the tree;
+ * the responsibilities remaining for Codegen are to add the Java
+ * handlers: (Either (but not both) of TARGET and FINALLY might not
+ * be defined)
+
+ * - a catch handler for javascript exceptions that unwraps the
+ * exception onto the stack and GOTOes to the catch target
+
+ * - a finally handler
+
+ * ... and a goto to GOTO around these handlers.
+ */
+ Node createTryCatchFinally(Node tryBlock, Node catchBlocks,
+ Node finallyBlock, int lineno)
+ {
+ boolean hasFinally = (finallyBlock != null)
+ && (finallyBlock.getType() != Token.BLOCK
+ || finallyBlock.hasChildren());
+
+ // short circuit
+ if (tryBlock.getType() == Token.BLOCK && !tryBlock.hasChildren()
+ && !hasFinally)
+ {
+ return tryBlock;
+ }
+
+ boolean hasCatch = catchBlocks.hasChildren();
+
+ // short circuit
+ if (!hasFinally && !hasCatch) {
+ // bc finally might be an empty block...
+ return tryBlock;
+ }
+
+
+ Node handlerBlock = new Node(Token.LOCAL_BLOCK);
+ Node.Jump pn = new Node.Jump(Token.TRY, tryBlock, lineno);
+ pn.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock);
+
+ if (hasCatch) {
+ // jump around catch code
+ Node endCatch = Node.newTarget();
+ pn.addChildToBack(makeJump(Token.GOTO, endCatch));
+
+ // make a TARGET for the catch that the tcf node knows about
+ Node catchTarget = Node.newTarget();
+ pn.target = catchTarget;
+ // mark it
+ pn.addChildToBack(catchTarget);
+
+ //
+ // Given
+ //
+ // try {
+ // tryBlock;
+ // } catch (e if condition1) {
+ // something1;
+ // ...
+ //
+ // } catch (e if conditionN) {
+ // somethingN;
+ // } catch (e) {
+ // somethingDefault;
+ // }
+ //
+ // rewrite as
+ //
+ // try {
+ // tryBlock;
+ // goto after_catch:
+ // } catch (x) {
+ // with (newCatchScope(e, x)) {
+ // if (condition1) {
+ // something1;
+ // goto after_catch;
+ // }
+ // }
+ // ...
+ // with (newCatchScope(e, x)) {
+ // if (conditionN) {
+ // somethingN;
+ // goto after_catch;
+ // }
+ // }
+ // with (newCatchScope(e, x)) {
+ // somethingDefault;
+ // goto after_catch;
+ // }
+ // }
+ // after_catch:
+ //
+ // If there is no default catch, then the last with block
+ // arround "somethingDefault;" is replaced by "rethrow;"
+
+ // It is assumed that catch handler generation will store
+ // exeception object in handlerBlock register
+
+ // Block with local for exception scope objects
+ Node catchScopeBlock = new Node(Token.LOCAL_BLOCK);
+
+ // expects catchblocks children to be (cond block) pairs.
+ Node cb = catchBlocks.getFirstChild();
+ boolean hasDefault = false;
+ int scopeIndex = 0;
+ while (cb != null) {
+ int catchLineNo = cb.getLineno();
+
+ Node name = cb.getFirstChild();
+ Node cond = name.getNext();
+ Node catchStatement = cond.getNext();
+ cb.removeChild(name);
+ cb.removeChild(cond);
+ cb.removeChild(catchStatement);
+
+ // Add goto to the catch statement to jump out of catch
+ // but prefix it with LEAVEWITH since try..catch produces
+ // "with"code in order to limit the scope of the exception
+ // object.
+ catchStatement.addChildToBack(new Node(Token.LEAVEWITH));
+ catchStatement.addChildToBack(makeJump(Token.GOTO, endCatch));
+
+ // Create condition "if" when present
+ Node condStmt;
+ if (cond.getType() == Token.EMPTY) {
+ condStmt = catchStatement;
+ hasDefault = true;
+ } else {
+ condStmt = createIf(cond, catchStatement, null,
+ catchLineNo);
+ }
+
+ // Generate code to create the scope object and store
+ // it in catchScopeBlock register
+ Node catchScope = new Node(Token.CATCH_SCOPE, name,
+ createUseLocal(handlerBlock));
+ catchScope.putProp(Node.LOCAL_BLOCK_PROP, catchScopeBlock);
+ catchScope.putIntProp(Node.CATCH_SCOPE_PROP, scopeIndex);
+ catchScopeBlock.addChildToBack(catchScope);
+
+ // Add with statement based on catch scope object
+ catchScopeBlock.addChildToBack(
+ createWith(createUseLocal(catchScopeBlock), condStmt,
+ catchLineNo));
+
+ // move to next cb
+ cb = cb.getNext();
+ ++scopeIndex;
+ }
+ pn.addChildToBack(catchScopeBlock);
+ if (!hasDefault) {
+ // Generate code to rethrow if no catch clause was executed
+ Node rethrow = new Node(Token.RETHROW);
+ rethrow.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock);
+ pn.addChildToBack(rethrow);
+ }
+
+ pn.addChildToBack(endCatch);
+ }
+
+ if (hasFinally) {
+ Node finallyTarget = Node.newTarget();
+ pn.setFinally(finallyTarget);
+
+ // add jsr finally to the try block
+ pn.addChildToBack(makeJump(Token.JSR, finallyTarget));
+
+ // jump around finally code
+ Node finallyEnd = Node.newTarget();
+ pn.addChildToBack(makeJump(Token.GOTO, finallyEnd));
+
+ pn.addChildToBack(finallyTarget);
+ Node fBlock = new Node(Token.FINALLY, finallyBlock);
+ fBlock.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock);
+ pn.addChildToBack(fBlock);
+
+ pn.addChildToBack(finallyEnd);
+ }
+ handlerBlock.addChildToBack(pn);
+ return handlerBlock;
+ }
+
+ /**
+ * Throw, Return, Label, Break and Continue are defined in ASTFactory.
+ */
+
+ /**
+ * With
+ */
+ Node createWith(Node obj, Node body, int lineno)
+ {
+ setRequiresActivation();
+ Node result = new Node(Token.BLOCK, lineno);
+ result.addChildToBack(new Node(Token.ENTERWITH, obj));
+ Node bodyNode = new Node(Token.WITH, body, lineno);
+ result.addChildrenToBack(bodyNode);
+ result.addChildToBack(new Node(Token.LEAVEWITH));
+ return result;
+ }
+
+ /**
+ * DOTQUERY
+ */
+ public Node createDotQuery (Node obj, Node body, int lineno)
+ {
+ setRequiresActivation();
+ Node result = new Node(Token.DOTQUERY, obj, body, lineno);
+ return result;
+ }
+
+ Node createArrayLiteral(ObjArray elems, int skipCount, int destructuringLen)
+ {
+ int length = elems.size();
+ int[] skipIndexes = null;
+ if (skipCount != 0) {
+ skipIndexes = new int[skipCount];
+ }
+ Node array = new Node(Token.ARRAYLIT);
+ for (int i = 0, j = 0; i != length; ++i) {
+ Node elem = (Node)elems.get(i);
+ if (elem != null) {
+ array.addChildToBack(elem);
+ } else {
+ skipIndexes[j] = i;
+ ++j;
+ }
+ }
+ if (skipCount != 0) {
+ array.putProp(Node.SKIP_INDEXES_PROP, skipIndexes);
+ }
+ array.putIntProp(Node.DESTRUCTURING_ARRAY_LENGTH, destructuringLen);
+ return array;
+ }
+
+ /**
+ * Object Literals
+ * <BR> createObjectLiteral rewrites its argument as object
+ * creation plus object property entries, so later compiler
+ * stages don't need to know about object literals.
+ */
+ Node createObjectLiteral(ObjArray elems)
+ {
+ int size = elems.size() / 2;
+ Node object = new Node(Token.OBJECTLIT);
+ Object[] properties;
+ if (size == 0) {
+ properties = ScriptRuntime.emptyArgs;
+ } else {
+ properties = new Object[size];
+ for (int i = 0; i != size; ++i) {
+ properties[i] = elems.get(2 * i);
+ Node value = (Node)elems.get(2 * i + 1);
+ object.addChildToBack(value);
+ }
+ }
+ object.putProp(Node.OBJECT_IDS_PROP, properties);
+ return object;
+ }
+
+ /**
+ * Regular expressions
+ */
+ Node createRegExp(int regexpIndex)
+ {
+ Node n = new Node(Token.REGEXP);
+ n.putIntProp(Node.REGEXP_PROP, regexpIndex);
+ return n;
+ }
+
+ /**
+ * If statement
+ */
+ Node createIf(Node cond, Node ifTrue, Node ifFalse, int lineno)
+ {
+ int condStatus = isAlwaysDefinedBoolean(cond);
+ if (condStatus == ALWAYS_TRUE_BOOLEAN) {
+ return ifTrue;
+ } else if (condStatus == ALWAYS_FALSE_BOOLEAN) {
+ if (ifFalse != null) {
+ return ifFalse;
+ }
+ // Replace if (false) xxx by empty block
+ return new Node(Token.BLOCK, lineno);
+ }
+
+ Node result = new Node(Token.BLOCK, lineno);
+ Node ifNotTarget = Node.newTarget();
+ Node.Jump IFNE = new Node.Jump(Token.IFNE, cond);
+ IFNE.target = ifNotTarget;
+
+ result.addChildToBack(IFNE);
+ result.addChildrenToBack(ifTrue);
+
+ if (ifFalse != null) {
+ Node endTarget = Node.newTarget();
+ result.addChildToBack(makeJump(Token.GOTO, endTarget));
+ result.addChildToBack(ifNotTarget);
+ result.addChildrenToBack(ifFalse);
+ result.addChildToBack(endTarget);
+ } else {
+ result.addChildToBack(ifNotTarget);
+ }
+
+ return result;
+ }
+
+ Node createCondExpr(Node cond, Node ifTrue, Node ifFalse)
+ {
+ int condStatus = isAlwaysDefinedBoolean(cond);
+ if (condStatus == ALWAYS_TRUE_BOOLEAN) {
+ return ifTrue;
+ } else if (condStatus == ALWAYS_FALSE_BOOLEAN) {
+ return ifFalse;
+ }
+ return new Node(Token.HOOK, cond, ifTrue, ifFalse);
+ }
+
+ /**
+ * Unary
+ */
+ Node createUnary(int nodeType, Node child)
+ {
+ int childType = child.getType();
+ switch (nodeType) {
+ case Token.DELPROP: {
+ Node n;
+ if (childType == Token.NAME) {
+ // Transform Delete(Name "a")
+ // to Delete(Bind("a"), String("a"))
+ child.setType(Token.BINDNAME);
+ Node left = child;
+ Node right = Node.newString(child.getString());
+ n = new Node(nodeType, left, right);
+ } else if (childType == Token.GETPROP ||
+ childType == Token.GETELEM)
+ {
+ Node left = child.getFirstChild();
+ Node right = child.getLastChild();
+ child.removeChild(left);
+ child.removeChild(right);
+ n = new Node(nodeType, left, right);
+ } else if (childType == Token.GET_REF) {
+ Node ref = child.getFirstChild();
+ child.removeChild(ref);
+ n = new Node(Token.DEL_REF, ref);
+ } else {
+ n = new Node(Token.TRUE);
+ }
+ return n;
+ }
+ case Token.TYPEOF:
+ if (childType == Token.NAME) {
+ child.setType(Token.TYPEOFNAME);
+ return child;
+ }
+ break;
+ case Token.BITNOT:
+ if (childType == Token.NUMBER) {
+ int value = ScriptRuntime.toInt32(child.getDouble());
+ child.setDouble(~value);
+ return child;
+ }
+ break;
+ case Token.NEG:
+ if (childType == Token.NUMBER) {
+ child.setDouble(-child.getDouble());
+ return child;
+ }
+ break;
+ case Token.NOT: {
+ int status = isAlwaysDefinedBoolean(child);
+ if (status != 0) {
+ int type;
+ if (status == ALWAYS_TRUE_BOOLEAN) {
+ type = Token.FALSE;
+ } else {
+ type = Token.TRUE;
+ }
+ if (childType == Token.TRUE || childType == Token.FALSE) {
+ child.setType(type);
+ return child;
+ }
+ return new Node(type);
+ }
+ break;
+ }
+ }
+ return new Node(nodeType, child);
+ }
+
+ Node createYield(Node child, int lineno)
+ {
+ if (!parser.insideFunction()) {
+ parser.reportError("msg.bad.yield");
+ }
+ setRequiresActivation();
+ setIsGenerator();
+ if (child != null)
+ return new Node(Token.YIELD, child, lineno);
+ else
+ return new Node(Token.YIELD, lineno);
+ }
+
+ Node createCallOrNew(int nodeType, Node child)
+ {
+ int type = Node.NON_SPECIALCALL;
+ if (child.getType() == Token.NAME) {
+ String name = child.getString();
+ if (name.equals("eval")) {
+ type = Node.SPECIALCALL_EVAL;
+ } else if (name.equals("With")) {
+ type = Node.SPECIALCALL_WITH;
+ }
+ } else if (child.getType() == Token.GETPROP) {
+ String name = child.getLastChild().getString();
+ if (name.equals("eval")) {
+ type = Node.SPECIALCALL_EVAL;
+ }
+ }
+ Node node = new Node(nodeType, child);
+ if (type != Node.NON_SPECIALCALL) {
+ // Calls to these functions require activation objects.
+ setRequiresActivation();
+ node.putIntProp(Node.SPECIALCALL_PROP, type);
+ }
+ return node;
+ }
+
+ Node createIncDec(int nodeType, boolean post, Node child)
+ {
+ child = makeReference(child);
+ if (child == null) {
+ String msg;
+ if (nodeType == Token.DEC) {
+ msg = "msg.bad.decr";
+ } else {
+ msg = "msg.bad.incr";
+ }
+ parser.reportError(msg);
+ return null;
+ }
+
+ int childType = child.getType();
+
+ switch (childType) {
+ case Token.NAME:
+ case Token.GETPROP:
+ case Token.GETELEM:
+ case Token.GET_REF: {
+ Node n = new Node(nodeType, child);
+ int incrDecrMask = 0;
+ if (nodeType == Token.DEC) {
+ incrDecrMask |= Node.DECR_FLAG;
+ }
+ if (post) {
+ incrDecrMask |= Node.POST_FLAG;
+ }
+ n.putIntProp(Node.INCRDECR_PROP, incrDecrMask);
+ return n;
+ }
+ }
+ throw Kit.codeBug();
+ }
+
+ Node createPropertyGet(Node target, String namespace, String name,
+ int memberTypeFlags)
+ {
+ if (namespace == null && memberTypeFlags == 0) {
+ if (target == null) {
+ return createName(name);
+ }
+ checkActivationName(name, Token.GETPROP);
+ if (ScriptRuntime.isSpecialProperty(name)) {
+ Node ref = new Node(Token.REF_SPECIAL, target);
+ ref.putProp(Node.NAME_PROP, name);
+ return new Node(Token.GET_REF, ref);
+ }
+ return new Node(Token.GETPROP, target, createString(name));
+ }
+ Node elem = createString(name);
+ memberTypeFlags |= Node.PROPERTY_FLAG;
+ return createMemberRefGet(target, namespace, elem, memberTypeFlags);
+ }
+
+ Node createElementGet(Node target, String namespace, Node elem,
+ int memberTypeFlags)
+ {
+ // OPT: could optimize to createPropertyGet
+ // iff elem is string that can not be number
+ if (namespace == null && memberTypeFlags == 0) {
+ // stand-alone [aaa] as primary expression is array literal
+ // declaration and should not come here!
+ if (target == null) throw Kit.codeBug();
+ return new Node(Token.GETELEM, target, elem);
+ }
+ return createMemberRefGet(target, namespace, elem, memberTypeFlags);
+ }
+
+ private Node createMemberRefGet(Node target, String namespace, Node elem,
+ int memberTypeFlags)
+ {
+ Node nsNode = null;
+ if (namespace != null) {
+ // See 11.1.2 in ECMA 357
+ if (namespace.equals("*")) {
+ nsNode = new Node(Token.NULL);
+ } else {
+ nsNode = createName(namespace);
+ }
+ }
+ Node ref;
+ if (target == null) {
+ if (namespace == null) {
+ ref = new Node(Token.REF_NAME, elem);
+ } else {
+ ref = new Node(Token.REF_NS_NAME, nsNode, elem);
+ }
+ } else {
+ if (namespace == null) {
+ ref = new Node(Token.REF_MEMBER, target, elem);
+ } else {
+ ref = new Node(Token.REF_NS_MEMBER, target, nsNode, elem);
+ }
+ }
+ if (memberTypeFlags != 0) {
+ ref.putIntProp(Node.MEMBER_TYPE_PROP, memberTypeFlags);
+ }
+ return new Node(Token.GET_REF, ref);
+ }
+
+ /**
+ * Binary
+ */
+ Node createBinary(int nodeType, Node left, Node right)
+ {
+ switch (nodeType) {
+
+ case Token.ADD:
+ // numerical addition and string concatenation
+ if (left.type == Token.STRING) {
+ String s2;
+ if (right.type == Token.STRING) {
+ s2 = right.getString();
+ } else if (right.type == Token.NUMBER) {
+ s2 = ScriptRuntime.numberToString(right.getDouble(), 10);
+ } else {
+ break;
+ }
+ String s1 = left.getString();
+ left.setString(s1.concat(s2));
+ return left;
+ } else if (left.type == Token.NUMBER) {
+ if (right.type == Token.NUMBER) {
+ left.setDouble(left.getDouble() + right.getDouble());
+ return left;
+ } else if (right.type == Token.STRING) {
+ String s1, s2;
+ s1 = ScriptRuntime.numberToString(left.getDouble(), 10);
+ s2 = right.getString();
+ right.setString(s1.concat(s2));
+ return right;
+ }
+ }
+ // can't do anything if we don't know both types - since
+ // 0 + object is supposed to call toString on the object and do
+ // string concantenation rather than addition
+ break;
+
+ case Token.SUB:
+ // numerical subtraction
+ if (left.type == Token.NUMBER) {
+ double ld = left.getDouble();
+ if (right.type == Token.NUMBER) {
+ //both numbers
+ left.setDouble(ld - right.getDouble());
+ return left;
+ } else if (ld == 0.0) {
+ // first 0: 0-x -> -x
+ return new Node(Token.NEG, right);
+ }
+ } else if (right.type == Token.NUMBER) {
+ if (right.getDouble() == 0.0) {
+ //second 0: x - 0 -> +x
+ // can not make simply x because x - 0 must be number
+ return new Node(Token.POS, left);
+ }
+ }
+ break;
+
+ case Token.MUL:
+ // numerical multiplication
+ if (left.type == Token.NUMBER) {
+ double ld = left.getDouble();
+ if (right.type == Token.NUMBER) {
+ //both numbers
+ left.setDouble(ld * right.getDouble());
+ return left;
+ } else if (ld == 1.0) {
+ // first 1: 1 * x -> +x
+ return new Node(Token.POS, right);
+ }
+ } else if (right.type == Token.NUMBER) {
+ if (right.getDouble() == 1.0) {
+ //second 1: x * 1 -> +x
+ // can not make simply x because x - 0 must be number
+ return new Node(Token.POS, left);
+ }
+ }
+ // can't do x*0: Infinity * 0 gives NaN, not 0
+ break;
+
+ case Token.DIV:
+ // number division
+ if (right.type == Token.NUMBER) {
+ double rd = right.getDouble();
+ if (left.type == Token.NUMBER) {
+ // both constants -- just divide, trust Java to handle x/0
+ left.setDouble(left.getDouble() / rd);
+ return left;
+ } else if (rd == 1.0) {
+ // second 1: x/1 -> +x
+ // not simply x to force number convertion
+ return new Node(Token.POS, left);
+ }
+ }
+ break;
+
+ case Token.AND: {
+ // Since x && y gives x, not false, when Boolean(x) is false,
+ // and y, not Boolean(y), when Boolean(x) is true, x && y
+ // can only be simplified if x is defined. See bug 309957.
+
+ int leftStatus = isAlwaysDefinedBoolean(left);
+ if (leftStatus == ALWAYS_FALSE_BOOLEAN) {
+ // if the first one is false, just return it
+ return left;
+ } else if (leftStatus == ALWAYS_TRUE_BOOLEAN) {
+ // if first is true, set to second
+ return right;
+ }
+ break;
+ }
+
+ case Token.OR: {
+ // Since x || y gives x, not true, when Boolean(x) is true,
+ // and y, not Boolean(y), when Boolean(x) is false, x || y
+ // can only be simplified if x is defined. See bug 309957.
+
+ int leftStatus = isAlwaysDefinedBoolean(left);
+ if (leftStatus == ALWAYS_TRUE_BOOLEAN) {
+ // if the first one is true, just return it
+ return left;
+ } else if (leftStatus == ALWAYS_FALSE_BOOLEAN) {
+ // if first is false, set to second
+ return right;
+ }
+ break;
+ }
+ }
+
+ return new Node(nodeType, left, right);
+ }
+
+ private Node simpleAssignment(Node left, Node right)
+ {
+ int nodeType = left.getType();
+ switch (nodeType) {
+ case Token.NAME:
+ left.setType(Token.BINDNAME);
+ return new Node(Token.SETNAME, left, right);
+
+ case Token.GETPROP:
+ case Token.GETELEM: {
+ Node obj = left.getFirstChild();
+ Node id = left.getLastChild();
+ int type;
+ if (nodeType == Token.GETPROP) {
+ type = Token.SETPROP;
+ } else {
+ type = Token.SETELEM;
+ }
+ return new Node(type, obj, id, right);
+ }
+ case Token.GET_REF: {
+ Node ref = left.getFirstChild();
+ checkMutableReference(ref);
+ return new Node(Token.SET_REF, ref, right);
+ }
+ }
+
+ throw Kit.codeBug();
+ }
+
+ private void checkMutableReference(Node n)
+ {
+ int memberTypeFlags = n.getIntProp(Node.MEMBER_TYPE_PROP, 0);
+ if ((memberTypeFlags & Node.DESCENDANTS_FLAG) != 0) {
+ parser.reportError("msg.bad.assign.left");
+ }
+ }
+
+ Node createAssignment(int assignType, Node left, Node right)
+ {
+ Node ref = makeReference(left);
+ if (ref == null) {
+ if (left.getType() == Token.ARRAYLIT ||
+ left.getType() == Token.OBJECTLIT)
+ {
+ if (assignType != Token.ASSIGN) {
+ parser.reportError("msg.bad.destruct.op");
+ return right;
+ }
+ return createDestructuringAssignment(-1, left, right);
+ }
+ parser.reportError("msg.bad.assign.left");
+ return right;
+ }
+ left = ref;
+
+ int assignOp;
+ switch (assignType) {
+ case Token.ASSIGN:
+ return simpleAssignment(left, right);
+ case Token.ASSIGN_BITOR: assignOp = Token.BITOR; break;
+ case Token.ASSIGN_BITXOR: assignOp = Token.BITXOR; break;
+ case Token.ASSIGN_BITAND: assignOp = Token.BITAND; break;
+ case Token.ASSIGN_LSH: assignOp = Token.LSH; break;
+ case Token.ASSIGN_RSH: assignOp = Token.RSH; break;
+ case Token.ASSIGN_URSH: assignOp = Token.URSH; break;
+ case Token.ASSIGN_ADD: assignOp = Token.ADD; break;
+ case Token.ASSIGN_SUB: assignOp = Token.SUB; break;
+ case Token.ASSIGN_MUL: assignOp = Token.MUL; break;
+ case Token.ASSIGN_DIV: assignOp = Token.DIV; break;
+ case Token.ASSIGN_MOD: assignOp = Token.MOD; break;
+ default: throw Kit.codeBug();
+ }
+
+ int nodeType = left.getType();
+ switch (nodeType) {
+ case Token.NAME: {
+ Node op = new Node(assignOp, left, right);
+ Node lvalueLeft = Node.newString(Token.BINDNAME, left.getString());
+ return new Node(Token.SETNAME, lvalueLeft, op);
+ }
+ case Token.GETPROP:
+ case Token.GETELEM: {
+ Node obj = left.getFirstChild();
+ Node id = left.getLastChild();
+
+ int type = nodeType == Token.GETPROP
+ ? Token.SETPROP_OP
+ : Token.SETELEM_OP;
+
+ Node opLeft = new Node(Token.USE_STACK);
+ Node op = new Node(assignOp, opLeft, right);
+ return new Node(type, obj, id, op);
+ }
+ case Token.GET_REF: {
+ ref = left.getFirstChild();
+ checkMutableReference(ref);
+ Node opLeft = new Node(Token.USE_STACK);
+ Node op = new Node(assignOp, opLeft, right);
+ return new Node(Token.SET_REF_OP, ref, op);
+ }
+ }
+
+ throw Kit.codeBug();
+ }
+
+ /**
+ * Given a destructuring assignment with a left hand side parsed
+ * as an array or object literal and a right hand side expression,
+ * rewrite as a series of assignments to the variables defined in
+ * left from property accesses to the expression on the right.
+ * @param type declaration type: Token.VAR or Token.LET or -1
+ * @param left array or object literal containing NAME nodes for
+ * variables to assign
+ * @param right expression to assign from
+ * @return expression that performs a series of assignments to
+ * the variables defined in left
+ */
+ Node createDestructuringAssignment(int type, Node left, Node right)
+ {
+ String tempName = parser.currentScriptOrFn.getNextTempName();
+ Node result = destructuringAssignmentHelper(type, left, right,
+ tempName);
+ Node comma = result.getLastChild();
+ comma.addChildToBack(createName(tempName));
+ return result;
+ }
+
+ private Node destructuringAssignmentHelper(int variableType, Node left,
+ Node right, String tempName)
+ {
+ Node result = createScopeNode(Token.LETEXPR,
+ parser.getCurrentLineNumber());
+ result.addChildToFront(new Node(Token.LET,
+ createName(Token.NAME, tempName, right)));
+ try {
+ parser.pushScope(result);
+ parser.defineSymbol(Token.LET, true, tempName);
+ } finally {
+ parser.popScope();
+ }
+ Node comma = new Node(Token.COMMA);
+ result.addChildToBack(comma);
+ final int setOp = variableType == Token.CONST ? Token.SETCONST
+ : Token.SETNAME;
+ List<String> destructuringNames = new ArrayList<String>();
+ boolean empty = true;
+ int type = left.getType();
+ if (type == Token.ARRAYLIT) {
+ int index = 0;
+ int[] skipIndices = (int[])left.getProp(Node.SKIP_INDEXES_PROP);
+ int skip = 0;
+ Node n = left.getFirstChild();
+ for (;;) {
+ if (skipIndices != null) {
+ while (skip < skipIndices.length &&
+ skipIndices[skip] == index) {
+ skip++;
+ index++;
+ }
+ }
+ if (n == null)
+ break;
+ Node rightElem = new Node(Token.GETELEM,
+ createName(tempName),
+ createNumber(index));
+ if (n.getType() == Token.NAME) {
+ String name = n.getString();
+ comma.addChildToBack(new Node(setOp,
+ createName(Token.BINDNAME, name, null),
+ rightElem));
+ if (variableType != -1) {
+ parser.defineSymbol(variableType, true, name);
+ destructuringNames.add(name);
+ }
+ } else {
+ comma.addChildToBack(
+ destructuringAssignmentHelper(variableType, n,
+ rightElem,
+ parser.currentScriptOrFn.getNextTempName()));
+ }
+ index++;
+ empty = false;
+ n = n.getNext();
+ }
+ } else if (type == Token.OBJECTLIT) {
+ int index = 0;
+ Object[] propertyIds = (Object[])
+ left.getProp(Node.OBJECT_IDS_PROP);
+ for (Node n = left.getFirstChild(); n != null; n = n.getNext())
+ {
+ Object id = propertyIds[index];
+ Node rightElem = id instanceof String
+ ? new Node(Token.GETPROP,
+ createName(tempName),
+ createString((String)id))
+ : new Node(Token.GETELEM,
+ createName(tempName),
+ createNumber(((Number)id).intValue()));
+ if (n.getType() == Token.NAME) {
+ String name = n.getString();
+ comma.addChildToBack(new Node(setOp,
+ createName(Token.BINDNAME, name, null),
+ rightElem));
+ if (variableType != -1) {
+ parser.defineSymbol(variableType, true, name);
+ destructuringNames.add(name);
+ }
+ } else {
+ comma.addChildToBack(
+ destructuringAssignmentHelper(variableType, n,
+ rightElem,
+ parser.currentScriptOrFn.getNextTempName()));
+ }
+ index++;
+ empty = false;
+ }
+ } else if (type == Token.GETPROP || type == Token.GETELEM) {
+ comma.addChildToBack(simpleAssignment(left, createName(tempName)));
+ } else {
+ parser.reportError("msg.bad.assign.left");
+ }
+ if (empty) {
+ // Don't want a COMMA node with no children. Just add a zero.
+ comma.addChildToBack(createNumber(0));
+ }
+ result.putProp(Node.DESTRUCTURING_NAMES, destructuringNames);
+ return result;
+ }
+
+ Node createUseLocal(Node localBlock)
+ {
+ if (Token.LOCAL_BLOCK != localBlock.getType()) throw Kit.codeBug();
+ Node result = new Node(Token.LOCAL_LOAD);
+ result.putProp(Node.LOCAL_BLOCK_PROP, localBlock);
+ return result;
+ }
+
+ private Node.Jump makeJump(int type, Node target)
+ {
+ Node.Jump n = new Node.Jump(type);
+ n.target = target;
+ return n;
+ }
+
+ private Node makeReference(Node node)
+ {
+ int type = node.getType();
+ switch (type) {
+ case Token.NAME:
+ case Token.GETPROP:
+ case Token.GETELEM:
+ case Token.GET_REF:
+ return node;
+ case Token.CALL:
+ node.setType(Token.REF_CALL);
+ return new Node(Token.GET_REF, node);
+ }
+ // Signal caller to report error
+ return null;
+ }
+
+ // Check if Node always mean true or false in boolean context
+ private static int isAlwaysDefinedBoolean(Node node)
+ {
+ switch (node.getType()) {
+ case Token.FALSE:
+ case Token.NULL:
+ return ALWAYS_FALSE_BOOLEAN;
+ case Token.TRUE:
+ return ALWAYS_TRUE_BOOLEAN;
+ case Token.NUMBER: {
+ double num = node.getDouble();
+ if (num == num && num != 0.0) {
+ return ALWAYS_TRUE_BOOLEAN;
+ } else {
+ return ALWAYS_FALSE_BOOLEAN;
+ }
+ }
+ }
+ return 0;
+ }
+
+ private void checkActivationName(String name, int token)
+ {
+ if (parser.insideFunction()) {
+ boolean activation = false;
+ if ("arguments".equals(name)
+ || (parser.compilerEnv.activationNames != null
+ && parser.compilerEnv.activationNames.contains(name)))
+ {
+ activation = true;
+ } else if ("length".equals(name)) {
+ if (token == Token.GETPROP
+ && parser.compilerEnv.getLanguageVersion()
+ == Context.VERSION_1_2)
+ {
+ // Use of "length" in 1.2 requires an activation object.
+ activation = true;
+ }
+ }
+ if (activation) {
+ setRequiresActivation();
+ }
+ }
+ }
+
+ private void setRequiresActivation()
+ {
+ if (parser.insideFunction()) {
+ ((FunctionNode)parser.currentScriptOrFn).itsNeedsActivation = true;
+ }
+ }
+
+ private void setIsGenerator()
+ {
+ if (parser.insideFunction()) {
+ ((FunctionNode)parser.currentScriptOrFn).itsIsGenerator = true;
+ }
+ }
+
+ private Parser parser;
+
+ private static final int LOOP_DO_WHILE = 0;
+ private static final int LOOP_WHILE = 1;
+ private static final int LOOP_FOR = 2;
+
+ private static final int ALWAYS_TRUE_BOOLEAN = 1;
+ private static final int ALWAYS_FALSE_BOOLEAN = -1;
+}
diff --git a/src/org/mozilla/javascript/IdFunctionCall.java b/src/org/mozilla/javascript/IdFunctionCall.java
new file mode 100644
index 0000000..713fabf
--- /dev/null
+++ b/src/org/mozilla/javascript/IdFunctionCall.java
@@ -0,0 +1,55 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * Master for id-based functions that knows their properties and how to
+ * execute them.
+ */
+public interface IdFunctionCall
+{
+ /**
+ * 'thisObj' will be null if invoked as constructor, in which case
+ * instance of Scriptable should be returned
+ */
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args);
+
+}
+
diff --git a/src/org/mozilla/javascript/IdFunctionObject.java b/src/org/mozilla/javascript/IdFunctionObject.java
new file mode 100644
index 0000000..32d03c9
--- /dev/null
+++ b/src/org/mozilla/javascript/IdFunctionObject.java
@@ -0,0 +1,196 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+public class IdFunctionObject extends BaseFunction
+{
+
+ static final long serialVersionUID = -5332312783643935019L;
+
+ public IdFunctionObject(IdFunctionCall idcall, Object tag, int id, int arity)
+ {
+ if (arity < 0)
+ throw new IllegalArgumentException();
+
+ this.idcall = idcall;
+ this.tag = tag;
+ this.methodId = id;
+ this.arity = arity;
+ if (arity < 0) throw new IllegalArgumentException();
+ }
+
+ public IdFunctionObject(IdFunctionCall idcall, Object tag, int id,
+ String name, int arity, Scriptable scope)
+ {
+ super(scope, null);
+
+ if (arity < 0)
+ throw new IllegalArgumentException();
+ if (name == null)
+ throw new IllegalArgumentException();
+
+ this.idcall = idcall;
+ this.tag = tag;
+ this.methodId = id;
+ this.arity = arity;
+ this.functionName = name;
+ }
+
+ public void initFunction(String name, Scriptable scope)
+ {
+ if (name == null) throw new IllegalArgumentException();
+ if (scope == null) throw new IllegalArgumentException();
+ this.functionName = name;
+ setParentScope(scope);
+ }
+
+ public final boolean hasTag(Object tag)
+ {
+ return this.tag == tag;
+ }
+
+ public final int methodId()
+ {
+ return methodId;
+ }
+
+ public final void markAsConstructor(Scriptable prototypeProperty)
+ {
+ useCallAsConstructor = true;
+ setImmunePrototypeProperty(prototypeProperty);
+ }
+
+ public final void addAsProperty(Scriptable target)
+ {
+ ScriptableObject.defineProperty(target, functionName, this,
+ ScriptableObject.DONTENUM);
+ }
+
+ public void exportAsScopeProperty()
+ {
+ addAsProperty(getParentScope());
+ }
+
+ @Override
+ public Scriptable getPrototype()
+ {
+ // Lazy initialization of prototype: for native functions this
+ // may not be called at all
+ Scriptable proto = super.getPrototype();
+ if (proto == null) {
+ proto = getFunctionPrototype(getParentScope());
+ setPrototype(proto);
+ }
+ return proto;
+ }
+
+ @Override
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ return idcall.execIdCall(this, cx, scope, thisObj, args);
+ }
+
+ @Override
+ public Scriptable createObject(Context cx, Scriptable scope)
+ {
+ if (useCallAsConstructor) {
+ return null;
+ }
+ // Throw error if not explicitly coded to be used as constructor,
+ // to satisfy ECMAScript standard (see bugzilla 202019).
+ // To follow current (2003-05-01) SpiderMonkey behavior, change it to:
+ // return super.createObject(cx, scope);
+ throw ScriptRuntime.typeError1("msg.not.ctor", functionName);
+ }
+
+ @Override
+ String decompile(int indent, int flags)
+ {
+ StringBuffer sb = new StringBuffer();
+ boolean justbody = (0 != (flags & Decompiler.ONLY_BODY_FLAG));
+ if (!justbody) {
+ sb.append("function ");
+ sb.append(getFunctionName());
+ sb.append("() { ");
+ }
+ sb.append("[native code for ");
+ if (idcall instanceof Scriptable) {
+ Scriptable sobj = (Scriptable)idcall;
+ sb.append(sobj.getClassName());
+ sb.append('.');
+ }
+ sb.append(getFunctionName());
+ sb.append(", arity=");
+ sb.append(getArity());
+ sb.append(justbody ? "]\n" : "] }\n");
+ return sb.toString();
+ }
+
+ @Override
+ public int getArity()
+ {
+ return arity;
+ }
+
+ @Override
+ public int getLength() { return getArity(); }
+
+ @Override
+ public String getFunctionName()
+ {
+ return (functionName == null) ? "" : functionName;
+ }
+
+ public final RuntimeException unknown()
+ {
+ // It is program error to call id-like methods for unknown function
+ return new IllegalArgumentException(
+ "BAD FUNCTION ID="+methodId+" MASTER="+idcall);
+ }
+
+ private final IdFunctionCall idcall;
+ private final Object tag;
+ private final int methodId;
+ private int arity;
+ private boolean useCallAsConstructor;
+ private String functionName;
+}
diff --git a/src/org/mozilla/javascript/IdScriptableObject.java b/src/org/mozilla/javascript/IdScriptableObject.java
new file mode 100644
index 0000000..44709da
--- /dev/null
+++ b/src/org/mozilla/javascript/IdScriptableObject.java
@@ -0,0 +1,741 @@
+/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.*;
+
+/**
+Base class for native object implementation that uses IdFunctionObject to export its methods to script via <class-name>.prototype object.
+
+Any descendant should implement at least the following methods:
+ findInstanceIdInfo
+ getInstanceIdName
+ execIdCall
+ methodArity
+
+To define non-function properties, the descendant should override
+ getInstanceIdValue
+ setInstanceIdValue
+to get/set property value and provide its default attributes.
+
+
+To customize initializition of constructor and protype objects, descendant
+may override scopeInit or fillConstructorProperties methods.
+
+*/
+public abstract class IdScriptableObject extends ScriptableObject
+ implements IdFunctionCall
+{
+ private transient volatile PrototypeValues prototypeValues;
+
+ private static final class PrototypeValues implements Serializable
+ {
+ static final long serialVersionUID = 3038645279153854371L;
+
+ private static final int VALUE_SLOT = 0;
+ private static final int NAME_SLOT = 1;
+ private static final int SLOT_SPAN = 2;
+
+ private IdScriptableObject obj;
+ private int maxId;
+ private volatile Object[] valueArray;
+ private volatile short[] attributeArray;
+ private volatile int lastFoundId = 1;
+
+ // The following helps to avoid creation of valueArray during runtime
+ // initialization for common case of "constructor" property
+ int constructorId;
+ private IdFunctionObject constructor;
+ private short constructorAttrs;
+
+ PrototypeValues(IdScriptableObject obj, int maxId)
+ {
+ if (obj == null) throw new IllegalArgumentException();
+ if (maxId < 1) throw new IllegalArgumentException();
+ this.obj = obj;
+ this.maxId = maxId;
+ }
+
+ final int getMaxId()
+ {
+ return maxId;
+ }
+
+ final void initValue(int id, String name, Object value, int attributes)
+ {
+ if (!(1 <= id && id <= maxId))
+ throw new IllegalArgumentException();
+ if (name == null)
+ throw new IllegalArgumentException();
+ if (value == NOT_FOUND)
+ throw new IllegalArgumentException();
+ ScriptableObject.checkValidAttributes(attributes);
+ if (obj.findPrototypeId(name) != id)
+ throw new IllegalArgumentException(name);
+
+ if (id == constructorId) {
+ if (!(value instanceof IdFunctionObject)) {
+ throw new IllegalArgumentException("consructor should be initialized with IdFunctionObject");
+ }
+ constructor = (IdFunctionObject)value;
+ constructorAttrs = (short)attributes;
+ return;
+ }
+
+ initSlot(id, name, value, attributes);
+ }
+
+ private void initSlot(int id, String name, Object value,
+ int attributes)
+ {
+ Object[] array = valueArray;
+ if (array == null)
+ throw new IllegalStateException();
+
+ if (value == null) {
+ value = UniqueTag.NULL_VALUE;
+ }
+ int index = (id - 1) * SLOT_SPAN;
+ synchronized (this) {
+ Object value2 = array[index + VALUE_SLOT];
+ if (value2 == null) {
+ array[index + VALUE_SLOT] = value;
+ array[index + NAME_SLOT] = name;
+ attributeArray[id - 1] = (short)attributes;
+ } else {
+ if (!name.equals(array[index + NAME_SLOT]))
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ final IdFunctionObject createPrecachedConstructor()
+ {
+ if (constructorId != 0) throw new IllegalStateException();
+ constructorId = obj.findPrototypeId("constructor");
+ if (constructorId == 0) {
+ throw new IllegalStateException(
+ "No id for constructor property");
+ }
+ obj.initPrototypeId(constructorId);
+ if (constructor == null) {
+ throw new IllegalStateException(
+ obj.getClass().getName()+".initPrototypeId() did not "
+ +"initialize id="+constructorId);
+ }
+ constructor.initFunction(obj.getClassName(),
+ ScriptableObject.getTopLevelScope(obj));
+ constructor.markAsConstructor(obj);
+ return constructor;
+ }
+
+ final int findId(String name)
+ {
+ Object[] array = valueArray;
+ if (array == null) {
+ return obj.findPrototypeId(name);
+ }
+ int id = lastFoundId;
+ if (name == array[(id - 1) * SLOT_SPAN + NAME_SLOT]) {
+ return id;
+ }
+ id = obj.findPrototypeId(name);
+ if (id != 0) {
+ int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
+ // Make cache to work!
+ array[nameSlot] = name;
+ lastFoundId = id;
+ }
+ return id;
+ }
+
+ final boolean has(int id)
+ {
+ Object[] array = valueArray;
+ if (array == null) {
+ // Not yet initialized, assume all exists
+ return true;
+ }
+ int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
+ Object value = array[valueSlot];
+ if (value == null) {
+ // The particular entry has not been yet initialized
+ return true;
+ }
+ return value != NOT_FOUND;
+ }
+
+ final Object get(int id)
+ {
+ Object value = ensureId(id);
+ if (value == UniqueTag.NULL_VALUE) {
+ value = null;
+ }
+ return value;
+ }
+
+ final void set(int id, Scriptable start, Object value)
+ {
+ if (value == NOT_FOUND) throw new IllegalArgumentException();
+ ensureId(id);
+ int attr = attributeArray[id - 1];
+ if ((attr & READONLY) == 0) {
+ if (start == obj) {
+ if (value == null) {
+ value = UniqueTag.NULL_VALUE;
+ }
+ int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
+ synchronized (this) {
+ valueArray[valueSlot] = value;
+ }
+ }
+ else {
+ int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
+ String name = (String)valueArray[nameSlot];
+ start.put(name, start, value);
+ }
+ }
+ }
+
+ final void delete(int id)
+ {
+ ensureId(id);
+ int attr = attributeArray[id - 1];
+ if ((attr & PERMANENT) == 0) {
+ int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
+ synchronized (this) {
+ valueArray[valueSlot] = NOT_FOUND;
+ attributeArray[id - 1] = EMPTY;
+ }
+ }
+ }
+
+ final int getAttributes(int id)
+ {
+ ensureId(id);
+ return attributeArray[id - 1];
+ }
+
+ final void setAttributes(int id, int attributes)
+ {
+ ScriptableObject.checkValidAttributes(attributes);
+ ensureId(id);
+ synchronized (this) {
+ attributeArray[id - 1] = (short)attributes;
+ }
+ }
+
+ final Object[] getNames(boolean getAll, Object[] extraEntries)
+ {
+ Object[] names = null;
+ int count = 0;
+ for (int id = 1; id <= maxId; ++id) {
+ Object value = ensureId(id);
+ if (getAll || (attributeArray[id - 1] & DONTENUM) == 0) {
+ if (value != NOT_FOUND) {
+ int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
+ String name = (String)valueArray[nameSlot];
+ if (names == null) {
+ names = new Object[maxId];
+ }
+ names[count++] = name;
+ }
+ }
+ }
+ if (count == 0) {
+ return extraEntries;
+ } else if (extraEntries == null || extraEntries.length == 0) {
+ if (count != names.length) {
+ Object[] tmp = new Object[count];
+ System.arraycopy(names, 0, tmp, 0, count);
+ names = tmp;
+ }
+ return names;
+ } else {
+ int extra = extraEntries.length;
+ Object[] tmp = new Object[extra + count];
+ System.arraycopy(extraEntries, 0, tmp, 0, extra);
+ System.arraycopy(names, 0, tmp, extra, count);
+ return tmp;
+ }
+ }
+
+ private Object ensureId(int id)
+ {
+ Object[] array = valueArray;
+ if (array == null) {
+ synchronized (this) {
+ array = valueArray;
+ if (array == null) {
+ array = new Object[maxId * SLOT_SPAN];
+ valueArray = array;
+ attributeArray = new short[maxId];
+ }
+ }
+ }
+ int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
+ Object value = array[valueSlot];
+ if (value == null) {
+ if (id == constructorId) {
+ initSlot(constructorId, "constructor",
+ constructor, constructorAttrs);
+ constructor = null; // no need to refer it any longer
+ } else {
+ obj.initPrototypeId(id);
+ }
+ value = array[valueSlot];
+ if (value == null) {
+ throw new IllegalStateException(
+ obj.getClass().getName()+".initPrototypeId(int id) "
+ +"did not initialize id="+id);
+ }
+ }
+ return value;
+ }
+ }
+
+ public IdScriptableObject()
+ {
+ }
+
+ public IdScriptableObject(Scriptable scope, Scriptable prototype)
+ {
+ super(scope, prototype);
+ }
+
+ protected final Object defaultGet(String name)
+ {
+ return super.get(name, this);
+ }
+
+ protected final void defaultPut(String name, Object value)
+ {
+ super.put(name, this, value);
+ }
+
+ @Override
+ public boolean has(String name, Scriptable start)
+ {
+ int info = findInstanceIdInfo(name);
+ if (info != 0) {
+ int attr = (info >>> 16);
+ if ((attr & PERMANENT) != 0) {
+ return true;
+ }
+ int id = (info & 0xFFFF);
+ return NOT_FOUND != getInstanceIdValue(id);
+ }
+ if (prototypeValues != null) {
+ int id = prototypeValues.findId(name);
+ if (id != 0) {
+ return prototypeValues.has(id);
+ }
+ }
+ return super.has(name, start);
+ }
+
+ @Override
+ public Object get(String name, Scriptable start)
+ {
+ int info = findInstanceIdInfo(name);
+ if (info != 0) {
+ int id = (info & 0xFFFF);
+ return getInstanceIdValue(id);
+ }
+ if (prototypeValues != null) {
+ int id = prototypeValues.findId(name);
+ if (id != 0) {
+ return prototypeValues.get(id);
+ }
+ }
+ return super.get(name, start);
+ }
+
+ @Override
+ public void put(String name, Scriptable start, Object value)
+ {
+ int info = findInstanceIdInfo(name);
+ if (info != 0) {
+ if (start == this && isSealed()) {
+ throw Context.reportRuntimeError1("msg.modify.sealed",
+ name);
+ }
+ int attr = (info >>> 16);
+ if ((attr & READONLY) == 0) {
+ if (start == this) {
+ int id = (info & 0xFFFF);
+ setInstanceIdValue(id, value);
+ }
+ else {
+ start.put(name, start, value);
+ }
+ }
+ return;
+ }
+ if (prototypeValues != null) {
+ int id = prototypeValues.findId(name);
+ if (id != 0) {
+ if (start == this && isSealed()) {
+ throw Context.reportRuntimeError1("msg.modify.sealed",
+ name);
+ }
+ prototypeValues.set(id, start, value);
+ return;
+ }
+ }
+ super.put(name, start, value);
+ }
+
+ @Override
+ public void delete(String name)
+ {
+ int info = findInstanceIdInfo(name);
+ if (info != 0) {
+ // Let the super class to throw exceptions for sealed objects
+ if (!isSealed()) {
+ int attr = (info >>> 16);
+ if ((attr & PERMANENT) == 0) {
+ int id = (info & 0xFFFF);
+ setInstanceIdValue(id, NOT_FOUND);
+ }
+ return;
+ }
+ }
+ if (prototypeValues != null) {
+ int id = prototypeValues.findId(name);
+ if (id != 0) {
+ if (!isSealed()) {
+ prototypeValues.delete(id);
+ }
+ return;
+ }
+ }
+ super.delete(name);
+ }
+
+ @Override
+ public int getAttributes(String name)
+ {
+ int info = findInstanceIdInfo(name);
+ if (info != 0) {
+ int attr = (info >>> 16);
+ return attr;
+ }
+ if (prototypeValues != null) {
+ int id = prototypeValues.findId(name);
+ if (id != 0) {
+ return prototypeValues.getAttributes(id);
+ }
+ }
+ return super.getAttributes(name);
+ }
+
+ @Override
+ public void setAttributes(String name, int attributes)
+ {
+ ScriptableObject.checkValidAttributes(attributes);
+ int info = findInstanceIdInfo(name);
+ if (info != 0) {
+ int currentAttributes = (info >>> 16);
+ if (attributes != currentAttributes) {
+ throw new RuntimeException(
+ "Change of attributes for this id is not supported");
+ }
+ return;
+ }
+ if (prototypeValues != null) {
+ int id = prototypeValues.findId(name);
+ if (id != 0) {
+ prototypeValues.setAttributes(id, attributes);
+ return;
+ }
+ }
+ super.setAttributes(name, attributes);
+ }
+
+ @Override
+ Object[] getIds(boolean getAll)
+ {
+ Object[] result = super.getIds(getAll);
+
+ if (prototypeValues != null) {
+ result = prototypeValues.getNames(getAll, result);
+ }
+
+ int maxInstanceId = getMaxInstanceId();
+ if (maxInstanceId != 0) {
+ Object[] ids = null;
+ int count = 0;
+
+ for (int id = maxInstanceId; id != 0; --id) {
+ String name = getInstanceIdName(id);
+ int info = findInstanceIdInfo(name);
+ if (info != 0) {
+ int attr = (info >>> 16);
+ if ((attr & PERMANENT) == 0) {
+ if (NOT_FOUND == getInstanceIdValue(id)) {
+ continue;
+ }
+ }
+ if (getAll || (attr & DONTENUM) == 0) {
+ if (count == 0) {
+ // Need extra room for no more then [1..id] names
+ ids = new Object[id];
+ }
+ ids[count++] = name;
+ }
+ }
+ }
+ if (count != 0) {
+ if (result.length == 0 && ids.length == count) {
+ result = ids;
+ }
+ else {
+ Object[] tmp = new Object[result.length + count];
+ System.arraycopy(result, 0, tmp, 0, result.length);
+ System.arraycopy(ids, 0, tmp, result.length, count);
+ result = tmp;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Get maximum id findInstanceIdInfo can generate.
+ */
+ protected int getMaxInstanceId()
+ {
+ return 0;
+ }
+
+ protected static int instanceIdInfo(int attributes, int id)
+ {
+ return (attributes << 16) | id;
+ }
+
+ /**
+ * Map name to id of instance property.
+ * Should return 0 if not found or the result of
+ * {@link #instanceIdInfo(int, int)}.
+ */
+ protected int findInstanceIdInfo(String name)
+ {
+ return 0;
+ }
+
+ /** Map id back to property name it defines.
+ */
+ protected String getInstanceIdName(int id)
+ {
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+
+ /** Get id value.
+ ** If id value is constant, descendant can call cacheIdValue to store
+ ** value in the permanent cache.
+ ** Default implementation creates IdFunctionObject instance for given id
+ ** and cache its value
+ */
+ protected Object getInstanceIdValue(int id)
+ {
+ throw new IllegalStateException(String.valueOf(id));
+ }
+
+ /**
+ * Set or delete id value. If value == NOT_FOUND , the implementation
+ * should make sure that the following getInstanceIdValue return NOT_FOUND.
+ */
+ protected void setInstanceIdValue(int id, Object value)
+ {
+ throw new IllegalStateException(String.valueOf(id));
+ }
+
+ /** 'thisObj' will be null if invoked as constructor, in which case
+ ** instance of Scriptable should be returned. */
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ throw f.unknown();
+ }
+
+ public final IdFunctionObject exportAsJSClass(int maxPrototypeId,
+ Scriptable scope,
+ boolean sealed)
+ {
+ // Set scope and prototype unless this is top level scope itself
+ if (scope != this && scope != null) {
+ setParentScope(scope);
+ setPrototype(getObjectPrototype(scope));
+ }
+
+ activatePrototypeMap(maxPrototypeId);
+ IdFunctionObject ctor = prototypeValues.createPrecachedConstructor();
+ if (sealed) {
+ sealObject();
+ }
+ fillConstructorProperties(ctor);
+ if (sealed) {
+ ctor.sealObject();
+ }
+ ctor.exportAsScopeProperty();
+ return ctor;
+ }
+
+ public final boolean hasPrototypeMap()
+ {
+ return prototypeValues != null;
+ }
+
+ public final void activatePrototypeMap(int maxPrototypeId)
+ {
+ PrototypeValues values = new PrototypeValues(this, maxPrototypeId);
+ synchronized (this) {
+ if (prototypeValues != null)
+ throw new IllegalStateException();
+ prototypeValues = values;
+ }
+ }
+
+ public final void initPrototypeMethod(Object tag, int id, String name,
+ int arity)
+ {
+ Scriptable scope = ScriptableObject.getTopLevelScope(this);
+ IdFunctionObject f = newIdFunction(tag, id, name, arity, scope);
+ prototypeValues.initValue(id, name, f, DONTENUM);
+ }
+
+ public final void initPrototypeConstructor(IdFunctionObject f)
+ {
+ int id = prototypeValues.constructorId;
+ if (id == 0)
+ throw new IllegalStateException();
+ if (f.methodId() != id)
+ throw new IllegalArgumentException();
+ if (isSealed()) { f.sealObject(); }
+ prototypeValues.initValue(id, "constructor", f, DONTENUM);
+ }
+
+ public final void initPrototypeValue(int id, String name, Object value,
+ int attributes)
+ {
+ prototypeValues.initValue(id, name, value, attributes);
+ }
+
+ protected void initPrototypeId(int id)
+ {
+ throw new IllegalStateException(String.valueOf(id));
+ }
+
+ protected int findPrototypeId(String name)
+ {
+ throw new IllegalStateException(name);
+ }
+
+ protected void fillConstructorProperties(IdFunctionObject ctor)
+ {
+ }
+
+ protected void addIdFunctionProperty(Scriptable obj, Object tag, int id,
+ String name, int arity)
+ {
+ Scriptable scope = ScriptableObject.getTopLevelScope(obj);
+ IdFunctionObject f = newIdFunction(tag, id, name, arity, scope);
+ f.addAsProperty(obj);
+ }
+
+ /**
+ * Utility method to construct type error to indicate incompatible call
+ * when converting script thisObj to a particular type is not possible.
+ * Possible usage would be to have a private function like realThis:
+ * <pre>
+ * private static NativeSomething realThis(Scriptable thisObj,
+ * IdFunctionObject f)
+ * {
+ * if (!(thisObj instanceof NativeSomething))
+ * throw incompatibleCallError(f);
+ * return (NativeSomething)thisObj;
+ * }
+ * </pre>
+ * Note that although such function can be implemented universally via
+ * java.lang.Class.isInstance(), it would be much more slower.
+ * @param f function that is attempting to convert 'this'
+ * object.
+ * @return Scriptable object suitable for a check by the instanceof
+ * operator.
+ * @throws RuntimeException if no more instanceof target can be found
+ */
+ protected static EcmaError incompatibleCallError(IdFunctionObject f)
+ {
+ throw ScriptRuntime.typeError1("msg.incompat.call",
+ f.getFunctionName());
+ }
+
+ private IdFunctionObject newIdFunction(Object tag, int id, String name,
+ int arity, Scriptable scope)
+ {
+ IdFunctionObject f = new IdFunctionObject(this, tag, id, name, arity,
+ scope);
+ if (isSealed()) { f.sealObject(); }
+ return f;
+ }
+
+ private void readObject(ObjectInputStream stream)
+ throws IOException, ClassNotFoundException
+ {
+ stream.defaultReadObject();
+ int maxPrototypeId = stream.readInt();
+ if (maxPrototypeId != 0) {
+ activatePrototypeMap(maxPrototypeId);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream stream)
+ throws IOException
+ {
+ stream.defaultWriteObject();
+ int maxPrototypeId = 0;
+ if (prototypeValues != null) {
+ maxPrototypeId = prototypeValues.getMaxId();
+ }
+ stream.writeInt(maxPrototypeId);
+ }
+
+}
+
diff --git a/src/org/mozilla/javascript/ImporterTopLevel.java b/src/org/mozilla/javascript/ImporterTopLevel.java
new file mode 100644
index 0000000..8b04dc9
--- /dev/null
+++ b/src/org/mozilla/javascript/ImporterTopLevel.java
@@ -0,0 +1,324 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Matthias Radestock
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * Class ImporterTopLevel
+ *
+ * This class defines a ScriptableObject that can be instantiated
+ * as a top-level ("global") object to provide functionality similar
+ * to Java's "import" statement.
+ * <p>
+ * This class can be used to create a top-level scope using the following code:
+ * <pre>
+ * Scriptable scope = new ImporterTopLevel(cx);
+ * </pre>
+ * Then JavaScript code will have access to the following methods:
+ * <ul>
+ * <li>importClass - will "import" a class by making its unqualified name
+ * available as a property of the top-level scope
+ * <li>importPackage - will "import" all the classes of the package by
+ * searching for unqualified names as classes qualified
+ * by the given package.
+ * </ul>
+ * The following code from the shell illustrates this use:
+ * <pre>
+ * js> importClass(java.io.File)
+ * js> f = new File('help.txt')
+ * help.txt
+ * js> importPackage(java.util)
+ * js> v = new Vector()
+ * []
+ *
+ * @author Norris Boyd
+ */
+public class ImporterTopLevel extends IdScriptableObject
+{
+ static final long serialVersionUID = -9095380847465315412L;
+
+ private static final Object IMPORTER_TAG = "Importer";
+
+ public ImporterTopLevel() { }
+
+ public ImporterTopLevel(Context cx) {
+ this(cx, false);
+ }
+
+ public ImporterTopLevel(Context cx, boolean sealed)
+ {
+ initStandardObjects(cx, sealed);
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return (topScopeFlag) ? "global" : "JavaImporter";
+ }
+
+ public static void init(Context cx, Scriptable scope, boolean sealed)
+ {
+ ImporterTopLevel obj = new ImporterTopLevel();
+ obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+ }
+
+ public void initStandardObjects(Context cx, boolean sealed)
+ {
+ // Assume that Context.initStandardObjects initialize JavaImporter
+ // property lazily so the above init call is not yet called
+ cx.initStandardObjects(this, sealed);
+ topScopeFlag = true;
+ // If seal is true then exportAsJSClass(cx, seal) would seal
+ // this obj. Since this is scope as well, it would not allow
+ // to add variables.
+ IdFunctionObject ctor = exportAsJSClass(MAX_PROTOTYPE_ID, this, false);
+ if (sealed) {
+ ctor.sealObject();
+ }
+ // delete "constructor" defined by exportAsJSClass so "constructor"
+ // name would refer to Object.constructor
+ // and not to JavaImporter.prototype.constructor.
+ delete("constructor");
+ }
+
+ @Override
+ public boolean has(String name, Scriptable start) {
+ return super.has(name, start)
+ || getPackageProperty(name, start) != NOT_FOUND;
+ }
+
+ @Override
+ public Object get(String name, Scriptable start) {
+ Object result = super.get(name, start);
+ if (result != NOT_FOUND)
+ return result;
+ result = getPackageProperty(name, start);
+ return result;
+ }
+
+ private Object getPackageProperty(String name, Scriptable start) {
+ Object result = NOT_FOUND;
+ Object[] elements;
+ synchronized (importedPackages) {
+ elements = importedPackages.toArray();
+ }
+ for (int i=0; i < elements.length; i++) {
+ NativeJavaPackage p = (NativeJavaPackage) elements[i];
+ Object v = p.getPkgProperty(name, start, false);
+ if (v != null && !(v instanceof NativeJavaPackage)) {
+ if (result == NOT_FOUND) {
+ result = v;
+ } else {
+ throw Context.reportRuntimeError2(
+ "msg.ambig.import", result.toString(), v.toString());
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @deprecated Kept only for compatibility.
+ */
+ public void importPackage(Context cx, Scriptable thisObj, Object[] args,
+ Function funObj)
+ {
+ js_importPackage(args);
+ }
+
+ private Object js_construct(Scriptable scope, Object[] args)
+ {
+ ImporterTopLevel result = new ImporterTopLevel();
+ for (int i = 0; i != args.length; ++i) {
+ Object arg = args[i];
+ if (arg instanceof NativeJavaClass) {
+ result.importClass((NativeJavaClass)arg);
+ } else if (arg instanceof NativeJavaPackage) {
+ result.importPackage((NativeJavaPackage)arg);
+ } else {
+ throw Context.reportRuntimeError1(
+ "msg.not.class.not.pkg", Context.toString(arg));
+ }
+ }
+ // set explicitly prototype and scope
+ // as otherwise in top scope mode BaseFunction.construct
+ // would keep them set to null. It also allow to use
+ // JavaImporter without new and still get properly
+ // initialized object.
+ result.setParentScope(scope);
+ result.setPrototype(this);
+ return result;
+ }
+
+ private Object js_importClass(Object[] args)
+ {
+ for (int i = 0; i != args.length; i++) {
+ Object arg = args[i];
+ if (!(arg instanceof NativeJavaClass)) {
+ throw Context.reportRuntimeError1(
+ "msg.not.class", Context.toString(arg));
+ }
+ importClass((NativeJavaClass)arg);
+ }
+ return Undefined.instance;
+ }
+
+ private Object js_importPackage(Object[] args)
+ {
+ for (int i = 0; i != args.length; i++) {
+ Object arg = args[i];
+ if (!(arg instanceof NativeJavaPackage)) {
+ throw Context.reportRuntimeError1(
+ "msg.not.pkg", Context.toString(arg));
+ }
+ importPackage((NativeJavaPackage)arg);
+ }
+ return Undefined.instance;
+ }
+
+ private void importPackage(NativeJavaPackage pkg)
+ {
+ if(pkg == null) {
+ return;
+ }
+ synchronized (importedPackages) {
+ for (int j = 0; j != importedPackages.size(); j++) {
+ if (pkg.equals(importedPackages.get(j))) {
+ return;
+ }
+ }
+ importedPackages.add(pkg);
+ }
+ }
+
+ private void importClass(NativeJavaClass cl)
+ {
+ String s = cl.getClassObject().getName();
+ String n = s.substring(s.lastIndexOf('.')+1);
+ Object val = get(n, this);
+ if (val != NOT_FOUND && val != cl) {
+ throw Context.reportRuntimeError1("msg.prop.defined", n);
+ }
+ //defineProperty(n, cl, DONTENUM);
+ put(n, this, cl);
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_constructor: arity=0; s="constructor"; break;
+ case Id_importClass: arity=1; s="importClass"; break;
+ case Id_importPackage: arity=1; s="importPackage"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(IMPORTER_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(IMPORTER_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+ switch (id) {
+ case Id_constructor:
+ return js_construct(scope, args);
+
+ case Id_importClass:
+ return realThis(thisObj, f).js_importClass(args);
+
+ case Id_importPackage:
+ return realThis(thisObj, f).js_importPackage(args);
+ }
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+
+ private ImporterTopLevel realThis(Scriptable thisObj, IdFunctionObject f)
+ {
+ if (topScopeFlag) {
+ // when used as top scope importPackage and importClass are global
+ // function that ignore thisObj
+ return this;
+ }
+ if (!(thisObj instanceof ImporterTopLevel))
+ throw incompatibleCallError(f);
+ return (ImporterTopLevel)thisObj;
+ }
+
+// #string_id_map#
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #generated# Last update: 2007-05-09 08:15:24 EDT
+ L0: { id = 0; String X = null; int c;
+ int s_length = s.length();
+ if (s_length==11) {
+ c=s.charAt(0);
+ if (c=='c') { X="constructor";id=Id_constructor; }
+ else if (c=='i') { X="importClass";id=Id_importClass; }
+ }
+ else if (s_length==13) { X="importPackage";id=Id_importPackage; }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_constructor = 1,
+ Id_importClass = 2,
+ Id_importPackage = 3,
+ MAX_PROTOTYPE_ID = 3;
+
+// #/string_id_map#
+
+ private ObjArray importedPackages = new ObjArray();
+ private boolean topScopeFlag;
+}
diff --git a/src/org/mozilla/javascript/InterfaceAdapter.java b/src/org/mozilla/javascript/InterfaceAdapter.java
new file mode 100644
index 0000000..a8d21ba
--- /dev/null
+++ b/src/org/mozilla/javascript/InterfaceAdapter.java
@@ -0,0 +1,156 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.Method;
+
+/**
+ * Adapter to use JS function as implementation of Java interfaces with
+ * single method or multiple methods with the same signature.
+ */
+public class InterfaceAdapter
+{
+ private final Object proxyHelper;
+
+ /**
+ * Make glue object implementing interface cl that will
+ * call the supplied JS function when called.
+ * Only interfaces were all methods have the same signature is supported.
+ *
+ * @return The glue object or null if <tt>cl</tt> is not interface or
+ * has methods with different signatures.
+ */
+ static Object create(Context cx, Class<?> cl, Callable function)
+ {
+ if (!cl.isInterface()) throw new IllegalArgumentException();
+
+ Scriptable topScope = ScriptRuntime.getTopCallScope(cx);
+ ClassCache cache = ClassCache.get(topScope);
+ InterfaceAdapter adapter;
+ adapter = (InterfaceAdapter)cache.getInterfaceAdapter(cl);
+ ContextFactory cf = cx.getFactory();
+ if (adapter == null) {
+ Method[] methods = cl.getMethods();
+ if (methods.length == 0) {
+ throw Context.reportRuntimeError2(
+ "msg.no.empty.interface.conversion",
+ String.valueOf(function),
+ cl.getClass().getName());
+ }
+ boolean canCallFunction = false;
+ canCallFunctionChecks: {
+ Class<?>[] argTypes = methods[0].getParameterTypes();
+ // check that the rest of methods has the same signature
+ for (int i = 1; i != methods.length; ++i) {
+ Class<?>[] types2 = methods[i].getParameterTypes();
+ if (types2.length != argTypes.length) {
+ break canCallFunctionChecks;
+ }
+ for (int j = 0; j != argTypes.length; ++j) {
+ if (types2[j] != argTypes[j]) {
+ break canCallFunctionChecks;
+ }
+ }
+ }
+ canCallFunction= true;
+ }
+ if (!canCallFunction) {
+ throw Context.reportRuntimeError2(
+ "msg.no.function.interface.conversion",
+ String.valueOf(function),
+ cl.getClass().getName());
+ }
+ adapter = new InterfaceAdapter(cf, cl);
+ cache.cacheInterfaceAdapter(cl, adapter);
+ }
+ return VMBridge.instance.newInterfaceProxy(
+ adapter.proxyHelper, cf, adapter, function, topScope);
+ }
+
+ private InterfaceAdapter(ContextFactory cf, Class<?> cl)
+ {
+ this.proxyHelper
+ = VMBridge.instance.getInterfaceProxyHelper(
+ cf, new Class[] { cl });
+ }
+
+ public Object invoke(ContextFactory cf,
+ final Object target,
+ final Scriptable topScope,
+ final Method method,
+ final Object[] args)
+ {
+ ContextAction action = new ContextAction() {
+ public Object run(Context cx)
+ {
+ return invokeImpl(cx, target, topScope, method, args);
+ }
+ };
+ return cf.call(action);
+ }
+
+ Object invokeImpl(Context cx,
+ Object target,
+ Scriptable topScope,
+ Method method,
+ Object[] args)
+ {
+ int N = (args == null) ? 0 : args.length;
+
+ Callable function = (Callable)target;
+ Scriptable thisObj = topScope;
+ Object[] jsargs = new Object[N + 1];
+ jsargs[N] = method.getName();
+ if (N != 0) {
+ WrapFactory wf = cx.getWrapFactory();
+ for (int i = 0; i != N; ++i) {
+ jsargs[i] = wf.wrap(cx, topScope, args[i], null);
+ }
+ }
+
+ Object result = function.call(cx, topScope, thisObj, jsargs);
+ Class<?> javaResultType = method.getReturnType();
+ if (javaResultType == Void.TYPE) {
+ result = null;
+ } else {
+ result = Context.jsToJava(result, javaResultType);
+ }
+ return result;
+ }
+}
diff --git a/src/org/mozilla/javascript/InterpretedFunction.java b/src/org/mozilla/javascript/InterpretedFunction.java
new file mode 100644
index 0000000..c26890b
--- /dev/null
+++ b/src/org/mozilla/javascript/InterpretedFunction.java
@@ -0,0 +1,235 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ * Bob Jervis
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import org.mozilla.javascript.debug.DebuggableScript;
+
+final class InterpretedFunction extends NativeFunction implements Script
+{
+ static final long serialVersionUID = 541475680333911468L;
+
+ InterpreterData idata;
+ SecurityController securityController;
+ Object securityDomain;
+ Scriptable[] functionRegExps;
+
+ private InterpretedFunction(InterpreterData idata,
+ Object staticSecurityDomain)
+ {
+ this.idata = idata;
+
+ // Always get Context from the current thread to
+ // avoid security breaches via passing mangled Context instances
+ // with bogus SecurityController
+ Context cx = Context.getContext();
+ SecurityController sc = cx.getSecurityController();
+ Object dynamicDomain;
+ if (sc != null) {
+ dynamicDomain = sc.getDynamicSecurityDomain(staticSecurityDomain);
+ } else {
+ if (staticSecurityDomain != null) {
+ throw new IllegalArgumentException();
+ }
+ dynamicDomain = null;
+ }
+
+ this.securityController = sc;
+ this.securityDomain = dynamicDomain;
+ }
+
+ private InterpretedFunction(InterpretedFunction parent, int index)
+ {
+ this.idata = parent.idata.itsNestedFunctions[index];
+ this.securityController = parent.securityController;
+ this.securityDomain = parent.securityDomain;
+ }
+
+ /**
+ * Create script from compiled bytecode.
+ */
+ static InterpretedFunction createScript(InterpreterData idata,
+ Object staticSecurityDomain)
+ {
+ InterpretedFunction f;
+ f = new InterpretedFunction(idata, staticSecurityDomain);
+ return f;
+ }
+
+ /**
+ * Create function compiled from Function(...) constructor.
+ */
+ static InterpretedFunction createFunction(Context cx,Scriptable scope,
+ InterpreterData idata,
+ Object staticSecurityDomain)
+ {
+ InterpretedFunction f;
+ f = new InterpretedFunction(idata, staticSecurityDomain);
+ f.initInterpretedFunction(cx, scope);
+ return f;
+ }
+
+ /**
+ * Create function embedded in script or another function.
+ */
+ static InterpretedFunction createFunction(Context cx, Scriptable scope,
+ InterpretedFunction parent,
+ int index)
+ {
+ InterpretedFunction f = new InterpretedFunction(parent, index);
+ f.initInterpretedFunction(cx, scope);
+ return f;
+ }
+
+ Scriptable[] createRegExpWraps(Context cx, Scriptable scope)
+ {
+ if (idata.itsRegExpLiterals == null) Kit.codeBug();
+
+ RegExpProxy rep = ScriptRuntime.checkRegExpProxy(cx);
+ int N = idata.itsRegExpLiterals.length;
+ Scriptable[] array = new Scriptable[N];
+ for (int i = 0; i != N; ++i) {
+ array[i] = rep.wrapRegExp(cx, scope, idata.itsRegExpLiterals[i]);
+ }
+ return array;
+ }
+
+ private void initInterpretedFunction(Context cx, Scriptable scope)
+ {
+ initScriptFunction(cx, scope);
+ if (idata.itsRegExpLiterals != null) {
+ functionRegExps = createRegExpWraps(cx, scope);
+ }
+ }
+
+ @Override
+ public String getFunctionName()
+ {
+ return (idata.itsName == null) ? "" : idata.itsName;
+ }
+
+ /**
+ * Calls the function.
+ * @param cx the current context
+ * @param scope the scope used for the call
+ * @param thisObj the value of "this"
+ * @param args function arguments. Must not be null. You can use
+ * {@link ScriptRuntime#emptyArgs} to pass empty arguments.
+ * @return the result of the function call.
+ */
+ @Override
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ if (!ScriptRuntime.hasTopCall(cx)) {
+ return ScriptRuntime.doTopCall(this, cx, scope, thisObj, args);
+ }
+ return Interpreter.interpret(this, cx, scope, thisObj, args);
+ }
+
+ public Object exec(Context cx, Scriptable scope)
+ {
+ if (!isScript()) {
+ // Can only be applied to scripts
+ throw new IllegalStateException();
+ }
+ if (!ScriptRuntime.hasTopCall(cx)) {
+ // It will go through "call" path. but they are equivalent
+ return ScriptRuntime.doTopCall(
+ this, cx, scope, scope, ScriptRuntime.emptyArgs);
+ }
+ return Interpreter.interpret(
+ this, cx, scope, scope, ScriptRuntime.emptyArgs);
+ }
+
+ public boolean isScript() {
+ return idata.itsFunctionType == 0;
+ }
+
+ @Override
+ public String getEncodedSource()
+ {
+ return Interpreter.getEncodedSource(idata);
+ }
+
+ @Override
+ public DebuggableScript getDebuggableView()
+ {
+ return idata;
+ }
+
+ @Override
+ public Object resumeGenerator(Context cx, Scriptable scope, int operation,
+ Object state, Object value)
+ {
+ return Interpreter.resumeGenerator(cx, scope, operation, state, value);
+ }
+
+ @Override
+ protected int getLanguageVersion()
+ {
+ return idata.languageVersion;
+ }
+
+ @Override
+ protected int getParamCount()
+ {
+ return idata.argCount;
+ }
+
+ @Override
+ protected int getParamAndVarCount()
+ {
+ return idata.argNames.length;
+ }
+
+ @Override
+ protected String getParamOrVarName(int index)
+ {
+ return idata.argNames[index];
+ }
+
+ @Override
+ protected boolean getParamOrVarConst(int index)
+ {
+ return idata.argIsConst[index];
+ }
+}
+
diff --git a/src/org/mozilla/javascript/Interpreter.java b/src/org/mozilla/javascript/Interpreter.java
new file mode 100644
index 0000000..f7f544d
--- /dev/null
+++ b/src/org/mozilla/javascript/Interpreter.java
@@ -0,0 +1,4740 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Patrick Beard
+ * Norris Boyd
+ * Igor Bukanov
+ * Ethan Hugg
+ * Bob Jervis
+ * Terry Lucas
+ * Roger Lawrence
+ * Milen Nankov
+ * Hannes Wallnoefer
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.PrintStream;
+import java.io.Serializable;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.mozilla.javascript.ScriptRuntime.NoSuchMethodShim;
+import org.mozilla.javascript.debug.DebugFrame;
+
+public class Interpreter implements Evaluator
+{
+
+// Additional interpreter-specific codes
+
+ private static final int
+
+ // Stack: ... value1 -> ... value1 value1
+ Icode_DUP = -1,
+
+ // Stack: ... value2 value1 -> ... value2 value1 value2 value1
+ Icode_DUP2 = -2,
+
+ // Stack: ... value2 value1 -> ... value1 value2
+ Icode_SWAP = -3,
+
+ // Stack: ... value1 -> ...
+ Icode_POP = -4,
+
+ // Store stack top into return register and then pop it
+ Icode_POP_RESULT = -5,
+
+ // To jump conditionally and pop additional stack value
+ Icode_IFEQ_POP = -6,
+
+ // various types of ++/--
+ Icode_VAR_INC_DEC = -7,
+ Icode_NAME_INC_DEC = -8,
+ Icode_PROP_INC_DEC = -9,
+ Icode_ELEM_INC_DEC = -10,
+ Icode_REF_INC_DEC = -11,
+
+ // load/save scope from/to local
+ Icode_SCOPE_LOAD = -12,
+ Icode_SCOPE_SAVE = -13,
+
+ Icode_TYPEOFNAME = -14,
+
+ // helper for function calls
+ Icode_NAME_AND_THIS = -15,
+ Icode_PROP_AND_THIS = -16,
+ Icode_ELEM_AND_THIS = -17,
+ Icode_VALUE_AND_THIS = -18,
+
+ // Create closure object for nested functions
+ Icode_CLOSURE_EXPR = -19,
+ Icode_CLOSURE_STMT = -20,
+
+ // Special calls
+ Icode_CALLSPECIAL = -21,
+
+ // To return undefined value
+ Icode_RETUNDEF = -22,
+
+ // Exception handling implementation
+ Icode_GOSUB = -23,
+ Icode_STARTSUB = -24,
+ Icode_RETSUB = -25,
+
+ // To indicating a line number change in icodes.
+ Icode_LINE = -26,
+
+ // To store shorts and ints inline
+ Icode_SHORTNUMBER = -27,
+ Icode_INTNUMBER = -28,
+
+ // To create and populate array to hold values for [] and {} literals
+ Icode_LITERAL_NEW = -29,
+ Icode_LITERAL_SET = -30,
+
+ // Array literal with skipped index like [1,,2]
+ Icode_SPARE_ARRAYLIT = -31,
+
+ // Load index register to prepare for the following index operation
+ Icode_REG_IND_C0 = -32,
+ Icode_REG_IND_C1 = -33,
+ Icode_REG_IND_C2 = -34,
+ Icode_REG_IND_C3 = -35,
+ Icode_REG_IND_C4 = -36,
+ Icode_REG_IND_C5 = -37,
+ Icode_REG_IND1 = -38,
+ Icode_REG_IND2 = -39,
+ Icode_REG_IND4 = -40,
+
+ // Load string register to prepare for the following string operation
+ Icode_REG_STR_C0 = -41,
+ Icode_REG_STR_C1 = -42,
+ Icode_REG_STR_C2 = -43,
+ Icode_REG_STR_C3 = -44,
+ Icode_REG_STR1 = -45,
+ Icode_REG_STR2 = -46,
+ Icode_REG_STR4 = -47,
+
+ // Version of getvar/setvar that read var index directly from bytecode
+ Icode_GETVAR1 = -48,
+ Icode_SETVAR1 = -49,
+
+ // Load unefined
+ Icode_UNDEF = -50,
+ Icode_ZERO = -51,
+ Icode_ONE = -52,
+
+ // entrance and exit from .()
+ Icode_ENTERDQ = -53,
+ Icode_LEAVEDQ = -54,
+
+ Icode_TAIL_CALL = -55,
+
+ // Clear local to allow GC its context
+ Icode_LOCAL_CLEAR = -56,
+
+ // Literal get/set
+ Icode_LITERAL_GETTER = -57,
+ Icode_LITERAL_SETTER = -58,
+
+ // const
+ Icode_SETCONST = -59,
+ Icode_SETCONSTVAR = -60,
+ Icode_SETCONSTVAR1 = -61,
+
+ // Generator opcodes (along with Token.YIELD)
+ Icode_GENERATOR = -62,
+ Icode_GENERATOR_END = -63,
+
+ Icode_DEBUGGER = -64,
+
+ // Last icode
+ MIN_ICODE = -64;
+
+ // data for parsing
+
+ private CompilerEnvirons compilerEnv;
+
+ private boolean itsInFunctionFlag;
+ private boolean itsInTryFlag;
+
+ private InterpreterData itsData;
+ private ScriptOrFnNode scriptOrFn;
+ private int itsICodeTop;
+ private int itsStackDepth;
+ private int itsLineNumber;
+ private int itsDoubleTableTop;
+ private ObjToIntMap itsStrings = new ObjToIntMap(20);
+ private int itsLocalTop;
+
+ private static final int MIN_LABEL_TABLE_SIZE = 32;
+ private static final int MIN_FIXUP_TABLE_SIZE = 40;
+ private int[] itsLabelTable;
+ private int itsLabelTableTop;
+// itsFixupTable[i] = (label_index << 32) | fixup_site
+ private long[] itsFixupTable;
+ private int itsFixupTableTop;
+ private ObjArray itsLiteralIds = new ObjArray();
+
+ private int itsExceptionTableTop;
+ private static final int EXCEPTION_TRY_START_SLOT = 0;
+ private static final int EXCEPTION_TRY_END_SLOT = 1;
+ private static final int EXCEPTION_HANDLER_SLOT = 2;
+ private static final int EXCEPTION_TYPE_SLOT = 3;
+ private static final int EXCEPTION_LOCAL_SLOT = 4;
+ private static final int EXCEPTION_SCOPE_SLOT = 5;
+ // SLOT_SIZE: space for try start/end, handler, start, handler type,
+ // exception local and scope local
+ private static final int EXCEPTION_SLOT_SIZE = 6;
+
+// ECF_ or Expression Context Flags constants: for now only TAIL is available
+ private static final int ECF_TAIL = 1 << 0;
+
+ /**
+ * Class to hold data corresponding to one interpreted call stack frame.
+ */
+ private static class CallFrame implements Cloneable, Serializable
+ {
+ static final long serialVersionUID = -2843792508994958978L;
+
+ CallFrame parentFrame;
+ // amount of stack frames before this one on the interpretation stack
+ int frameIndex;
+ // If true indicates read-only frame that is a part of continuation
+ boolean frozen;
+
+ InterpretedFunction fnOrScript;
+ InterpreterData idata;
+
+// Stack structure
+// stack[0 <= i < localShift]: arguments and local variables
+// stack[localShift <= i <= emptyStackTop]: used for local temporaries
+// stack[emptyStackTop < i < stack.length]: stack data
+// sDbl[i]: if stack[i] is UniqueTag.DOUBLE_MARK, sDbl[i] holds the number value
+
+ Object[] stack;
+ int[] stackAttributes;
+ double[] sDbl;
+ CallFrame varSource; // defaults to this unless continuation frame
+ int localShift;
+ int emptyStackTop;
+
+ DebugFrame debuggerFrame;
+ boolean useActivation;
+ boolean isContinuationsTopFrame;
+
+ Scriptable thisObj;
+ Scriptable[] scriptRegExps;
+
+// The values that change during interpretation
+
+ Object result;
+ double resultDbl;
+ int pc;
+ int pcPrevBranch;
+ int pcSourceLineStart;
+ Scriptable scope;
+
+ int savedStackTop;
+ int savedCallOp;
+ Object throwable;
+
+ CallFrame cloneFrozen()
+ {
+ if (!frozen) Kit.codeBug();
+
+ CallFrame copy;
+ try {
+ copy = (CallFrame)clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new IllegalStateException();
+ }
+
+ // clone stack but keep varSource to point to values
+ // from this frame to share variables.
+
+ copy.stack = stack.clone();
+ copy.stackAttributes = stackAttributes.clone();
+ copy.sDbl = sDbl.clone();
+
+ copy.frozen = false;
+ return copy;
+ }
+ }
+
+ private static final class ContinuationJump implements Serializable
+ {
+ static final long serialVersionUID = 7687739156004308247L;
+
+ CallFrame capturedFrame;
+ CallFrame branchFrame;
+ Object result;
+ double resultDbl;
+
+ ContinuationJump(NativeContinuation c, CallFrame current)
+ {
+ this.capturedFrame = (CallFrame)c.getImplementation();
+ if (this.capturedFrame == null || current == null) {
+ // Continuation and current execution does not share
+ // any frames if there is nothing to capture or
+ // if there is no currently executed frames
+ this.branchFrame = null;
+ } else {
+ // Search for branch frame where parent frame chains starting
+ // from captured and current meet.
+ CallFrame chain1 = this.capturedFrame;
+ CallFrame chain2 = current;
+
+ // First work parents of chain1 or chain2 until the same
+ // frame depth.
+ int diff = chain1.frameIndex - chain2.frameIndex;
+ if (diff != 0) {
+ if (diff < 0) {
+ // swap to make sure that
+ // chain1.frameIndex > chain2.frameIndex and diff > 0
+ chain1 = current;
+ chain2 = this.capturedFrame;
+ diff = -diff;
+ }
+ do {
+ chain1 = chain1.parentFrame;
+ } while (--diff != 0);
+ if (chain1.frameIndex != chain2.frameIndex) Kit.codeBug();
+ }
+
+ // Now walk parents in parallel until a shared frame is found
+ // or until the root is reached.
+ while (chain1 != chain2 && chain1 != null) {
+ chain1 = chain1.parentFrame;
+ chain2 = chain2.parentFrame;
+ }
+
+ this.branchFrame = chain1;
+ if (this.branchFrame != null && !this.branchFrame.frozen)
+ Kit.codeBug();
+ }
+ }
+ }
+
+ private static CallFrame captureFrameForGenerator(CallFrame frame) {
+ frame.frozen = true;
+ CallFrame result = frame.cloneFrozen();
+ frame.frozen = false;
+
+ // now isolate this frame from its previous context
+ result.parentFrame = null;
+ result.frameIndex = 0;
+
+ return result;
+ }
+
+ static {
+ // Checks for byte code consistencies, good compiler can eliminate them
+
+ if (Token.LAST_BYTECODE_TOKEN > 127) {
+ String str = "Violation of Token.LAST_BYTECODE_TOKEN <= 127";
+ System.err.println(str);
+ throw new IllegalStateException(str);
+ }
+ if (MIN_ICODE < -128) {
+ String str = "Violation of Interpreter.MIN_ICODE >= -128";
+ System.err.println(str);
+ throw new IllegalStateException(str);
+ }
+ }
+
+ private static String bytecodeName(int bytecode)
+ {
+ if (!validBytecode(bytecode)) {
+ throw new IllegalArgumentException(String.valueOf(bytecode));
+ }
+
+ if (!Token.printICode) {
+ return String.valueOf(bytecode);
+ }
+
+ if (validTokenCode(bytecode)) {
+ return Token.name(bytecode);
+ }
+
+ switch (bytecode) {
+ case Icode_DUP: return "DUP";
+ case Icode_DUP2: return "DUP2";
+ case Icode_SWAP: return "SWAP";
+ case Icode_POP: return "POP";
+ case Icode_POP_RESULT: return "POP_RESULT";
+ case Icode_IFEQ_POP: return "IFEQ_POP";
+ case Icode_VAR_INC_DEC: return "VAR_INC_DEC";
+ case Icode_NAME_INC_DEC: return "NAME_INC_DEC";
+ case Icode_PROP_INC_DEC: return "PROP_INC_DEC";
+ case Icode_ELEM_INC_DEC: return "ELEM_INC_DEC";
+ case Icode_REF_INC_DEC: return "REF_INC_DEC";
+ case Icode_SCOPE_LOAD: return "SCOPE_LOAD";
+ case Icode_SCOPE_SAVE: return "SCOPE_SAVE";
+ case Icode_TYPEOFNAME: return "TYPEOFNAME";
+ case Icode_NAME_AND_THIS: return "NAME_AND_THIS";
+ case Icode_PROP_AND_THIS: return "PROP_AND_THIS";
+ case Icode_ELEM_AND_THIS: return "ELEM_AND_THIS";
+ case Icode_VALUE_AND_THIS: return "VALUE_AND_THIS";
+ case Icode_CLOSURE_EXPR: return "CLOSURE_EXPR";
+ case Icode_CLOSURE_STMT: return "CLOSURE_STMT";
+ case Icode_CALLSPECIAL: return "CALLSPECIAL";
+ case Icode_RETUNDEF: return "RETUNDEF";
+ case Icode_GOSUB: return "GOSUB";
+ case Icode_STARTSUB: return "STARTSUB";
+ case Icode_RETSUB: return "RETSUB";
+ case Icode_LINE: return "LINE";
+ case Icode_SHORTNUMBER: return "SHORTNUMBER";
+ case Icode_INTNUMBER: return "INTNUMBER";
+ case Icode_LITERAL_NEW: return "LITERAL_NEW";
+ case Icode_LITERAL_SET: return "LITERAL_SET";
+ case Icode_SPARE_ARRAYLIT: return "SPARE_ARRAYLIT";
+ case Icode_REG_IND_C0: return "REG_IND_C0";
+ case Icode_REG_IND_C1: return "REG_IND_C1";
+ case Icode_REG_IND_C2: return "REG_IND_C2";
+ case Icode_REG_IND_C3: return "REG_IND_C3";
+ case Icode_REG_IND_C4: return "REG_IND_C4";
+ case Icode_REG_IND_C5: return "REG_IND_C5";
+ case Icode_REG_IND1: return "LOAD_IND1";
+ case Icode_REG_IND2: return "LOAD_IND2";
+ case Icode_REG_IND4: return "LOAD_IND4";
+ case Icode_REG_STR_C0: return "REG_STR_C0";
+ case Icode_REG_STR_C1: return "REG_STR_C1";
+ case Icode_REG_STR_C2: return "REG_STR_C2";
+ case Icode_REG_STR_C3: return "REG_STR_C3";
+ case Icode_REG_STR1: return "LOAD_STR1";
+ case Icode_REG_STR2: return "LOAD_STR2";
+ case Icode_REG_STR4: return "LOAD_STR4";
+ case Icode_GETVAR1: return "GETVAR1";
+ case Icode_SETVAR1: return "SETVAR1";
+ case Icode_UNDEF: return "UNDEF";
+ case Icode_ZERO: return "ZERO";
+ case Icode_ONE: return "ONE";
+ case Icode_ENTERDQ: return "ENTERDQ";
+ case Icode_LEAVEDQ: return "LEAVEDQ";
+ case Icode_TAIL_CALL: return "TAIL_CALL";
+ case Icode_LOCAL_CLEAR: return "LOCAL_CLEAR";
+ case Icode_LITERAL_GETTER: return "LITERAL_GETTER";
+ case Icode_LITERAL_SETTER: return "LITERAL_SETTER";
+ case Icode_SETCONST: return "SETCONST";
+ case Icode_SETCONSTVAR: return "SETCONSTVAR";
+ case Icode_SETCONSTVAR1: return "SETCONSTVAR1";
+ case Icode_GENERATOR: return "GENERATOR";
+ case Icode_GENERATOR_END: return "GENERATOR_END";
+ case Icode_DEBUGGER: return "DEBUGGER";
+ }
+
+ // icode without name
+ throw new IllegalStateException(String.valueOf(bytecode));
+ }
+
+ private static boolean validIcode(int icode)
+ {
+ return MIN_ICODE <= icode && icode <= -1;
+ }
+
+ private static boolean validTokenCode(int token)
+ {
+ return Token.FIRST_BYTECODE_TOKEN <= token
+ && token <= Token.LAST_BYTECODE_TOKEN;
+ }
+
+ private static boolean validBytecode(int bytecode)
+ {
+ return validIcode(bytecode) || validTokenCode(bytecode);
+ }
+
+ public Object compile(CompilerEnvirons compilerEnv,
+ ScriptOrFnNode tree,
+ String encodedSource,
+ boolean returnFunction)
+ {
+ this.compilerEnv = compilerEnv;
+ new NodeTransformer().transform(tree);
+
+ if (Token.printTrees) {
+ System.out.println(tree.toStringTree(tree));
+ }
+
+ if (returnFunction) {
+ tree = tree.getFunctionNode(0);
+ }
+
+ scriptOrFn = tree;
+ itsData = new InterpreterData(compilerEnv.getLanguageVersion(),
+ scriptOrFn.getSourceName(),
+ encodedSource);
+ itsData.topLevel = true;
+
+ if (returnFunction) {
+ generateFunctionICode();
+ } else {
+ generateICodeFromTree(scriptOrFn);
+ }
+
+ return itsData;
+ }
+
+ public Script createScriptObject(Object bytecode, Object staticSecurityDomain)
+ {
+ if(bytecode != itsData)
+ {
+ Kit.codeBug();
+ }
+ return InterpretedFunction.createScript(itsData,
+ staticSecurityDomain);
+ }
+
+ public void setEvalScriptFlag(Script script) {
+ ((InterpretedFunction)script).idata.evalScriptFlag = true;
+ }
+
+
+ public Function createFunctionObject(Context cx, Scriptable scope,
+ Object bytecode, Object staticSecurityDomain)
+ {
+ if(bytecode != itsData)
+ {
+ Kit.codeBug();
+ }
+ return InterpretedFunction.createFunction(cx, scope, itsData,
+ staticSecurityDomain);
+ }
+
+ private void generateFunctionICode()
+ {
+ itsInFunctionFlag = true;
+
+ FunctionNode theFunction = (FunctionNode)scriptOrFn;
+
+ itsData.itsFunctionType = theFunction.getFunctionType();
+ itsData.itsNeedsActivation = theFunction.requiresActivation();
+ itsData.itsName = theFunction.getFunctionName();
+ if (!theFunction.getIgnoreDynamicScope()) {
+ if (compilerEnv.isUseDynamicScope()) {
+ itsData.useDynamicScope = true;
+ }
+ }
+ if (theFunction.isGenerator()) {
+ addIcode(Icode_GENERATOR);
+ addUint16(theFunction.getBaseLineno() & 0xFFFF);
+ }
+
+ generateICodeFromTree(theFunction.getLastChild());
+ }
+
+ private void generateICodeFromTree(Node tree)
+ {
+ generateNestedFunctions();
+
+ generateRegExpLiterals();
+
+ visitStatement(tree, 0);
+ fixLabelGotos();
+ // add RETURN_RESULT only to scripts as function always ends with RETURN
+ if (itsData.itsFunctionType == 0) {
+ addToken(Token.RETURN_RESULT);
+ }
+
+ if (itsData.itsICode.length != itsICodeTop) {
+ // Make itsData.itsICode length exactly itsICodeTop to save memory
+ // and catch bugs with jumps beyond icode as early as possible
+ byte[] tmp = new byte[itsICodeTop];
+ System.arraycopy(itsData.itsICode, 0, tmp, 0, itsICodeTop);
+ itsData.itsICode = tmp;
+ }
+ if (itsStrings.size() == 0) {
+ itsData.itsStringTable = null;
+ } else {
+ itsData.itsStringTable = new String[itsStrings.size()];
+ ObjToIntMap.Iterator iter = itsStrings.newIterator();
+ for (iter.start(); !iter.done(); iter.next()) {
+ String str = (String)iter.getKey();
+ int index = iter.getValue();
+ if (itsData.itsStringTable[index] != null) Kit.codeBug();
+ itsData.itsStringTable[index] = str;
+ }
+ }
+ if (itsDoubleTableTop == 0) {
+ itsData.itsDoubleTable = null;
+ } else if (itsData.itsDoubleTable.length != itsDoubleTableTop) {
+ double[] tmp = new double[itsDoubleTableTop];
+ System.arraycopy(itsData.itsDoubleTable, 0, tmp, 0,
+ itsDoubleTableTop);
+ itsData.itsDoubleTable = tmp;
+ }
+ if (itsExceptionTableTop != 0
+ && itsData.itsExceptionTable.length != itsExceptionTableTop)
+ {
+ int[] tmp = new int[itsExceptionTableTop];
+ System.arraycopy(itsData.itsExceptionTable, 0, tmp, 0,
+ itsExceptionTableTop);
+ itsData.itsExceptionTable = tmp;
+ }
+
+ itsData.itsMaxVars = scriptOrFn.getParamAndVarCount();
+ // itsMaxFrameArray: interpret method needs this amount for its
+ // stack and sDbl arrays
+ itsData.itsMaxFrameArray = itsData.itsMaxVars
+ + itsData.itsMaxLocals
+ + itsData.itsMaxStack;
+
+ itsData.argNames = scriptOrFn.getParamAndVarNames();
+ itsData.argIsConst = scriptOrFn.getParamAndVarConst();
+ itsData.argCount = scriptOrFn.getParamCount();
+
+ itsData.encodedSourceStart = scriptOrFn.getEncodedSourceStart();
+ itsData.encodedSourceEnd = scriptOrFn.getEncodedSourceEnd();
+
+ if (itsLiteralIds.size() != 0) {
+ itsData.literalIds = itsLiteralIds.toArray();
+ }
+
+ if (Token.printICode) dumpICode(itsData);
+ }
+
+ private void generateNestedFunctions()
+ {
+ int functionCount = scriptOrFn.getFunctionCount();
+ if (functionCount == 0) return;
+
+ InterpreterData[] array = new InterpreterData[functionCount];
+ for (int i = 0; i != functionCount; i++) {
+ FunctionNode def = scriptOrFn.getFunctionNode(i);
+ Interpreter jsi = new Interpreter();
+ jsi.compilerEnv = compilerEnv;
+ jsi.scriptOrFn = def;
+ jsi.itsData = new InterpreterData(itsData);
+ jsi.generateFunctionICode();
+ array[i] = jsi.itsData;
+ }
+ itsData.itsNestedFunctions = array;
+ }
+
+ private void generateRegExpLiterals()
+ {
+ int N = scriptOrFn.getRegexpCount();
+ if (N == 0) return;
+
+ Context cx = Context.getContext();
+ RegExpProxy rep = ScriptRuntime.checkRegExpProxy(cx);
+ Object[] array = new Object[N];
+ for (int i = 0; i != N; i++) {
+ String string = scriptOrFn.getRegexpString(i);
+ String flags = scriptOrFn.getRegexpFlags(i);
+ array[i] = rep.compileRegExp(cx, string, flags);
+ }
+ itsData.itsRegExpLiterals = array;
+ }
+
+ private void updateLineNumber(Node node)
+ {
+ int lineno = node.getLineno();
+ if (lineno != itsLineNumber && lineno >= 0) {
+ if (itsData.firstLinePC < 0) {
+ itsData.firstLinePC = lineno;
+ }
+ itsLineNumber = lineno;
+ addIcode(Icode_LINE);
+ addUint16(lineno & 0xFFFF);
+ }
+ }
+
+ private RuntimeException badTree(Node node)
+ {
+ throw new RuntimeException(node.toString());
+ }
+
+ private void visitStatement(Node node, int initialStackDepth)
+ {
+ int type = node.getType();
+ Node child = node.getFirstChild();
+ switch (type) {
+
+ case Token.FUNCTION:
+ {
+ int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
+ int fnType = scriptOrFn.getFunctionNode(fnIndex).
+ getFunctionType();
+ // Only function expressions or function expression
+ // statements need closure code creating new function
+ // object on stack as function statements are initialized
+ // at script/function start.
+ // In addition, function expressions can not be present here
+ // at statement level, they must only be present as expressions.
+ if (fnType == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) {
+ addIndexOp(Icode_CLOSURE_STMT, fnIndex);
+ } else {
+ if (fnType != FunctionNode.FUNCTION_STATEMENT) {
+ throw Kit.codeBug();
+ }
+ }
+ // For function statements or function expression statements
+ // in scripts, we need to ensure that the result of the script
+ // is the function if it is the last statement in the script.
+ // For example, eval("function () {}") should return a
+ // function, not undefined.
+ if (!itsInFunctionFlag) {
+ addIndexOp(Icode_CLOSURE_EXPR, fnIndex);
+ stackChange(1);
+ addIcode(Icode_POP_RESULT);
+ stackChange(-1);
+ }
+ }
+ break;
+
+ case Token.LABEL:
+ case Token.LOOP:
+ case Token.BLOCK:
+ case Token.EMPTY:
+ case Token.WITH:
+ updateLineNumber(node);
+ case Token.SCRIPT:
+ // fall through
+ while (child != null) {
+ visitStatement(child, initialStackDepth);
+ child = child.getNext();
+ }
+ break;
+
+ case Token.ENTERWITH:
+ visitExpression(child, 0);
+ addToken(Token.ENTERWITH);
+ stackChange(-1);
+ break;
+
+ case Token.LEAVEWITH:
+ addToken(Token.LEAVEWITH);
+ break;
+
+ case Token.LOCAL_BLOCK:
+ {
+ int local = allocLocal();
+ node.putIntProp(Node.LOCAL_PROP, local);
+ updateLineNumber(node);
+ while (child != null) {
+ visitStatement(child, initialStackDepth);
+ child = child.getNext();
+ }
+ addIndexOp(Icode_LOCAL_CLEAR, local);
+ releaseLocal(local);
+ }
+ break;
+
+ case Token.DEBUGGER:
+ addIcode(Icode_DEBUGGER);
+ break;
+
+ case Token.SWITCH:
+ updateLineNumber(node);
+ // See comments in IRFactory.createSwitch() for description
+ // of SWITCH node
+ {
+ visitExpression(child, 0);
+ for (Node.Jump caseNode = (Node.Jump)child.getNext();
+ caseNode != null;
+ caseNode = (Node.Jump)caseNode.getNext())
+ {
+ if (caseNode.getType() != Token.CASE)
+ throw badTree(caseNode);
+ Node test = caseNode.getFirstChild();
+ addIcode(Icode_DUP);
+ stackChange(1);
+ visitExpression(test, 0);
+ addToken(Token.SHEQ);
+ stackChange(-1);
+ // If true, Icode_IFEQ_POP will jump and remove case
+ // value from stack
+ addGoto(caseNode.target, Icode_IFEQ_POP);
+ stackChange(-1);
+ }
+ addIcode(Icode_POP);
+ stackChange(-1);
+ }
+ break;
+
+ case Token.TARGET:
+ markTargetLabel(node);
+ break;
+
+ case Token.IFEQ :
+ case Token.IFNE :
+ {
+ Node target = ((Node.Jump)node).target;
+ visitExpression(child, 0);
+ addGoto(target, type);
+ stackChange(-1);
+ }
+ break;
+
+ case Token.GOTO:
+ {
+ Node target = ((Node.Jump)node).target;
+ addGoto(target, type);
+ }
+ break;
+
+ case Token.JSR:
+ {
+ Node target = ((Node.Jump)node).target;
+ addGoto(target, Icode_GOSUB);
+ }
+ break;
+
+ case Token.FINALLY:
+ {
+ // Account for incomming GOTOSUB address
+ stackChange(1);
+ int finallyRegister = getLocalBlockRef(node);
+ addIndexOp(Icode_STARTSUB, finallyRegister);
+ stackChange(-1);
+ while (child != null) {
+ visitStatement(child, initialStackDepth);
+ child = child.getNext();
+ }
+ addIndexOp(Icode_RETSUB, finallyRegister);
+ }
+ break;
+
+ case Token.EXPR_VOID:
+ case Token.EXPR_RESULT:
+ updateLineNumber(node);
+ visitExpression(child, 0);
+ addIcode((type == Token.EXPR_VOID) ? Icode_POP : Icode_POP_RESULT);
+ stackChange(-1);
+ break;
+
+ case Token.TRY:
+ {
+ Node.Jump tryNode = (Node.Jump)node;
+ int exceptionObjectLocal = getLocalBlockRef(tryNode);
+ int scopeLocal = allocLocal();
+
+ addIndexOp(Icode_SCOPE_SAVE, scopeLocal);
+
+ int tryStart = itsICodeTop;
+ boolean savedFlag = itsInTryFlag;
+ itsInTryFlag = true;
+ while (child != null) {
+ visitStatement(child, initialStackDepth);
+ child = child.getNext();
+ }
+ itsInTryFlag = savedFlag;
+
+ Node catchTarget = tryNode.target;
+ if (catchTarget != null) {
+ int catchStartPC
+ = itsLabelTable[getTargetLabel(catchTarget)];
+ addExceptionHandler(
+ tryStart, catchStartPC, catchStartPC,
+ false, exceptionObjectLocal, scopeLocal);
+ }
+ Node finallyTarget = tryNode.getFinally();
+ if (finallyTarget != null) {
+ int finallyStartPC
+ = itsLabelTable[getTargetLabel(finallyTarget)];
+ addExceptionHandler(
+ tryStart, finallyStartPC, finallyStartPC,
+ true, exceptionObjectLocal, scopeLocal);
+ }
+
+ addIndexOp(Icode_LOCAL_CLEAR, scopeLocal);
+ releaseLocal(scopeLocal);
+ }
+ break;
+
+ case Token.CATCH_SCOPE:
+ {
+ int localIndex = getLocalBlockRef(node);
+ int scopeIndex = node.getExistingIntProp(Node.CATCH_SCOPE_PROP);
+ String name = child.getString();
+ child = child.getNext();
+ visitExpression(child, 0); // load expression object
+ addStringPrefix(name);
+ addIndexPrefix(localIndex);
+ addToken(Token.CATCH_SCOPE);
+ addUint8(scopeIndex != 0 ? 1 : 0);
+ stackChange(-1);
+ }
+ break;
+
+ case Token.THROW:
+ updateLineNumber(node);
+ visitExpression(child, 0);
+ addToken(Token.THROW);
+ addUint16(itsLineNumber & 0xFFFF);
+ stackChange(-1);
+ break;
+
+ case Token.RETHROW:
+ updateLineNumber(node);
+ addIndexOp(Token.RETHROW, getLocalBlockRef(node));
+ break;
+
+ case Token.RETURN:
+ updateLineNumber(node);
+ if (node.getIntProp(Node.GENERATOR_END_PROP, 0) != 0) {
+ // We're in a generator, so change RETURN to GENERATOR_END
+ addIcode(Icode_GENERATOR_END);
+ addUint16(itsLineNumber & 0xFFFF);
+ } else if (child != null) {
+ visitExpression(child, ECF_TAIL);
+ addToken(Token.RETURN);
+ stackChange(-1);
+ } else {
+ addIcode(Icode_RETUNDEF);
+ }
+ break;
+
+ case Token.RETURN_RESULT:
+ updateLineNumber(node);
+ addToken(Token.RETURN_RESULT);
+ break;
+
+ case Token.ENUM_INIT_KEYS:
+ case Token.ENUM_INIT_VALUES:
+ case Token.ENUM_INIT_ARRAY:
+ visitExpression(child, 0);
+ addIndexOp(type, getLocalBlockRef(node));
+ stackChange(-1);
+ break;
+
+ case Icode_GENERATOR:
+ break;
+
+ default:
+ throw badTree(node);
+ }
+
+ if (itsStackDepth != initialStackDepth) {
+ throw Kit.codeBug();
+ }
+ }
+
+ private void visitExpression(Node node, int contextFlags)
+ {
+ int type = node.getType();
+ Node child = node.getFirstChild();
+ int savedStackDepth = itsStackDepth;
+ switch (type) {
+
+ case Token.FUNCTION:
+ {
+ int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
+ FunctionNode fn = scriptOrFn.getFunctionNode(fnIndex);
+ // See comments in visitStatement for Token.FUNCTION case
+ if (fn.getFunctionType() != FunctionNode.FUNCTION_EXPRESSION) {
+ throw Kit.codeBug();
+ }
+ addIndexOp(Icode_CLOSURE_EXPR, fnIndex);
+ stackChange(1);
+ }
+ break;
+
+ case Token.LOCAL_LOAD:
+ {
+ int localIndex = getLocalBlockRef(node);
+ addIndexOp(Token.LOCAL_LOAD, localIndex);
+ stackChange(1);
+ }
+ break;
+
+ case Token.COMMA:
+ {
+ Node lastChild = node.getLastChild();
+ while (child != lastChild) {
+ visitExpression(child, 0);
+ addIcode(Icode_POP);
+ stackChange(-1);
+ child = child.getNext();
+ }
+ // Preserve tail context flag if any
+ visitExpression(child, contextFlags & ECF_TAIL);
+ }
+ break;
+
+ case Token.USE_STACK:
+ // Indicates that stack was modified externally,
+ // like placed catch object
+ stackChange(1);
+ break;
+
+ case Token.REF_CALL:
+ case Token.CALL:
+ case Token.NEW:
+ {
+ if (type == Token.NEW) {
+ visitExpression(child, 0);
+ } else {
+ generateCallFunAndThis(child);
+ }
+ int argCount = 0;
+ while ((child = child.getNext()) != null) {
+ visitExpression(child, 0);
+ ++argCount;
+ }
+ int callType = node.getIntProp(Node.SPECIALCALL_PROP,
+ Node.NON_SPECIALCALL);
+ if (callType != Node.NON_SPECIALCALL) {
+ // embed line number and source filename
+ addIndexOp(Icode_CALLSPECIAL, argCount);
+ addUint8(callType);
+ addUint8(type == Token.NEW ? 1 : 0);
+ addUint16(itsLineNumber & 0xFFFF);
+ } else {
+ // Only use the tail call optimization if we're not in a try
+ // or we're not generating debug info (since the
+ // optimization will confuse the debugger)
+ if (type == Token.CALL && (contextFlags & ECF_TAIL) != 0 &&
+ !compilerEnv.isGenerateDebugInfo() && !itsInTryFlag)
+ {
+ type = Icode_TAIL_CALL;
+ }
+ addIndexOp(type, argCount);
+ }
+ // adjust stack
+ if (type == Token.NEW) {
+ // new: f, args -> result
+ stackChange(-argCount);
+ } else {
+ // call: f, thisObj, args -> result
+ // ref_call: f, thisObj, args -> ref
+ stackChange(-1 - argCount);
+ }
+ if (argCount > itsData.itsMaxCalleeArgs) {
+ itsData.itsMaxCalleeArgs = argCount;
+ }
+ }
+ break;
+
+ case Token.AND:
+ case Token.OR:
+ {
+ visitExpression(child, 0);
+ addIcode(Icode_DUP);
+ stackChange(1);
+ int afterSecondJumpStart = itsICodeTop;
+ int jump = (type == Token.AND) ? Token.IFNE : Token.IFEQ;
+ addGotoOp(jump);
+ stackChange(-1);
+ addIcode(Icode_POP);
+ stackChange(-1);
+ child = child.getNext();
+ // Preserve tail context flag if any
+ visitExpression(child, contextFlags & ECF_TAIL);
+ resolveForwardGoto(afterSecondJumpStart);
+ }
+ break;
+
+ case Token.HOOK:
+ {
+ Node ifThen = child.getNext();
+ Node ifElse = ifThen.getNext();
+ visitExpression(child, 0);
+ int elseJumpStart = itsICodeTop;
+ addGotoOp(Token.IFNE);
+ stackChange(-1);
+ // Preserve tail context flag if any
+ visitExpression(ifThen, contextFlags & ECF_TAIL);
+ int afterElseJumpStart = itsICodeTop;
+ addGotoOp(Token.GOTO);
+ resolveForwardGoto(elseJumpStart);
+ itsStackDepth = savedStackDepth;
+ // Preserve tail context flag if any
+ visitExpression(ifElse, contextFlags & ECF_TAIL);
+ resolveForwardGoto(afterElseJumpStart);
+ }
+ break;
+
+ case Token.GETPROP:
+ case Token.GETPROPNOWARN:
+ visitExpression(child, 0);
+ child = child.getNext();
+ addStringOp(type, child.getString());
+ break;
+
+ case Token.GETELEM:
+ case Token.DELPROP:
+ case Token.BITAND:
+ case Token.BITOR:
+ case Token.BITXOR:
+ case Token.LSH:
+ case Token.RSH:
+ case Token.URSH:
+ case Token.ADD:
+ case Token.SUB:
+ case Token.MOD:
+ case Token.DIV:
+ case Token.MUL:
+ case Token.EQ:
+ case Token.NE:
+ case Token.SHEQ:
+ case Token.SHNE:
+ case Token.IN:
+ case Token.INSTANCEOF:
+ case Token.LE:
+ case Token.LT:
+ case Token.GE:
+ case Token.GT:
+ visitExpression(child, 0);
+ child = child.getNext();
+ visitExpression(child, 0);
+ addToken(type);
+ stackChange(-1);
+ break;
+
+ case Token.POS:
+ case Token.NEG:
+ case Token.NOT:
+ case Token.BITNOT:
+ case Token.TYPEOF:
+ case Token.VOID:
+ visitExpression(child, 0);
+ if (type == Token.VOID) {
+ addIcode(Icode_POP);
+ addIcode(Icode_UNDEF);
+ } else {
+ addToken(type);
+ }
+ break;
+
+ case Token.GET_REF:
+ case Token.DEL_REF:
+ visitExpression(child, 0);
+ addToken(type);
+ break;
+
+ case Token.SETPROP:
+ case Token.SETPROP_OP:
+ {
+ visitExpression(child, 0);
+ child = child.getNext();
+ String property = child.getString();
+ child = child.getNext();
+ if (type == Token.SETPROP_OP) {
+ addIcode(Icode_DUP);
+ stackChange(1);
+ addStringOp(Token.GETPROP, property);
+ // Compensate for the following USE_STACK
+ stackChange(-1);
+ }
+ visitExpression(child, 0);
+ addStringOp(Token.SETPROP, property);
+ stackChange(-1);
+ }
+ break;
+
+ case Token.SETELEM:
+ case Token.SETELEM_OP:
+ visitExpression(child, 0);
+ child = child.getNext();
+ visitExpression(child, 0);
+ child = child.getNext();
+ if (type == Token.SETELEM_OP) {
+ addIcode(Icode_DUP2);
+ stackChange(2);
+ addToken(Token.GETELEM);
+ stackChange(-1);
+ // Compensate for the following USE_STACK
+ stackChange(-1);
+ }
+ visitExpression(child, 0);
+ addToken(Token.SETELEM);
+ stackChange(-2);
+ break;
+
+ case Token.SET_REF:
+ case Token.SET_REF_OP:
+ visitExpression(child, 0);
+ child = child.getNext();
+ if (type == Token.SET_REF_OP) {
+ addIcode(Icode_DUP);
+ stackChange(1);
+ addToken(Token.GET_REF);
+ // Compensate for the following USE_STACK
+ stackChange(-1);
+ }
+ visitExpression(child, 0);
+ addToken(Token.SET_REF);
+ stackChange(-1);
+ break;
+
+ case Token.SETNAME:
+ {
+ String name = child.getString();
+ visitExpression(child, 0);
+ child = child.getNext();
+ visitExpression(child, 0);
+ addStringOp(Token.SETNAME, name);
+ stackChange(-1);
+ }
+ break;
+
+ case Token.SETCONST:
+ {
+ String name = child.getString();
+ visitExpression(child, 0);
+ child = child.getNext();
+ visitExpression(child, 0);
+ addStringOp(Icode_SETCONST, name);
+ stackChange(-1);
+ }
+ break;
+
+ case Token.TYPEOFNAME:
+ {
+ int index = -1;
+ // use typeofname if an activation frame exists
+ // since the vars all exist there instead of in jregs
+ if (itsInFunctionFlag && !itsData.itsNeedsActivation)
+ index = scriptOrFn.getIndexForNameNode(node);
+ if (index == -1) {
+ addStringOp(Icode_TYPEOFNAME, node.getString());
+ stackChange(1);
+ } else {
+ addVarOp(Token.GETVAR, index);
+ stackChange(1);
+ addToken(Token.TYPEOF);
+ }
+ }
+ break;
+
+ case Token.BINDNAME:
+ case Token.NAME:
+ case Token.STRING:
+ addStringOp(type, node.getString());
+ stackChange(1);
+ break;
+
+ case Token.INC:
+ case Token.DEC:
+ visitIncDec(node, child);
+ break;
+
+ case Token.NUMBER:
+ {
+ double num = node.getDouble();
+ int inum = (int)num;
+ if (inum == num) {
+ if (inum == 0) {
+ addIcode(Icode_ZERO);
+ // Check for negative zero
+ if (1.0 / num < 0.0) {
+ addToken(Token.NEG);
+ }
+ } else if (inum == 1) {
+ addIcode(Icode_ONE);
+ } else if ((short)inum == inum) {
+ addIcode(Icode_SHORTNUMBER);
+ // write short as uin16 bit pattern
+ addUint16(inum & 0xFFFF);
+ } else {
+ addIcode(Icode_INTNUMBER);
+ addInt(inum);
+ }
+ } else {
+ int index = getDoubleIndex(num);
+ addIndexOp(Token.NUMBER, index);
+ }
+ stackChange(1);
+ }
+ break;
+
+ case Token.GETVAR:
+ {
+ if (itsData.itsNeedsActivation) Kit.codeBug();
+ int index = scriptOrFn.getIndexForNameNode(node);
+ addVarOp(Token.GETVAR, index);
+ stackChange(1);
+ }
+ break;
+
+ case Token.SETVAR:
+ {
+ if (itsData.itsNeedsActivation) Kit.codeBug();
+ int index = scriptOrFn.getIndexForNameNode(child);
+ child = child.getNext();
+ visitExpression(child, 0);
+ addVarOp(Token.SETVAR, index);
+ }
+ break;
+
+ case Token.SETCONSTVAR:
+ {
+ if (itsData.itsNeedsActivation) Kit.codeBug();
+ int index = scriptOrFn.getIndexForNameNode(child);
+ child = child.getNext();
+ visitExpression(child, 0);
+ addVarOp(Token.SETCONSTVAR, index);
+ }
+ break;
+
+ case Token.NULL:
+ case Token.THIS:
+ case Token.THISFN:
+ case Token.FALSE:
+ case Token.TRUE:
+ addToken(type);
+ stackChange(1);
+ break;
+
+ case Token.ENUM_NEXT:
+ case Token.ENUM_ID:
+ addIndexOp(type, getLocalBlockRef(node));
+ stackChange(1);
+ break;
+
+ case Token.REGEXP:
+ {
+ int index = node.getExistingIntProp(Node.REGEXP_PROP);
+ addIndexOp(Token.REGEXP, index);
+ stackChange(1);
+ }
+ break;
+
+ case Token.ARRAYLIT:
+ case Token.OBJECTLIT:
+ visitLiteral(node, child);
+ break;
+
+ case Token.ARRAYCOMP:
+ visitArrayComprehension(node, child, child.getNext());
+ break;
+
+ case Token.REF_SPECIAL:
+ visitExpression(child, 0);
+ addStringOp(type, (String)node.getProp(Node.NAME_PROP));
+ break;
+
+ case Token.REF_MEMBER:
+ case Token.REF_NS_MEMBER:
+ case Token.REF_NAME:
+ case Token.REF_NS_NAME:
+ {
+ int memberTypeFlags = node.getIntProp(Node.MEMBER_TYPE_PROP, 0);
+ // generate possible target, possible namespace and member
+ int childCount = 0;
+ do {
+ visitExpression(child, 0);
+ ++childCount;
+ child = child.getNext();
+ } while (child != null);
+ addIndexOp(type, memberTypeFlags);
+ stackChange(1 - childCount);
+ }
+ break;
+
+ case Token.DOTQUERY:
+ {
+ int queryPC;
+ updateLineNumber(node);
+ visitExpression(child, 0);
+ addIcode(Icode_ENTERDQ);
+ stackChange(-1);
+ queryPC = itsICodeTop;
+ visitExpression(child.getNext(), 0);
+ addBackwardGoto(Icode_LEAVEDQ, queryPC);
+ }
+ break;
+
+ case Token.DEFAULTNAMESPACE :
+ case Token.ESCXMLATTR :
+ case Token.ESCXMLTEXT :
+ visitExpression(child, 0);
+ addToken(type);
+ break;
+
+ case Token.YIELD:
+ if (child != null) {
+ visitExpression(child, 0);
+ } else {
+ addIcode(Icode_UNDEF);
+ stackChange(1);
+ }
+ addToken(Token.YIELD);
+ addUint16(node.getLineno() & 0xFFFF);
+ break;
+
+ case Token.WITHEXPR: {
+ Node enterWith = node.getFirstChild();
+ Node with = enterWith.getNext();
+ visitExpression(enterWith.getFirstChild(), 0);
+ addToken(Token.ENTERWITH);
+ stackChange(-1);
+ visitExpression(with.getFirstChild(), 0);
+ addToken(Token.LEAVEWITH);
+ break;
+ }
+
+ default:
+ throw badTree(node);
+ }
+ if (savedStackDepth + 1 != itsStackDepth) {
+ Kit.codeBug();
+ }
+ }
+
+ private void generateCallFunAndThis(Node left)
+ {
+ // Generate code to place on stack function and thisObj
+ int type = left.getType();
+ switch (type) {
+ case Token.NAME: {
+ String name = left.getString();
+ // stack: ... -> ... function thisObj
+ addStringOp(Icode_NAME_AND_THIS, name);
+ stackChange(2);
+ break;
+ }
+ case Token.GETPROP:
+ case Token.GETELEM: {
+ Node target = left.getFirstChild();
+ visitExpression(target, 0);
+ Node id = target.getNext();
+ if (type == Token.GETPROP) {
+ String property = id.getString();
+ // stack: ... target -> ... function thisObj
+ addStringOp(Icode_PROP_AND_THIS, property);
+ stackChange(1);
+ } else {
+ visitExpression(id, 0);
+ // stack: ... target id -> ... function thisObj
+ addIcode(Icode_ELEM_AND_THIS);
+ }
+ break;
+ }
+ default:
+ // Including Token.GETVAR
+ visitExpression(left, 0);
+ // stack: ... value -> ... function thisObj
+ addIcode(Icode_VALUE_AND_THIS);
+ stackChange(1);
+ break;
+ }
+ }
+
+ private void visitIncDec(Node node, Node child)
+ {
+ int incrDecrMask = node.getExistingIntProp(Node.INCRDECR_PROP);
+ int childType = child.getType();
+ switch (childType) {
+ case Token.GETVAR : {
+ if (itsData.itsNeedsActivation) Kit.codeBug();
+ int i = scriptOrFn.getIndexForNameNode(child);
+ addVarOp(Icode_VAR_INC_DEC, i);
+ addUint8(incrDecrMask);
+ stackChange(1);
+ break;
+ }
+ case Token.NAME : {
+ String name = child.getString();
+ addStringOp(Icode_NAME_INC_DEC, name);
+ addUint8(incrDecrMask);
+ stackChange(1);
+ break;
+ }
+ case Token.GETPROP : {
+ Node object = child.getFirstChild();
+ visitExpression(object, 0);
+ String property = object.getNext().getString();
+ addStringOp(Icode_PROP_INC_DEC, property);
+ addUint8(incrDecrMask);
+ break;
+ }
+ case Token.GETELEM : {
+ Node object = child.getFirstChild();
+ visitExpression(object, 0);
+ Node index = object.getNext();
+ visitExpression(index, 0);
+ addIcode(Icode_ELEM_INC_DEC);
+ addUint8(incrDecrMask);
+ stackChange(-1);
+ break;
+ }
+ case Token.GET_REF : {
+ Node ref = child.getFirstChild();
+ visitExpression(ref, 0);
+ addIcode(Icode_REF_INC_DEC);
+ addUint8(incrDecrMask);
+ break;
+ }
+ default : {
+ throw badTree(node);
+ }
+ }
+ }
+
+ private void visitLiteral(Node node, Node child)
+ {
+ int type = node.getType();
+ int count;
+ Object[] propertyIds = null;
+ if (type == Token.ARRAYLIT) {
+ count = 0;
+ for (Node n = child; n != null; n = n.getNext()) {
+ ++count;
+ }
+ } else if (type == Token.OBJECTLIT) {
+ propertyIds = (Object[])node.getProp(Node.OBJECT_IDS_PROP);
+ count = propertyIds.length;
+ } else {
+ throw badTree(node);
+ }
+ addIndexOp(Icode_LITERAL_NEW, count);
+ stackChange(2);
+ while (child != null) {
+ int childType = child.getType();
+ if (childType == Token.GET) {
+ visitExpression(child.getFirstChild(), 0);
+ addIcode(Icode_LITERAL_GETTER);
+ } else if (childType == Token.SET) {
+ visitExpression(child.getFirstChild(), 0);
+ addIcode(Icode_LITERAL_SETTER);
+ } else {
+ visitExpression(child, 0);
+ addIcode(Icode_LITERAL_SET);
+ }
+ stackChange(-1);
+ child = child.getNext();
+ }
+ if (type == Token.ARRAYLIT) {
+ int[] skipIndexes = (int[])node.getProp(Node.SKIP_INDEXES_PROP);
+ if (skipIndexes == null) {
+ addToken(Token.ARRAYLIT);
+ } else {
+ int index = itsLiteralIds.size();
+ itsLiteralIds.add(skipIndexes);
+ addIndexOp(Icode_SPARE_ARRAYLIT, index);
+ }
+ } else {
+ int index = itsLiteralIds.size();
+ itsLiteralIds.add(propertyIds);
+ addIndexOp(Token.OBJECTLIT, index);
+ }
+ stackChange(-1);
+ }
+
+ private void visitArrayComprehension(Node node, Node initStmt, Node expr)
+ {
+ // A bit of a hack: array comprehensions are implemented using
+ // statement nodes for the iteration, yet they appear in an
+ // expression context. So we pass the current stack depth to
+ // visitStatement so it can check that the depth is not altered
+ // by statements.
+ visitStatement(initStmt, itsStackDepth);
+ visitExpression(expr, 0);
+ }
+
+ private int getLocalBlockRef(Node node)
+ {
+ Node localBlock = (Node)node.getProp(Node.LOCAL_BLOCK_PROP);
+ return localBlock.getExistingIntProp(Node.LOCAL_PROP);
+ }
+
+ private int getTargetLabel(Node target)
+ {
+ int label = target.labelId();
+ if (label != -1) {
+ return label;
+ }
+ label = itsLabelTableTop;
+ if (itsLabelTable == null || label == itsLabelTable.length) {
+ if (itsLabelTable == null) {
+ itsLabelTable = new int[MIN_LABEL_TABLE_SIZE];
+ }else {
+ int[] tmp = new int[itsLabelTable.length * 2];
+ System.arraycopy(itsLabelTable, 0, tmp, 0, label);
+ itsLabelTable = tmp;
+ }
+ }
+ itsLabelTableTop = label + 1;
+ itsLabelTable[label] = -1;
+
+ target.labelId(label);
+ return label;
+ }
+
+ private void markTargetLabel(Node target)
+ {
+ int label = getTargetLabel(target);
+ if (itsLabelTable[label] != -1) {
+ // Can mark label only once
+ Kit.codeBug();
+ }
+ itsLabelTable[label] = itsICodeTop;
+ }
+
+ private void addGoto(Node target, int gotoOp)
+ {
+ int label = getTargetLabel(target);
+ if (!(label < itsLabelTableTop)) Kit.codeBug();
+ int targetPC = itsLabelTable[label];
+
+ if (targetPC != -1) {
+ addBackwardGoto(gotoOp, targetPC);
+ } else {
+ int gotoPC = itsICodeTop;
+ addGotoOp(gotoOp);
+ int top = itsFixupTableTop;
+ if (itsFixupTable == null || top == itsFixupTable.length) {
+ if (itsFixupTable == null) {
+ itsFixupTable = new long[MIN_FIXUP_TABLE_SIZE];
+ } else {
+ long[] tmp = new long[itsFixupTable.length * 2];
+ System.arraycopy(itsFixupTable, 0, tmp, 0, top);
+ itsFixupTable = tmp;
+ }
+ }
+ itsFixupTableTop = top + 1;
+ itsFixupTable[top] = ((long)label << 32) | gotoPC;
+ }
+ }
+
+ private void fixLabelGotos()
+ {
+ for (int i = 0; i < itsFixupTableTop; i++) {
+ long fixup = itsFixupTable[i];
+ int label = (int)(fixup >> 32);
+ int jumpSource = (int)fixup;
+ int pc = itsLabelTable[label];
+ if (pc == -1) {
+ // Unlocated label
+ throw Kit.codeBug();
+ }
+ resolveGoto(jumpSource, pc);
+ }
+ itsFixupTableTop = 0;
+ }
+
+ private void addBackwardGoto(int gotoOp, int jumpPC)
+ {
+ int fromPC = itsICodeTop;
+ // Ensure that this is a jump backward
+ if (fromPC <= jumpPC) throw Kit.codeBug();
+ addGotoOp(gotoOp);
+ resolveGoto(fromPC, jumpPC);
+ }
+
+ private void resolveForwardGoto(int fromPC)
+ {
+ // Ensure that forward jump skips at least self bytecode
+ if (itsICodeTop < fromPC + 3) throw Kit.codeBug();
+ resolveGoto(fromPC, itsICodeTop);
+ }
+
+ private void resolveGoto(int fromPC, int jumpPC)
+ {
+ int offset = jumpPC - fromPC;
+ // Ensure that jumps do not overlap
+ if (0 <= offset && offset <= 2) throw Kit.codeBug();
+ int offsetSite = fromPC + 1;
+ if (offset != (short)offset) {
+ if (itsData.longJumps == null) {
+ itsData.longJumps = new UintMap();
+ }
+ itsData.longJumps.put(offsetSite, jumpPC);
+ offset = 0;
+ }
+ byte[] array = itsData.itsICode;
+ array[offsetSite] = (byte)(offset >> 8);
+ array[offsetSite + 1] = (byte)offset;
+ }
+
+ private void addToken(int token)
+ {
+ if (!validTokenCode(token)) throw Kit.codeBug();
+ addUint8(token);
+ }
+
+ private void addIcode(int icode)
+ {
+ if (!validIcode(icode)) throw Kit.codeBug();
+ // Write negative icode as uint8 bits
+ addUint8(icode & 0xFF);
+ }
+
+ private void addUint8(int value)
+ {
+ if ((value & ~0xFF) != 0) throw Kit.codeBug();
+ byte[] array = itsData.itsICode;
+ int top = itsICodeTop;
+ if (top == array.length) {
+ array = increaseICodeCapacity(1);
+ }
+ array[top] = (byte)value;
+ itsICodeTop = top + 1;
+ }
+
+ private void addUint16(int value)
+ {
+ if ((value & ~0xFFFF) != 0) throw Kit.codeBug();
+ byte[] array = itsData.itsICode;
+ int top = itsICodeTop;
+ if (top + 2 > array.length) {
+ array = increaseICodeCapacity(2);
+ }
+ array[top] = (byte)(value >>> 8);
+ array[top + 1] = (byte)value;
+ itsICodeTop = top + 2;
+ }
+
+ private void addInt(int i)
+ {
+ byte[] array = itsData.itsICode;
+ int top = itsICodeTop;
+ if (top + 4 > array.length) {
+ array = increaseICodeCapacity(4);
+ }
+ array[top] = (byte)(i >>> 24);
+ array[top + 1] = (byte)(i >>> 16);
+ array[top + 2] = (byte)(i >>> 8);
+ array[top + 3] = (byte)i;
+ itsICodeTop = top + 4;
+ }
+
+ private int getDoubleIndex(double num)
+ {
+ int index = itsDoubleTableTop;
+ if (index == 0) {
+ itsData.itsDoubleTable = new double[64];
+ } else if (itsData.itsDoubleTable.length == index) {
+ double[] na = new double[index * 2];
+ System.arraycopy(itsData.itsDoubleTable, 0, na, 0, index);
+ itsData.itsDoubleTable = na;
+ }
+ itsData.itsDoubleTable[index] = num;
+ itsDoubleTableTop = index + 1;
+ return index;
+ }
+
+ private void addGotoOp(int gotoOp)
+ {
+ byte[] array = itsData.itsICode;
+ int top = itsICodeTop;
+ if (top + 3 > array.length) {
+ array = increaseICodeCapacity(3);
+ }
+ array[top] = (byte)gotoOp;
+ // Offset would written later
+ itsICodeTop = top + 1 + 2;
+ }
+
+ private void addVarOp(int op, int varIndex)
+ {
+ switch (op) {
+ case Token.SETCONSTVAR:
+ if (varIndex < 128) {
+ addIcode(Icode_SETCONSTVAR1);
+ addUint8(varIndex);
+ return;
+ }
+ addIndexOp(Icode_SETCONSTVAR, varIndex);
+ return;
+ case Token.GETVAR:
+ case Token.SETVAR:
+ if (varIndex < 128) {
+ addIcode(op == Token.GETVAR ? Icode_GETVAR1 : Icode_SETVAR1);
+ addUint8(varIndex);
+ return;
+ }
+ // fallthrough
+ case Icode_VAR_INC_DEC:
+ addIndexOp(op, varIndex);
+ return;
+ }
+ throw Kit.codeBug();
+ }
+
+ private void addStringOp(int op, String str)
+ {
+ addStringPrefix(str);
+ if (validIcode(op)) {
+ addIcode(op);
+ } else {
+ addToken(op);
+ }
+ }
+
+ private void addIndexOp(int op, int index)
+ {
+ addIndexPrefix(index);
+ if (validIcode(op)) {
+ addIcode(op);
+ } else {
+ addToken(op);
+ }
+ }
+
+ private void addStringPrefix(String str)
+ {
+ int index = itsStrings.get(str, -1);
+ if (index == -1) {
+ index = itsStrings.size();
+ itsStrings.put(str, index);
+ }
+ if (index < 4) {
+ addIcode(Icode_REG_STR_C0 - index);
+ } else if (index <= 0xFF) {
+ addIcode(Icode_REG_STR1);
+ addUint8(index);
+ } else if (index <= 0xFFFF) {
+ addIcode(Icode_REG_STR2);
+ addUint16(index);
+ } else {
+ addIcode(Icode_REG_STR4);
+ addInt(index);
+ }
+ }
+
+ private void addIndexPrefix(int index)
+ {
+ if (index < 0) Kit.codeBug();
+ if (index < 6) {
+ addIcode(Icode_REG_IND_C0 - index);
+ } else if (index <= 0xFF) {
+ addIcode(Icode_REG_IND1);
+ addUint8(index);
+ } else if (index <= 0xFFFF) {
+ addIcode(Icode_REG_IND2);
+ addUint16(index);
+ } else {
+ addIcode(Icode_REG_IND4);
+ addInt(index);
+ }
+ }
+
+ private void addExceptionHandler(int icodeStart, int icodeEnd,
+ int handlerStart, boolean isFinally,
+ int exceptionObjectLocal, int scopeLocal)
+ {
+ int top = itsExceptionTableTop;
+ int[] table = itsData.itsExceptionTable;
+ if (table == null) {
+ if (top != 0) Kit.codeBug();
+ table = new int[EXCEPTION_SLOT_SIZE * 2];
+ itsData.itsExceptionTable = table;
+ } else if (table.length == top) {
+ table = new int[table.length * 2];
+ System.arraycopy(itsData.itsExceptionTable, 0, table, 0, top);
+ itsData.itsExceptionTable = table;
+ }
+ table[top + EXCEPTION_TRY_START_SLOT] = icodeStart;
+ table[top + EXCEPTION_TRY_END_SLOT] = icodeEnd;
+ table[top + EXCEPTION_HANDLER_SLOT] = handlerStart;
+ table[top + EXCEPTION_TYPE_SLOT] = isFinally ? 1 : 0;
+ table[top + EXCEPTION_LOCAL_SLOT] = exceptionObjectLocal;
+ table[top + EXCEPTION_SCOPE_SLOT] = scopeLocal;
+
+ itsExceptionTableTop = top + EXCEPTION_SLOT_SIZE;
+ }
+
+ private byte[] increaseICodeCapacity(int extraSize)
+ {
+ int capacity = itsData.itsICode.length;
+ int top = itsICodeTop;
+ if (top + extraSize <= capacity) throw Kit.codeBug();
+ capacity *= 2;
+ if (top + extraSize > capacity) {
+ capacity = top + extraSize;
+ }
+ byte[] array = new byte[capacity];
+ System.arraycopy(itsData.itsICode, 0, array, 0, top);
+ itsData.itsICode = array;
+ return array;
+ }
+
+ private void stackChange(int change)
+ {
+ if (change <= 0) {
+ itsStackDepth += change;
+ } else {
+ int newDepth = itsStackDepth + change;
+ if (newDepth > itsData.itsMaxStack) {
+ itsData.itsMaxStack = newDepth;
+ }
+ itsStackDepth = newDepth;
+ }
+ }
+
+ private int allocLocal()
+ {
+ int localSlot = itsLocalTop;
+ ++itsLocalTop;
+ if (itsLocalTop > itsData.itsMaxLocals) {
+ itsData.itsMaxLocals = itsLocalTop;
+ }
+ return localSlot;
+ }
+
+ private void releaseLocal(int localSlot)
+ {
+ --itsLocalTop;
+ if (localSlot != itsLocalTop) Kit.codeBug();
+ }
+
+ private static int getShort(byte[] iCode, int pc) {
+ return (iCode[pc] << 8) | (iCode[pc + 1] & 0xFF);
+ }
+
+ private static int getIndex(byte[] iCode, int pc) {
+ return ((iCode[pc] & 0xFF) << 8) | (iCode[pc + 1] & 0xFF);
+ }
+
+ private static int getInt(byte[] iCode, int pc) {
+ return (iCode[pc] << 24) | ((iCode[pc + 1] & 0xFF) << 16)
+ | ((iCode[pc + 2] & 0xFF) << 8) | (iCode[pc + 3] & 0xFF);
+ }
+
+ private static int getExceptionHandler(CallFrame frame,
+ boolean onlyFinally)
+ {
+ int[] exceptionTable = frame.idata.itsExceptionTable;
+ if (exceptionTable == null) {
+ // No exception handlers
+ return -1;
+ }
+
+ // Icode switch in the interpreter increments PC immediately
+ // and it is necessary to subtract 1 from the saved PC
+ // to point it before the start of the next instruction.
+ int pc = frame.pc - 1;
+
+ // OPT: use binary search
+ int best = -1, bestStart = 0, bestEnd = 0;
+ for (int i = 0; i != exceptionTable.length; i += EXCEPTION_SLOT_SIZE) {
+ int start = exceptionTable[i + EXCEPTION_TRY_START_SLOT];
+ int end = exceptionTable[i + EXCEPTION_TRY_END_SLOT];
+ if (!(start <= pc && pc < end)) {
+ continue;
+ }
+ if (onlyFinally && exceptionTable[i + EXCEPTION_TYPE_SLOT] != 1) {
+ continue;
+ }
+ if (best >= 0) {
+ // Since handlers always nest and they never have shared end
+ // although they can share start it is sufficient to compare
+ // handlers ends
+ if (bestEnd < end) {
+ continue;
+ }
+ // Check the above assumption
+ if (bestStart > start) Kit.codeBug(); // should be nested
+ if (bestEnd == end) Kit.codeBug(); // no ens sharing
+ }
+ best = i;
+ bestStart = start;
+ bestEnd = end;
+ }
+ return best;
+ }
+
+ private static void dumpICode(InterpreterData idata)
+ {
+ if (!Token.printICode) {
+ return;
+ }
+
+ byte iCode[] = idata.itsICode;
+ int iCodeLength = iCode.length;
+ String[] strings = idata.itsStringTable;
+ PrintStream out = System.out;
+ out.println("ICode dump, for " + idata.itsName
+ + ", length = " + iCodeLength);
+ out.println("MaxStack = " + idata.itsMaxStack);
+
+ int indexReg = 0;
+ for (int pc = 0; pc < iCodeLength; ) {
+ out.flush();
+ out.print(" [" + pc + "] ");
+ int token = iCode[pc];
+ int icodeLength = bytecodeSpan(token);
+ String tname = bytecodeName(token);
+ int old_pc = pc;
+ ++pc;
+ switch (token) {
+ default:
+ if (icodeLength != 1) Kit.codeBug();
+ out.println(tname);
+ break;
+
+ case Icode_GOSUB :
+ case Token.GOTO :
+ case Token.IFEQ :
+ case Token.IFNE :
+ case Icode_IFEQ_POP :
+ case Icode_LEAVEDQ : {
+ int newPC = pc + getShort(iCode, pc) - 1;
+ out.println(tname + " " + newPC);
+ pc += 2;
+ break;
+ }
+ case Icode_VAR_INC_DEC :
+ case Icode_NAME_INC_DEC :
+ case Icode_PROP_INC_DEC :
+ case Icode_ELEM_INC_DEC :
+ case Icode_REF_INC_DEC: {
+ int incrDecrType = iCode[pc];
+ out.println(tname + " " + incrDecrType);
+ ++pc;
+ break;
+ }
+
+ case Icode_CALLSPECIAL : {
+ int callType = iCode[pc] & 0xFF;
+ boolean isNew = (iCode[pc + 1] != 0);
+ int line = getIndex(iCode, pc+2);
+ out.println(tname+" "+callType+" "+isNew+" "+indexReg+" "+line);
+ pc += 4;
+ break;
+ }
+
+ case Token.CATCH_SCOPE:
+ {
+ boolean afterFisrtFlag = (iCode[pc] != 0);
+ out.println(tname+" "+afterFisrtFlag);
+ ++pc;
+ }
+ break;
+ case Token.REGEXP :
+ out.println(tname+" "+idata.itsRegExpLiterals[indexReg]);
+ break;
+ case Token.OBJECTLIT :
+ case Icode_SPARE_ARRAYLIT :
+ out.println(tname+" "+idata.literalIds[indexReg]);
+ break;
+ case Icode_CLOSURE_EXPR :
+ case Icode_CLOSURE_STMT :
+ out.println(tname+" "+idata.itsNestedFunctions[indexReg]);
+ break;
+ case Token.CALL :
+ case Icode_TAIL_CALL :
+ case Token.REF_CALL :
+ case Token.NEW :
+ out.println(tname+' '+indexReg);
+ break;
+ case Token.THROW :
+ case Token.YIELD :
+ case Icode_GENERATOR :
+ case Icode_GENERATOR_END :
+ {
+ int line = getIndex(iCode, pc);
+ out.println(tname + " : " + line);
+ pc += 2;
+ break;
+ }
+ case Icode_SHORTNUMBER : {
+ int value = getShort(iCode, pc);
+ out.println(tname + " " + value);
+ pc += 2;
+ break;
+ }
+ case Icode_INTNUMBER : {
+ int value = getInt(iCode, pc);
+ out.println(tname + " " + value);
+ pc += 4;
+ break;
+ }
+ case Token.NUMBER : {
+ double value = idata.itsDoubleTable[indexReg];
+ out.println(tname + " " + value);
+ break;
+ }
+ case Icode_LINE : {
+ int line = getIndex(iCode, pc);
+ out.println(tname + " : " + line);
+ pc += 2;
+ break;
+ }
+ case Icode_REG_STR1: {
+ String str = strings[0xFF & iCode[pc]];
+ out.println(tname + " \"" + str + '"');
+ ++pc;
+ break;
+ }
+ case Icode_REG_STR2: {
+ String str = strings[getIndex(iCode, pc)];
+ out.println(tname + " \"" + str + '"');
+ pc += 2;
+ break;
+ }
+ case Icode_REG_STR4: {
+ String str = strings[getInt(iCode, pc)];
+ out.println(tname + " \"" + str + '"');
+ pc += 4;
+ break;
+ }
+ case Icode_REG_IND_C0:
+ indexReg = 0;
+ out.println(tname);
+ break;
+ case Icode_REG_IND_C1:
+ indexReg = 1;
+ out.println(tname);
+ break;
+ case Icode_REG_IND_C2:
+ indexReg = 2;
+ out.println(tname);
+ break;
+ case Icode_REG_IND_C3:
+ indexReg = 3;
+ out.println(tname);
+ break;
+ case Icode_REG_IND_C4:
+ indexReg = 4;
+ out.println(tname);
+ break;
+ case Icode_REG_IND_C5:
+ indexReg = 5;
+ out.println(tname);
+ break;
+ case Icode_REG_IND1: {
+ indexReg = 0xFF & iCode[pc];
+ out.println(tname+" "+indexReg);
+ ++pc;
+ break;
+ }
+ case Icode_REG_IND2: {
+ indexReg = getIndex(iCode, pc);
+ out.println(tname+" "+indexReg);
+ pc += 2;
+ break;
+ }
+ case Icode_REG_IND4: {
+ indexReg = getInt(iCode, pc);
+ out.println(tname+" "+indexReg);
+ pc += 4;
+ break;
+ }
+ case Icode_GETVAR1:
+ case Icode_SETVAR1:
+ case Icode_SETCONSTVAR1:
+ indexReg = iCode[pc];
+ out.println(tname+" "+indexReg);
+ ++pc;
+ break;
+ }
+ if (old_pc + icodeLength != pc) Kit.codeBug();
+ }
+
+ int[] table = idata.itsExceptionTable;
+ if (table != null) {
+ out.println("Exception handlers: "
+ +table.length / EXCEPTION_SLOT_SIZE);
+ for (int i = 0; i != table.length;
+ i += EXCEPTION_SLOT_SIZE)
+ {
+ int tryStart = table[i + EXCEPTION_TRY_START_SLOT];
+ int tryEnd = table[i + EXCEPTION_TRY_END_SLOT];
+ int handlerStart = table[i + EXCEPTION_HANDLER_SLOT];
+ int type = table[i + EXCEPTION_TYPE_SLOT];
+ int exceptionLocal = table[i + EXCEPTION_LOCAL_SLOT];
+ int scopeLocal = table[i + EXCEPTION_SCOPE_SLOT];
+
+ out.println(" tryStart="+tryStart+" tryEnd="+tryEnd
+ +" handlerStart="+handlerStart
+ +" type="+(type == 0 ? "catch" : "finally")
+ +" exceptionLocal="+exceptionLocal);
+ }
+ }
+ out.flush();
+ }
+
+ private static int bytecodeSpan(int bytecode)
+ {
+ switch (bytecode) {
+ case Token.THROW :
+ case Token.YIELD:
+ case Icode_GENERATOR:
+ case Icode_GENERATOR_END:
+ // source line
+ return 1 + 2;
+
+ case Icode_GOSUB :
+ case Token.GOTO :
+ case Token.IFEQ :
+ case Token.IFNE :
+ case Icode_IFEQ_POP :
+ case Icode_LEAVEDQ :
+ // target pc offset
+ return 1 + 2;
+
+ case Icode_CALLSPECIAL :
+ // call type
+ // is new
+ // line number
+ return 1 + 1 + 1 + 2;
+
+ case Token.CATCH_SCOPE:
+ // scope flag
+ return 1 + 1;
+
+ case Icode_VAR_INC_DEC:
+ case Icode_NAME_INC_DEC:
+ case Icode_PROP_INC_DEC:
+ case Icode_ELEM_INC_DEC:
+ case Icode_REF_INC_DEC:
+ // type of ++/--
+ return 1 + 1;
+
+ case Icode_SHORTNUMBER :
+ // short number
+ return 1 + 2;
+
+ case Icode_INTNUMBER :
+ // int number
+ return 1 + 4;
+
+ case Icode_REG_IND1:
+ // ubyte index
+ return 1 + 1;
+
+ case Icode_REG_IND2:
+ // ushort index
+ return 1 + 2;
+
+ case Icode_REG_IND4:
+ // int index
+ return 1 + 4;
+
+ case Icode_REG_STR1:
+ // ubyte string index
+ return 1 + 1;
+
+ case Icode_REG_STR2:
+ // ushort string index
+ return 1 + 2;
+
+ case Icode_REG_STR4:
+ // int string index
+ return 1 + 4;
+
+ case Icode_GETVAR1:
+ case Icode_SETVAR1:
+ case Icode_SETCONSTVAR1:
+ // byte var index
+ return 1 + 1;
+
+ case Icode_LINE :
+ // line number
+ return 1 + 2;
+ }
+ if (!validBytecode(bytecode)) throw Kit.codeBug();
+ return 1;
+ }
+
+ static int[] getLineNumbers(InterpreterData data)
+ {
+ UintMap presentLines = new UintMap();
+
+ byte[] iCode = data.itsICode;
+ int iCodeLength = iCode.length;
+ for (int pc = 0; pc != iCodeLength;) {
+ int bytecode = iCode[pc];
+ int span = bytecodeSpan(bytecode);
+ if (bytecode == Icode_LINE) {
+ if (span != 3) Kit.codeBug();
+ int line = getIndex(iCode, pc + 1);
+ presentLines.put(line, 0);
+ }
+ pc += span;
+ }
+
+ return presentLines.getKeys();
+ }
+
+ public void captureStackInfo(RhinoException ex)
+ {
+ Context cx = Context.getCurrentContext();
+ if (cx == null || cx.lastInterpreterFrame == null) {
+ // No interpreter invocations
+ ex.interpreterStackInfo = null;
+ ex.interpreterLineData = null;
+ return;
+ }
+ // has interpreter frame on the stack
+ CallFrame[] array;
+ if (cx.previousInterpreterInvocations == null
+ || cx.previousInterpreterInvocations.size() == 0)
+ {
+ array = new CallFrame[1];
+ } else {
+ int previousCount = cx.previousInterpreterInvocations.size();
+ if (cx.previousInterpreterInvocations.peek()
+ == cx.lastInterpreterFrame)
+ {
+ // It can happen if exception was generated after
+ // frame was pushed to cx.previousInterpreterInvocations
+ // but before assignment to cx.lastInterpreterFrame.
+ // In this case frames has to be ignored.
+ --previousCount;
+ }
+ array = new CallFrame[previousCount + 1];
+ cx.previousInterpreterInvocations.toArray(array);
+ }
+ array[array.length - 1] = (CallFrame)cx.lastInterpreterFrame;
+
+ int interpreterFrameCount = 0;
+ for (int i = 0; i != array.length; ++i) {
+ interpreterFrameCount += 1 + array[i].frameIndex;
+ }
+
+ int[] linePC = new int[interpreterFrameCount];
+ // Fill linePC with pc positions from all interpreter frames.
+ // Start from the most nested frame
+ int linePCIndex = interpreterFrameCount;
+ for (int i = array.length; i != 0;) {
+ --i;
+ CallFrame frame = array[i];
+ while (frame != null) {
+ --linePCIndex;
+ linePC[linePCIndex] = frame.pcSourceLineStart;
+ frame = frame.parentFrame;
+ }
+ }
+ if (linePCIndex != 0) Kit.codeBug();
+
+ ex.interpreterStackInfo = array;
+ ex.interpreterLineData = linePC;
+ }
+
+ public String getSourcePositionFromStack(Context cx, int[] linep)
+ {
+ CallFrame frame = (CallFrame)cx.lastInterpreterFrame;
+ InterpreterData idata = frame.idata;
+ if (frame.pcSourceLineStart >= 0) {
+ linep[0] = getIndex(idata.itsICode, frame.pcSourceLineStart);
+ } else {
+ linep[0] = 0;
+ }
+ return idata.itsSourceFile;
+ }
+
+ public String getPatchedStack(RhinoException ex,
+ String nativeStackTrace)
+ {
+ String tag = "org.mozilla.javascript.Interpreter.interpretLoop";
+ StringBuffer sb = new StringBuffer(nativeStackTrace.length() + 1000);
+ String lineSeparator = SecurityUtilities.getSystemProperty("line.separator");
+
+ CallFrame[] array = (CallFrame[])ex.interpreterStackInfo;
+ int[] linePC = ex.interpreterLineData;
+ int arrayIndex = array.length;
+ int linePCIndex = linePC.length;
+ int offset = 0;
+ while (arrayIndex != 0) {
+ --arrayIndex;
+ int pos = nativeStackTrace.indexOf(tag, offset);
+ if (pos < 0) {
+ break;
+ }
+
+ // Skip tag length
+ pos += tag.length();
+ // Skip until the end of line
+ for (; pos != nativeStackTrace.length(); ++pos) {
+ char c = nativeStackTrace.charAt(pos);
+ if (c == '\n' || c == '\r') {
+ break;
+ }
+ }
+ sb.append(nativeStackTrace.substring(offset, pos));
+ offset = pos;
+
+ CallFrame frame = array[arrayIndex];
+ while (frame != null) {
+ if (linePCIndex == 0) Kit.codeBug();
+ --linePCIndex;
+ InterpreterData idata = frame.idata;
+ sb.append(lineSeparator);
+ sb.append("\tat script");
+ if (idata.itsName != null && idata.itsName.length() != 0) {
+ sb.append('.');
+ sb.append(idata.itsName);
+ }
+ sb.append('(');
+ sb.append(idata.itsSourceFile);
+ int pc = linePC[linePCIndex];
+ if (pc >= 0) {
+ // Include line info only if available
+ sb.append(':');
+ sb.append(getIndex(idata.itsICode, pc));
+ }
+ sb.append(')');
+ frame = frame.parentFrame;
+ }
+ }
+ sb.append(nativeStackTrace.substring(offset));
+
+ return sb.toString();
+ }
+
+ public List<String> getScriptStack(RhinoException ex)
+ {
+ if (ex.interpreterStackInfo == null) {
+ return null;
+ }
+
+ List<String> list = new ArrayList<String>();
+ String lineSeparator =
+ SecurityUtilities.getSystemProperty("line.separator");
+
+ CallFrame[] array = (CallFrame[])ex.interpreterStackInfo;
+ int[] linePC = ex.interpreterLineData;
+ int arrayIndex = array.length;
+ int linePCIndex = linePC.length;
+ while (arrayIndex != 0) {
+ --arrayIndex;
+ StringBuilder sb = new StringBuilder();
+ CallFrame frame = array[arrayIndex];
+ while (frame != null) {
+ if (linePCIndex == 0) Kit.codeBug();
+ --linePCIndex;
+ InterpreterData idata = frame.idata;
+ sb.append("\tat ");
+ sb.append(idata.itsSourceFile);
+ int pc = linePC[linePCIndex];
+ if (pc >= 0) {
+ // Include line info only if available
+ sb.append(':');
+ sb.append(getIndex(idata.itsICode, pc));
+ }
+ if (idata.itsName != null && idata.itsName.length() != 0) {
+ sb.append(" (");
+ sb.append(idata.itsName);
+ sb.append(')');
+ }
+ sb.append(lineSeparator);
+ frame = frame.parentFrame;
+ }
+ list.add(sb.toString());
+ }
+ return list;
+ }
+
+ static String getEncodedSource(InterpreterData idata)
+ {
+ if (idata.encodedSource == null) {
+ return null;
+ }
+ return idata.encodedSource.substring(idata.encodedSourceStart,
+ idata.encodedSourceEnd);
+ }
+
+ private static void initFunction(Context cx, Scriptable scope,
+ InterpretedFunction parent, int index)
+ {
+ InterpretedFunction fn;
+ fn = InterpretedFunction.createFunction(cx, scope, parent, index);
+ ScriptRuntime.initFunction(cx, scope, fn, fn.idata.itsFunctionType,
+ parent.idata.evalScriptFlag);
+ }
+
+ static Object interpret(InterpretedFunction ifun,
+ Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!ScriptRuntime.hasTopCall(cx)) Kit.codeBug();
+
+ if (cx.interpreterSecurityDomain != ifun.securityDomain) {
+ Object savedDomain = cx.interpreterSecurityDomain;
+ cx.interpreterSecurityDomain = ifun.securityDomain;
+ try {
+ return ifun.securityController.callWithDomain(
+ ifun.securityDomain, cx, ifun, scope, thisObj, args);
+ } finally {
+ cx.interpreterSecurityDomain = savedDomain;
+ }
+ }
+
+ CallFrame frame = new CallFrame();
+ initFrame(cx, scope, thisObj, args, null, 0, args.length,
+ ifun, null, frame);
+ frame.isContinuationsTopFrame = cx.isContinuationsTopCall;
+ cx.isContinuationsTopCall = false;
+
+ return interpretLoop(cx, frame, null);
+ }
+
+ static class GeneratorState {
+ GeneratorState(int operation, Object value) {
+ this.operation = operation;
+ this.value = value;
+ }
+ int operation;
+ Object value;
+ RuntimeException returnedException;
+ }
+
+ public static Object resumeGenerator(Context cx,
+ Scriptable scope,
+ int operation,
+ Object savedState,
+ Object value)
+ {
+ CallFrame frame = (CallFrame) savedState;
+ GeneratorState generatorState = new GeneratorState(operation, value);
+ if (operation == NativeGenerator.GENERATOR_CLOSE) {
+ try {
+ return interpretLoop(cx, frame, generatorState);
+ } catch (RuntimeException e) {
+ // Only propagate exceptions other than closingException
+ if (e != value)
+ throw e;
+ }
+ return Undefined.instance;
+ }
+ Object result = interpretLoop(cx, frame, generatorState);
+ if (generatorState.returnedException != null)
+ throw generatorState.returnedException;
+ return result;
+ }
+
+ public static Object restartContinuation(NativeContinuation c, Context cx,
+ Scriptable scope, Object[] args)
+ {
+ if (!ScriptRuntime.hasTopCall(cx)) {
+ return ScriptRuntime.doTopCall(c, cx, scope, null, args);
+ }
+
+ Object arg;
+ if (args.length == 0) {
+ arg = Undefined.instance;
+ } else {
+ arg = args[0];
+ }
+
+ CallFrame capturedFrame = (CallFrame)c.getImplementation();
+ if (capturedFrame == null) {
+ // No frames to restart
+ return arg;
+ }
+
+ ContinuationJump cjump = new ContinuationJump(c, null);
+
+ cjump.result = arg;
+ return interpretLoop(cx, null, cjump);
+ }
+
+ private static Object interpretLoop(Context cx, CallFrame frame,
+ Object throwable)
+ {
+ // throwable holds exception object to rethrow or catch
+ // It is also used for continuation restart in which case
+ // it holds ContinuationJump
+
+ final Object DBL_MRK = UniqueTag.DOUBLE_MARK;
+ final Object undefined = Undefined.instance;
+
+ final boolean instructionCounting = (cx.instructionThreshold != 0);
+ // arbitrary number to add to instructionCount when calling
+ // other functions
+ final int INVOCATION_COST = 100;
+ // arbitrary exception cost for instruction counting
+ final int EXCEPTION_COST = 100;
+
+ String stringReg = null;
+ int indexReg = -1;
+
+ if (cx.lastInterpreterFrame != null) {
+ // save the top frame from the previous interpretLoop
+ // invocation on the stack
+ if (cx.previousInterpreterInvocations == null) {
+ cx.previousInterpreterInvocations = new ObjArray();
+ }
+ cx.previousInterpreterInvocations.push(cx.lastInterpreterFrame);
+ }
+
+ // When restarting continuation throwable is not null and to jump
+ // to the code that rewind continuation state indexReg should be set
+ // to -1.
+ // With the normal call throwable == null and indexReg == -1 allows to
+ // catch bugs with using indeReg to access array elements before
+ // initializing indexReg.
+
+ GeneratorState generatorState = null;
+ if (throwable != null) {
+ if (throwable instanceof GeneratorState) {
+ generatorState = (GeneratorState) throwable;
+
+ // reestablish this call frame
+ enterFrame(cx, frame, ScriptRuntime.emptyArgs, true);
+ throwable = null;
+ } else if (!(throwable instanceof ContinuationJump)) {
+ // It should be continuation
+ Kit.codeBug();
+ }
+ }
+
+ Object interpreterResult = null;
+ double interpreterResultDbl = 0.0;
+
+ StateLoop: for (;;) {
+ withoutExceptions: try {
+
+ if (throwable != null) {
+ // Need to return both 'frame' and 'throwable' from
+ // 'processThrowable', so just added a 'throwable'
+ // member in 'frame'.
+ frame = processThrowable(cx, throwable, frame, indexReg,
+ instructionCounting);
+ throwable = frame.throwable;
+ frame.throwable = null;
+ } else {
+ if (generatorState == null && frame.frozen) Kit.codeBug();
+ }
+
+ // Use local variables for constant values in frame
+ // for faster access
+ Object[] stack = frame.stack;
+ double[] sDbl = frame.sDbl;
+ Object[] vars = frame.varSource.stack;
+ double[] varDbls = frame.varSource.sDbl;
+ int[] varAttributes = frame.varSource.stackAttributes;
+ byte[] iCode = frame.idata.itsICode;
+ String[] strings = frame.idata.itsStringTable;
+
+ // Use local for stackTop as well. Since execption handlers
+ // can only exist at statement level where stack is empty,
+ // it is necessary to save/restore stackTop only across
+ // function calls and normal returns.
+ int stackTop = frame.savedStackTop;
+
+ // Store new frame in cx which is used for error reporting etc.
+ cx.lastInterpreterFrame = frame;
+
+ Loop: for (;;) {
+
+ // Exception handler assumes that PC is already incremented
+ // pass the instruction start when it searches the
+ // exception handler
+ int op = iCode[frame.pc++];
+ jumplessRun: {
+
+ // Back indent to ease implementation reading
+switch (op) {
+ case Icode_GENERATOR: {
+ if (!frame.frozen) {
+ // First time encountering this opcode: create new generator
+ // object and return
+ frame.pc--; // we want to come back here when we resume
+ CallFrame generatorFrame = captureFrameForGenerator(frame);
+ generatorFrame.frozen = true;
+ NativeGenerator generator = new NativeGenerator(frame.scope,
+ generatorFrame.fnOrScript, generatorFrame);
+ frame.result = generator;
+ break Loop;
+ } else {
+ // We are now resuming execution. Fall through to YIELD case.
+ }
+ }
+ // fall through...
+ case Token.YIELD: {
+ if (!frame.frozen) {
+ return freezeGenerator(cx, frame, stackTop, generatorState);
+ } else {
+ Object obj = thawGenerator(frame, stackTop, generatorState, op);
+ if (obj != Scriptable.NOT_FOUND) {
+ throwable = obj;
+ break withoutExceptions;
+ }
+ continue Loop;
+ }
+ }
+ case Icode_GENERATOR_END: {
+ // throw StopIteration
+ frame.frozen = true;
+ int sourceLine = getIndex(iCode, frame.pc);
+ generatorState.returnedException = new JavaScriptException(
+ NativeIterator.getStopIterationObject(frame.scope),
+ frame.idata.itsSourceFile, sourceLine);
+ break Loop;
+ }
+ case Token.THROW: {
+ Object value = stack[stackTop];
+ if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+
+ int sourceLine = getIndex(iCode, frame.pc);
+ throwable = new JavaScriptException(value,
+ frame.idata.itsSourceFile,
+ sourceLine);
+ break withoutExceptions;
+ }
+ case Token.RETHROW: {
+ indexReg += frame.localShift;
+ throwable = stack[indexReg];
+ break withoutExceptions;
+ }
+ case Token.GE :
+ case Token.LE :
+ case Token.GT :
+ case Token.LT : {
+ --stackTop;
+ Object rhs = stack[stackTop + 1];
+ Object lhs = stack[stackTop];
+ boolean valBln;
+ object_compare:
+ {
+ number_compare:
+ {
+ double rDbl, lDbl;
+ if (rhs == DBL_MRK) {
+ rDbl = sDbl[stackTop + 1];
+ lDbl = stack_double(frame, stackTop);
+ } else if (lhs == DBL_MRK) {
+ rDbl = ScriptRuntime.toNumber(rhs);
+ lDbl = sDbl[stackTop];
+ } else {
+ break number_compare;
+ }
+ switch (op) {
+ case Token.GE:
+ valBln = (lDbl >= rDbl);
+ break object_compare;
+ case Token.LE:
+ valBln = (lDbl <= rDbl);
+ break object_compare;
+ case Token.GT:
+ valBln = (lDbl > rDbl);
+ break object_compare;
+ case Token.LT:
+ valBln = (lDbl < rDbl);
+ break object_compare;
+ default:
+ throw Kit.codeBug();
+ }
+ }
+ switch (op) {
+ case Token.GE:
+ valBln = ScriptRuntime.cmp_LE(rhs, lhs);
+ break;
+ case Token.LE:
+ valBln = ScriptRuntime.cmp_LE(lhs, rhs);
+ break;
+ case Token.GT:
+ valBln = ScriptRuntime.cmp_LT(rhs, lhs);
+ break;
+ case Token.LT:
+ valBln = ScriptRuntime.cmp_LT(lhs, rhs);
+ break;
+ default:
+ throw Kit.codeBug();
+ }
+ }
+ stack[stackTop] = ScriptRuntime.wrapBoolean(valBln);
+ continue Loop;
+ }
+ case Token.IN :
+ case Token.INSTANCEOF : {
+ Object rhs = stack[stackTop];
+ if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ boolean valBln;
+ if (op == Token.IN) {
+ valBln = ScriptRuntime.in(lhs, rhs, cx);
+ } else {
+ valBln = ScriptRuntime.instanceOf(lhs, rhs, cx);
+ }
+ stack[stackTop] = ScriptRuntime.wrapBoolean(valBln);
+ continue Loop;
+ }
+ case Token.EQ :
+ case Token.NE : {
+ --stackTop;
+ boolean valBln;
+ Object rhs = stack[stackTop + 1];
+ Object lhs = stack[stackTop];
+ if (rhs == DBL_MRK) {
+ if (lhs == DBL_MRK) {
+ valBln = (sDbl[stackTop] == sDbl[stackTop + 1]);
+ } else {
+ valBln = ScriptRuntime.eqNumber(sDbl[stackTop + 1], lhs);
+ }
+ } else {
+ if (lhs == DBL_MRK) {
+ valBln = ScriptRuntime.eqNumber(sDbl[stackTop], rhs);
+ } else {
+ valBln = ScriptRuntime.eq(lhs, rhs);
+ }
+ }
+ valBln ^= (op == Token.NE);
+ stack[stackTop] = ScriptRuntime.wrapBoolean(valBln);
+ continue Loop;
+ }
+ case Token.SHEQ :
+ case Token.SHNE : {
+ --stackTop;
+ boolean valBln = shallowEquals(stack, sDbl, stackTop);
+ valBln ^= (op == Token.SHNE);
+ stack[stackTop] = ScriptRuntime.wrapBoolean(valBln);
+ continue Loop;
+ }
+ case Token.IFNE :
+ if (stack_boolean(frame, stackTop--)) {
+ frame.pc += 2;
+ continue Loop;
+ }
+ break jumplessRun;
+ case Token.IFEQ :
+ if (!stack_boolean(frame, stackTop--)) {
+ frame.pc += 2;
+ continue Loop;
+ }
+ break jumplessRun;
+ case Icode_IFEQ_POP :
+ if (!stack_boolean(frame, stackTop--)) {
+ frame.pc += 2;
+ continue Loop;
+ }
+ stack[stackTop--] = null;
+ break jumplessRun;
+ case Token.GOTO :
+ break jumplessRun;
+ case Icode_GOSUB :
+ ++stackTop;
+ stack[stackTop] = DBL_MRK;
+ sDbl[stackTop] = frame.pc + 2;
+ break jumplessRun;
+ case Icode_STARTSUB :
+ if (stackTop == frame.emptyStackTop + 1) {
+ // Call from Icode_GOSUB: store return PC address in the local
+ indexReg += frame.localShift;
+ stack[indexReg] = stack[stackTop];
+ sDbl[indexReg] = sDbl[stackTop];
+ --stackTop;
+ } else {
+ // Call from exception handler: exception object is already stored
+ // in the local
+ if (stackTop != frame.emptyStackTop) Kit.codeBug();
+ }
+ continue Loop;
+ case Icode_RETSUB : {
+ // indexReg: local to store return address
+ if (instructionCounting) {
+ addInstructionCount(cx, frame, 0);
+ }
+ indexReg += frame.localShift;
+ Object value = stack[indexReg];
+ if (value != DBL_MRK) {
+ // Invocation from exception handler, restore object to rethrow
+ throwable = value;
+ break withoutExceptions;
+ }
+ // Normal return from GOSUB
+ frame.pc = (int)sDbl[indexReg];
+ if (instructionCounting) {
+ frame.pcPrevBranch = frame.pc;
+ }
+ continue Loop;
+ }
+ case Icode_POP :
+ stack[stackTop] = null;
+ stackTop--;
+ continue Loop;
+ case Icode_POP_RESULT :
+ frame.result = stack[stackTop];
+ frame.resultDbl = sDbl[stackTop];
+ stack[stackTop] = null;
+ --stackTop;
+ continue Loop;
+ case Icode_DUP :
+ stack[stackTop + 1] = stack[stackTop];
+ sDbl[stackTop + 1] = sDbl[stackTop];
+ stackTop++;
+ continue Loop;
+ case Icode_DUP2 :
+ stack[stackTop + 1] = stack[stackTop - 1];
+ sDbl[stackTop + 1] = sDbl[stackTop - 1];
+ stack[stackTop + 2] = stack[stackTop];
+ sDbl[stackTop + 2] = sDbl[stackTop];
+ stackTop += 2;
+ continue Loop;
+ case Icode_SWAP : {
+ Object o = stack[stackTop];
+ stack[stackTop] = stack[stackTop - 1];
+ stack[stackTop - 1] = o;
+ double d = sDbl[stackTop];
+ sDbl[stackTop] = sDbl[stackTop - 1];
+ sDbl[stackTop - 1] = d;
+ continue Loop;
+ }
+ case Token.RETURN :
+ frame.result = stack[stackTop];
+ frame.resultDbl = sDbl[stackTop];
+ --stackTop;
+ break Loop;
+ case Token.RETURN_RESULT :
+ break Loop;
+ case Icode_RETUNDEF :
+ frame.result = undefined;
+ break Loop;
+ case Token.BITNOT : {
+ int rIntValue = stack_int32(frame, stackTop);
+ stack[stackTop] = DBL_MRK;
+ sDbl[stackTop] = ~rIntValue;
+ continue Loop;
+ }
+ case Token.BITAND :
+ case Token.BITOR :
+ case Token.BITXOR :
+ case Token.LSH :
+ case Token.RSH : {
+ int lIntValue = stack_int32(frame, stackTop-1);
+ int rIntValue = stack_int32(frame, stackTop);
+ stack[--stackTop] = DBL_MRK;
+ switch (op) {
+ case Token.BITAND:
+ lIntValue &= rIntValue;
+ break;
+ case Token.BITOR:
+ lIntValue |= rIntValue;
+ break;
+ case Token.BITXOR:
+ lIntValue ^= rIntValue;
+ break;
+ case Token.LSH:
+ lIntValue <<= rIntValue;
+ break;
+ case Token.RSH:
+ lIntValue >>= rIntValue;
+ break;
+ }
+ sDbl[stackTop] = lIntValue;
+ continue Loop;
+ }
+ case Token.URSH : {
+ double lDbl = stack_double(frame, stackTop-1);
+ int rIntValue = stack_int32(frame, stackTop) & 0x1F;
+ stack[--stackTop] = DBL_MRK;
+ sDbl[stackTop] = ScriptRuntime.toUint32(lDbl) >>> rIntValue;
+ continue Loop;
+ }
+ case Token.NEG :
+ case Token.POS : {
+ double rDbl = stack_double(frame, stackTop);
+ stack[stackTop] = DBL_MRK;
+ if (op == Token.NEG) {
+ rDbl = -rDbl;
+ }
+ sDbl[stackTop] = rDbl;
+ continue Loop;
+ }
+ case Token.ADD :
+ --stackTop;
+ do_add(stack, sDbl, stackTop, cx);
+ continue Loop;
+ case Token.SUB :
+ case Token.MUL :
+ case Token.DIV :
+ case Token.MOD : {
+ double rDbl = stack_double(frame, stackTop);
+ --stackTop;
+ double lDbl = stack_double(frame, stackTop);
+ stack[stackTop] = DBL_MRK;
+ switch (op) {
+ case Token.SUB:
+ lDbl -= rDbl;
+ break;
+ case Token.MUL:
+ lDbl *= rDbl;
+ break;
+ case Token.DIV:
+ lDbl /= rDbl;
+ break;
+ case Token.MOD:
+ lDbl %= rDbl;
+ break;
+ }
+ sDbl[stackTop] = lDbl;
+ continue Loop;
+ }
+ case Token.NOT :
+ stack[stackTop] = ScriptRuntime.wrapBoolean(
+ !stack_boolean(frame, stackTop));
+ continue Loop;
+ case Token.BINDNAME :
+ stack[++stackTop] = ScriptRuntime.bind(cx, frame.scope, stringReg);
+ continue Loop;
+ case Token.SETNAME : {
+ Object rhs = stack[stackTop];
+ if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ Scriptable lhs = (Scriptable)stack[stackTop];
+ stack[stackTop] = ScriptRuntime.setName(lhs, rhs, cx,
+ frame.scope, stringReg);
+ continue Loop;
+ }
+ case Icode_SETCONST: {
+ Object rhs = stack[stackTop];
+ if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ Scriptable lhs = (Scriptable)stack[stackTop];
+ stack[stackTop] = ScriptRuntime.setConst(lhs, rhs, cx, stringReg);
+ continue Loop;
+ }
+ case Token.DELPROP : {
+ Object rhs = stack[stackTop];
+ if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.delete(lhs, rhs, cx);
+ continue Loop;
+ }
+ case Token.GETPROPNOWARN : {
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.getObjectPropNoWarn(lhs, stringReg, cx);
+ continue Loop;
+ }
+ case Token.GETPROP : {
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.getObjectProp(lhs, stringReg, cx, frame.scope);
+ continue Loop;
+ }
+ case Token.SETPROP : {
+ Object rhs = stack[stackTop];
+ if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.setObjectProp(lhs, stringReg, rhs,
+ cx);
+ continue Loop;
+ }
+ case Icode_PROP_INC_DEC : {
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.propIncrDecr(lhs, stringReg,
+ cx, iCode[frame.pc]);
+ ++frame.pc;
+ continue Loop;
+ }
+ case Token.GETELEM : {
+ --stackTop;
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) {
+ lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ }
+ Object value;
+ Object id = stack[stackTop + 1];
+ if (id != DBL_MRK) {
+ value = ScriptRuntime.getObjectElem(lhs, id, cx, frame.scope);
+ } else {
+ double d = sDbl[stackTop + 1];
+ value = ScriptRuntime.getObjectIndex(lhs, d, cx);
+ }
+ stack[stackTop] = value;
+ continue Loop;
+ }
+ case Token.SETELEM : {
+ stackTop -= 2;
+ Object rhs = stack[stackTop + 2];
+ if (rhs == DBL_MRK) {
+ rhs = ScriptRuntime.wrapNumber(sDbl[stackTop + 2]);
+ }
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) {
+ lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ }
+ Object value;
+ Object id = stack[stackTop + 1];
+ if (id != DBL_MRK) {
+ value = ScriptRuntime.setObjectElem(lhs, id, rhs, cx);
+ } else {
+ double d = sDbl[stackTop + 1];
+ value = ScriptRuntime.setObjectIndex(lhs, d, rhs, cx);
+ }
+ stack[stackTop] = value;
+ continue Loop;
+ }
+ case Icode_ELEM_INC_DEC: {
+ Object rhs = stack[stackTop];
+ if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.elemIncrDecr(lhs, rhs, cx,
+ iCode[frame.pc]);
+ ++frame.pc;
+ continue Loop;
+ }
+ case Token.GET_REF : {
+ Ref ref = (Ref)stack[stackTop];
+ stack[stackTop] = ScriptRuntime.refGet(ref, cx);
+ continue Loop;
+ }
+ case Token.SET_REF : {
+ Object value = stack[stackTop];
+ if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ Ref ref = (Ref)stack[stackTop];
+ stack[stackTop] = ScriptRuntime.refSet(ref, value, cx);
+ continue Loop;
+ }
+ case Token.DEL_REF : {
+ Ref ref = (Ref)stack[stackTop];
+ stack[stackTop] = ScriptRuntime.refDel(ref, cx);
+ continue Loop;
+ }
+ case Icode_REF_INC_DEC : {
+ Ref ref = (Ref)stack[stackTop];
+ stack[stackTop] = ScriptRuntime.refIncrDecr(ref, cx, iCode[frame.pc]);
+ ++frame.pc;
+ continue Loop;
+ }
+ case Token.LOCAL_LOAD :
+ ++stackTop;
+ indexReg += frame.localShift;
+ stack[stackTop] = stack[indexReg];
+ sDbl[stackTop] = sDbl[indexReg];
+ continue Loop;
+ case Icode_LOCAL_CLEAR :
+ indexReg += frame.localShift;
+ stack[indexReg] = null;
+ continue Loop;
+ case Icode_NAME_AND_THIS :
+ // stringReg: name
+ ++stackTop;
+ stack[stackTop] = ScriptRuntime.getNameFunctionAndThis(stringReg,
+ cx, frame.scope);
+ ++stackTop;
+ stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx);
+ continue Loop;
+ case Icode_PROP_AND_THIS: {
+ Object obj = stack[stackTop];
+ if (obj == DBL_MRK) obj = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ // stringReg: property
+ stack[stackTop] = ScriptRuntime.getPropFunctionAndThis(obj, stringReg,
+ cx, frame.scope);
+ ++stackTop;
+ stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx);
+ continue Loop;
+ }
+ case Icode_ELEM_AND_THIS: {
+ Object obj = stack[stackTop - 1];
+ if (obj == DBL_MRK) obj = ScriptRuntime.wrapNumber(sDbl[stackTop - 1]);
+ Object id = stack[stackTop];
+ if (id == DBL_MRK) id = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop - 1] = ScriptRuntime.getElemFunctionAndThis(obj, id, cx);
+ stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx);
+ continue Loop;
+ }
+ case Icode_VALUE_AND_THIS : {
+ Object value = stack[stackTop];
+ if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.getValueFunctionAndThis(value, cx);
+ ++stackTop;
+ stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx);
+ continue Loop;
+ }
+ case Icode_CALLSPECIAL : {
+ if (instructionCounting) {
+ cx.instructionCount += INVOCATION_COST;
+ }
+ int callType = iCode[frame.pc] & 0xFF;
+ boolean isNew = (iCode[frame.pc + 1] != 0);
+ int sourceLine = getIndex(iCode, frame.pc + 2);
+
+ // indexReg: number of arguments
+ if (isNew) {
+ // stack change: function arg0 .. argN -> newResult
+ stackTop -= indexReg;
+
+ Object function = stack[stackTop];
+ if (function == DBL_MRK)
+ function = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ Object[] outArgs = getArgsArray(
+ stack, sDbl, stackTop + 1, indexReg);
+ stack[stackTop] = ScriptRuntime.newSpecial(
+ cx, function, outArgs, frame.scope, callType);
+ } else {
+ // stack change: function thisObj arg0 .. argN -> result
+ stackTop -= 1 + indexReg;
+
+ // Call code generation ensure that stack here
+ // is ... Callable Scriptable
+ Scriptable functionThis = (Scriptable)stack[stackTop + 1];
+ Callable function = (Callable)stack[stackTop];
+ Object[] outArgs = getArgsArray(
+ stack, sDbl, stackTop + 2, indexReg);
+ stack[stackTop] = ScriptRuntime.callSpecial(
+ cx, function, functionThis, outArgs,
+ frame.scope, frame.thisObj, callType,
+ frame.idata.itsSourceFile, sourceLine);
+ }
+ frame.pc += 4;
+ continue Loop;
+ }
+ case Token.CALL :
+ case Icode_TAIL_CALL :
+ case Token.REF_CALL : {
+ if (instructionCounting) {
+ cx.instructionCount += INVOCATION_COST;
+ }
+ // stack change: function thisObj arg0 .. argN -> result
+ // indexReg: number of arguments
+ stackTop -= 1 + indexReg;
+
+ // CALL generation ensures that fun and funThisObj
+ // are already Scriptable and Callable objects respectively
+ Callable fun = (Callable)stack[stackTop];
+ Scriptable funThisObj = (Scriptable)stack[stackTop + 1];
+ if (op == Token.REF_CALL) {
+ Object[] outArgs = getArgsArray(stack, sDbl, stackTop + 2,
+ indexReg);
+ stack[stackTop] = ScriptRuntime.callRef(fun, funThisObj,
+ outArgs, cx);
+ continue Loop;
+ }
+ Scriptable calleeScope = frame.scope;
+ if (frame.useActivation) {
+ calleeScope = ScriptableObject.getTopLevelScope(frame.scope);
+ }
+ if (fun instanceof InterpretedFunction) {
+ InterpretedFunction ifun = (InterpretedFunction)fun;
+ if (frame.fnOrScript.securityDomain == ifun.securityDomain) {
+ CallFrame callParentFrame = frame;
+ CallFrame calleeFrame = new CallFrame();
+ if (op == Icode_TAIL_CALL) {
+ // In principle tail call can re-use the current
+ // frame and its stack arrays but it is hard to
+ // do properly. Any exceptions that can legally
+ // happen during frame re-initialization including
+ // StackOverflowException during innocent looking
+ // System.arraycopy may leave the current frame
+ // data corrupted leading to undefined behaviour
+ // in the catch code bellow that unwinds JS stack
+ // on exceptions. Then there is issue about frame release
+ // end exceptions there.
+ // To avoid frame allocation a released frame
+ // can be cached for re-use which would also benefit
+ // non-tail calls but it is not clear that this caching
+ // would gain in performance due to potentially
+ // bad interaction with GC.
+ callParentFrame = frame.parentFrame;
+ // Release the current frame. See Bug #344501 to see why
+ // it is being done here.
+ exitFrame(cx, frame, null);
+ }
+ initFrame(cx, calleeScope, funThisObj, stack, sDbl,
+ stackTop + 2, indexReg, ifun, callParentFrame,
+ calleeFrame);
+ if (op != Icode_TAIL_CALL) {
+ frame.savedStackTop = stackTop;
+ frame.savedCallOp = op;
+ }
+ frame = calleeFrame;
+ continue StateLoop;
+ }
+ }
+
+ if (fun instanceof NativeContinuation) {
+ // Jump to the captured continuation
+ ContinuationJump cjump;
+ cjump = new ContinuationJump((NativeContinuation)fun, frame);
+
+ // continuation result is the first argument if any
+ // of continuation call
+ if (indexReg == 0) {
+ cjump.result = undefined;
+ } else {
+ cjump.result = stack[stackTop + 2];
+ cjump.resultDbl = sDbl[stackTop + 2];
+ }
+
+ // Start the real unwind job
+ throwable = cjump;
+ break withoutExceptions;
+ }
+
+ if (fun instanceof IdFunctionObject) {
+ IdFunctionObject ifun = (IdFunctionObject)fun;
+ if (NativeContinuation.isContinuationConstructor(ifun)) {
+ frame.stack[stackTop] = captureContinuation(cx,
+ frame.parentFrame, false);
+ continue Loop;
+ }
+ // Bug 405654 -- make best effort to keep Function.apply and
+ // Function.call within this interpreter loop invocation
+ if (BaseFunction.isApplyOrCall(ifun)) {
+ Callable applyCallable = ScriptRuntime.getCallable(funThisObj);
+ if (applyCallable instanceof InterpretedFunction) {
+ InterpretedFunction iApplyCallable = (InterpretedFunction)applyCallable;
+ if (frame.fnOrScript.securityDomain == iApplyCallable.securityDomain) {
+ frame = initFrameForApplyOrCall(cx, frame, indexReg,
+ stack, sDbl, stackTop, op, calleeScope, ifun,
+ iApplyCallable);
+ continue StateLoop;
+ }
+ }
+ }
+ }
+
+ // Bug 447697 -- make best effort to keep __noSuchMethod__ within this
+ // interpreter loop invocation
+ if (fun instanceof NoSuchMethodShim) {
+ // get the shim and the actual method
+ NoSuchMethodShim noSuchMethodShim = (NoSuchMethodShim) fun;
+ Callable noSuchMethodMethod = noSuchMethodShim.noSuchMethodMethod;
+ // if the method is in fact an InterpretedFunction
+ if (noSuchMethodMethod instanceof InterpretedFunction) {
+ InterpretedFunction ifun = (InterpretedFunction) noSuchMethodMethod;
+ if (frame.fnOrScript.securityDomain == ifun.securityDomain) {
+ frame = initFrameForNoSuchMethod(cx, frame, indexReg, stack, sDbl,
+ stackTop, op, funThisObj, calleeScope,
+ noSuchMethodShim, ifun);
+ continue StateLoop;
+ }
+ }
+ }
+
+ cx.lastInterpreterFrame = frame;
+ frame.savedCallOp = op;
+ frame.savedStackTop = stackTop;
+ stack[stackTop] = fun.call(cx, calleeScope, funThisObj,
+ getArgsArray(stack, sDbl, stackTop + 2, indexReg));
+ cx.lastInterpreterFrame = null;
+
+ continue Loop;
+ }
+ case Token.NEW : {
+ if (instructionCounting) {
+ cx.instructionCount += INVOCATION_COST;
+ }
+ // stack change: function arg0 .. argN -> newResult
+ // indexReg: number of arguments
+ stackTop -= indexReg;
+
+ Object lhs = stack[stackTop];
+ if (lhs instanceof InterpretedFunction) {
+ InterpretedFunction f = (InterpretedFunction)lhs;
+ if (frame.fnOrScript.securityDomain == f.securityDomain) {
+ Scriptable newInstance = f.createObject(cx, frame.scope);
+ CallFrame calleeFrame = new CallFrame();
+ initFrame(cx, frame.scope, newInstance, stack, sDbl,
+ stackTop + 1, indexReg, f, frame,
+ calleeFrame);
+
+ stack[stackTop] = newInstance;
+ frame.savedStackTop = stackTop;
+ frame.savedCallOp = op;
+ frame = calleeFrame;
+ continue StateLoop;
+ }
+ }
+ if (!(lhs instanceof Function)) {
+ if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ throw ScriptRuntime.notFunctionError(lhs);
+ }
+ Function fun = (Function)lhs;
+
+ if (fun instanceof IdFunctionObject) {
+ IdFunctionObject ifun = (IdFunctionObject)fun;
+ if (NativeContinuation.isContinuationConstructor(ifun)) {
+ frame.stack[stackTop] =
+ captureContinuation(cx, frame.parentFrame, false);
+ continue Loop;
+ }
+ }
+
+ Object[] outArgs = getArgsArray(stack, sDbl, stackTop + 1, indexReg);
+ stack[stackTop] = fun.construct(cx, frame.scope, outArgs);
+ continue Loop;
+ }
+ case Token.TYPEOF : {
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.typeof(lhs);
+ continue Loop;
+ }
+ case Icode_TYPEOFNAME :
+ stack[++stackTop] = ScriptRuntime.typeofName(frame.scope, stringReg);
+ continue Loop;
+ case Token.STRING :
+ stack[++stackTop] = stringReg;
+ continue Loop;
+ case Icode_SHORTNUMBER :
+ ++stackTop;
+ stack[stackTop] = DBL_MRK;
+ sDbl[stackTop] = getShort(iCode, frame.pc);
+ frame.pc += 2;
+ continue Loop;
+ case Icode_INTNUMBER :
+ ++stackTop;
+ stack[stackTop] = DBL_MRK;
+ sDbl[stackTop] = getInt(iCode, frame.pc);
+ frame.pc += 4;
+ continue Loop;
+ case Token.NUMBER :
+ ++stackTop;
+ stack[stackTop] = DBL_MRK;
+ sDbl[stackTop] = frame.idata.itsDoubleTable[indexReg];
+ continue Loop;
+ case Token.NAME :
+ stack[++stackTop] = ScriptRuntime.name(cx, frame.scope, stringReg);
+ continue Loop;
+ case Icode_NAME_INC_DEC :
+ stack[++stackTop] = ScriptRuntime.nameIncrDecr(frame.scope, stringReg,
+ cx, iCode[frame.pc]);
+ ++frame.pc;
+ continue Loop;
+ case Icode_SETCONSTVAR1:
+ indexReg = iCode[frame.pc++];
+ // fallthrough
+ case Token.SETCONSTVAR :
+ if (!frame.useActivation) {
+ if ((varAttributes[indexReg] & ScriptableObject.READONLY) == 0) {
+ throw Context.reportRuntimeError1("msg.var.redecl",
+ frame.idata.argNames[indexReg]);
+ }
+ if ((varAttributes[indexReg] & ScriptableObject.UNINITIALIZED_CONST)
+ != 0)
+ {
+ vars[indexReg] = stack[stackTop];
+ varAttributes[indexReg] &= ~ScriptableObject.UNINITIALIZED_CONST;
+ varDbls[indexReg] = sDbl[stackTop];
+ }
+ } else {
+ Object val = stack[stackTop];
+ if (val == DBL_MRK) val = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stringReg = frame.idata.argNames[indexReg];
+ if (frame.scope instanceof ConstProperties) {
+ ConstProperties cp = (ConstProperties)frame.scope;
+ cp.putConst(stringReg, frame.scope, val);
+ } else
+ throw Kit.codeBug();
+ }
+ continue Loop;
+ case Icode_SETVAR1:
+ indexReg = iCode[frame.pc++];
+ // fallthrough
+ case Token.SETVAR :
+ if (!frame.useActivation) {
+ if ((varAttributes[indexReg] & ScriptableObject.READONLY) == 0) {
+ vars[indexReg] = stack[stackTop];
+ varDbls[indexReg] = sDbl[stackTop];
+ }
+ } else {
+ Object val = stack[stackTop];
+ if (val == DBL_MRK) val = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stringReg = frame.idata.argNames[indexReg];
+ frame.scope.put(stringReg, frame.scope, val);
+ }
+ continue Loop;
+ case Icode_GETVAR1:
+ indexReg = iCode[frame.pc++];
+ // fallthrough
+ case Token.GETVAR :
+ ++stackTop;
+ if (!frame.useActivation) {
+ stack[stackTop] = vars[indexReg];
+ sDbl[stackTop] = varDbls[indexReg];
+ } else {
+ stringReg = frame.idata.argNames[indexReg];
+ stack[stackTop] = frame.scope.get(stringReg, frame.scope);
+ }
+ continue Loop;
+ case Icode_VAR_INC_DEC : {
+ // indexReg : varindex
+ ++stackTop;
+ int incrDecrMask = iCode[frame.pc];
+ if (!frame.useActivation) {
+ stack[stackTop] = DBL_MRK;
+ Object varValue = vars[indexReg];
+ double d;
+ if (varValue == DBL_MRK) {
+ d = varDbls[indexReg];
+ } else {
+ d = ScriptRuntime.toNumber(varValue);
+ vars[indexReg] = DBL_MRK;
+ }
+ double d2 = ((incrDecrMask & Node.DECR_FLAG) == 0)
+ ? d + 1.0 : d - 1.0;
+ varDbls[indexReg] = d2;
+ sDbl[stackTop] = ((incrDecrMask & Node.POST_FLAG) == 0) ? d2 : d;
+ } else {
+ String varName = frame.idata.argNames[indexReg];
+ stack[stackTop] = ScriptRuntime.nameIncrDecr(frame.scope, varName,
+ cx, incrDecrMask);
+ }
+ ++frame.pc;
+ continue Loop;
+ }
+ case Icode_ZERO :
+ ++stackTop;
+ stack[stackTop] = DBL_MRK;
+ sDbl[stackTop] = 0;
+ continue Loop;
+ case Icode_ONE :
+ ++stackTop;
+ stack[stackTop] = DBL_MRK;
+ sDbl[stackTop] = 1;
+ continue Loop;
+ case Token.NULL :
+ stack[++stackTop] = null;
+ continue Loop;
+ case Token.THIS :
+ stack[++stackTop] = frame.thisObj;
+ continue Loop;
+ case Token.THISFN :
+ stack[++stackTop] = frame.fnOrScript;
+ continue Loop;
+ case Token.FALSE :
+ stack[++stackTop] = Boolean.FALSE;
+ continue Loop;
+ case Token.TRUE :
+ stack[++stackTop] = Boolean.TRUE;
+ continue Loop;
+ case Icode_UNDEF :
+ stack[++stackTop] = undefined;
+ continue Loop;
+ case Token.ENTERWITH : {
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ frame.scope = ScriptRuntime.enterWith(lhs, cx, frame.scope);
+ continue Loop;
+ }
+ case Token.LEAVEWITH :
+ frame.scope = ScriptRuntime.leaveWith(frame.scope);
+ continue Loop;
+ case Token.CATCH_SCOPE : {
+ // stack top: exception object
+ // stringReg: name of exception variable
+ // indexReg: local for exception scope
+ --stackTop;
+ indexReg += frame.localShift;
+
+ boolean afterFirstScope = (frame.idata.itsICode[frame.pc] != 0);
+ Throwable caughtException = (Throwable)stack[stackTop + 1];
+ Scriptable lastCatchScope;
+ if (!afterFirstScope) {
+ lastCatchScope = null;
+ } else {
+ lastCatchScope = (Scriptable)stack[indexReg];
+ }
+ stack[indexReg] = ScriptRuntime.newCatchScope(caughtException,
+ lastCatchScope, stringReg,
+ cx, frame.scope);
+ ++frame.pc;
+ continue Loop;
+ }
+ case Token.ENUM_INIT_KEYS :
+ case Token.ENUM_INIT_VALUES :
+ case Token.ENUM_INIT_ARRAY : {
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ indexReg += frame.localShift;
+ int enumType = op == Token.ENUM_INIT_KEYS
+ ? ScriptRuntime.ENUMERATE_KEYS :
+ op == Token.ENUM_INIT_VALUES
+ ? ScriptRuntime.ENUMERATE_VALUES :
+ ScriptRuntime.ENUMERATE_ARRAY;
+ stack[indexReg] = ScriptRuntime.enumInit(lhs, cx, enumType);
+ continue Loop;
+ }
+ case Token.ENUM_NEXT :
+ case Token.ENUM_ID : {
+ indexReg += frame.localShift;
+ Object val = stack[indexReg];
+ ++stackTop;
+ stack[stackTop] = (op == Token.ENUM_NEXT)
+ ? (Object)ScriptRuntime.enumNext(val)
+ : (Object)ScriptRuntime.enumId(val, cx);
+ continue Loop;
+ }
+ case Token.REF_SPECIAL : {
+ //stringReg: name of special property
+ Object obj = stack[stackTop];
+ if (obj == DBL_MRK) obj = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.specialRef(obj, stringReg, cx);
+ continue Loop;
+ }
+ case Token.REF_MEMBER: {
+ //indexReg: flags
+ Object elem = stack[stackTop];
+ if (elem == DBL_MRK) elem = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ Object obj = stack[stackTop];
+ if (obj == DBL_MRK) obj = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.memberRef(obj, elem, cx, indexReg);
+ continue Loop;
+ }
+ case Token.REF_NS_MEMBER: {
+ //indexReg: flags
+ Object elem = stack[stackTop];
+ if (elem == DBL_MRK) elem = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ Object ns = stack[stackTop];
+ if (ns == DBL_MRK) ns = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ Object obj = stack[stackTop];
+ if (obj == DBL_MRK) obj = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.memberRef(obj, ns, elem, cx, indexReg);
+ continue Loop;
+ }
+ case Token.REF_NAME: {
+ //indexReg: flags
+ Object name = stack[stackTop];
+ if (name == DBL_MRK) name = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.nameRef(name, cx, frame.scope,
+ indexReg);
+ continue Loop;
+ }
+ case Token.REF_NS_NAME: {
+ //indexReg: flags
+ Object name = stack[stackTop];
+ if (name == DBL_MRK) name = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ Object ns = stack[stackTop];
+ if (ns == DBL_MRK) ns = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.nameRef(ns, name, cx, frame.scope,
+ indexReg);
+ continue Loop;
+ }
+ case Icode_SCOPE_LOAD :
+ indexReg += frame.localShift;
+ frame.scope = (Scriptable)stack[indexReg];
+ continue Loop;
+ case Icode_SCOPE_SAVE :
+ indexReg += frame.localShift;
+ stack[indexReg] = frame.scope;
+ continue Loop;
+ case Icode_CLOSURE_EXPR :
+ stack[++stackTop] = InterpretedFunction.createFunction(cx, frame.scope,
+ frame.fnOrScript,
+ indexReg);
+ continue Loop;
+ case Icode_CLOSURE_STMT :
+ initFunction(cx, frame.scope, frame.fnOrScript, indexReg);
+ continue Loop;
+ case Token.REGEXP :
+ stack[++stackTop] = frame.scriptRegExps[indexReg];
+ continue Loop;
+ case Icode_LITERAL_NEW :
+ // indexReg: number of values in the literal
+ ++stackTop;
+ stack[stackTop] = new int[indexReg];
+ ++stackTop;
+ stack[stackTop] = new Object[indexReg];
+ sDbl[stackTop] = 0;
+ continue Loop;
+ case Icode_LITERAL_SET : {
+ Object value = stack[stackTop];
+ if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ int i = (int)sDbl[stackTop];
+ ((Object[])stack[stackTop])[i] = value;
+ sDbl[stackTop] = i + 1;
+ continue Loop;
+ }
+ case Icode_LITERAL_GETTER : {
+ Object value = stack[stackTop];
+ --stackTop;
+ int i = (int)sDbl[stackTop];
+ ((Object[])stack[stackTop])[i] = value;
+ ((int[])stack[stackTop - 1])[i] = -1;
+ sDbl[stackTop] = i + 1;
+ continue Loop;
+ }
+ case Icode_LITERAL_SETTER : {
+ Object value = stack[stackTop];
+ --stackTop;
+ int i = (int)sDbl[stackTop];
+ ((Object[])stack[stackTop])[i] = value;
+ ((int[])stack[stackTop - 1])[i] = +1;
+ sDbl[stackTop] = i + 1;
+ continue Loop;
+ }
+ case Token.ARRAYLIT :
+ case Icode_SPARE_ARRAYLIT :
+ case Token.OBJECTLIT : {
+ Object[] data = (Object[])stack[stackTop];
+ --stackTop;
+ int[] getterSetters = (int[])stack[stackTop];
+ Object val;
+ if (op == Token.OBJECTLIT) {
+ Object[] ids = (Object[])frame.idata.literalIds[indexReg];
+ val = ScriptRuntime.newObjectLiteral(ids, data, getterSetters, cx,
+ frame.scope);
+ } else {
+ int[] skipIndexces = null;
+ if (op == Icode_SPARE_ARRAYLIT) {
+ skipIndexces = (int[])frame.idata.literalIds[indexReg];
+ }
+ val = ScriptRuntime.newArrayLiteral(data, skipIndexces, cx,
+ frame.scope);
+ }
+ stack[stackTop] = val;
+ continue Loop;
+ }
+ case Icode_ENTERDQ : {
+ Object lhs = stack[stackTop];
+ if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ --stackTop;
+ frame.scope = ScriptRuntime.enterDotQuery(lhs, frame.scope);
+ continue Loop;
+ }
+ case Icode_LEAVEDQ : {
+ boolean valBln = stack_boolean(frame, stackTop);
+ Object x = ScriptRuntime.updateDotQuery(valBln, frame.scope);
+ if (x != null) {
+ stack[stackTop] = x;
+ frame.scope = ScriptRuntime.leaveDotQuery(frame.scope);
+ frame.pc += 2;
+ continue Loop;
+ }
+ // reset stack and PC to code after ENTERDQ
+ --stackTop;
+ break jumplessRun;
+ }
+ case Token.DEFAULTNAMESPACE : {
+ Object value = stack[stackTop];
+ if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]);
+ stack[stackTop] = ScriptRuntime.setDefaultNamespace(value, cx);
+ continue Loop;
+ }
+ case Token.ESCXMLATTR : {
+ Object value = stack[stackTop];
+ if (value != DBL_MRK) {
+ stack[stackTop] = ScriptRuntime.escapeAttributeValue(value, cx);
+ }
+ continue Loop;
+ }
+ case Token.ESCXMLTEXT : {
+ Object value = stack[stackTop];
+ if (value != DBL_MRK) {
+ stack[stackTop] = ScriptRuntime.escapeTextValue(value, cx);
+ }
+ continue Loop;
+ }
+ case Icode_DEBUGGER:
+ if (frame.debuggerFrame != null) {
+ frame.debuggerFrame.onDebuggerStatement(cx);
+ }
+ continue Loop;
+ case Icode_LINE :
+ frame.pcSourceLineStart = frame.pc;
+ if (frame.debuggerFrame != null) {
+ int line = getIndex(iCode, frame.pc);
+ frame.debuggerFrame.onLineChange(cx, line);
+ }
+ frame.pc += 2;
+ continue Loop;
+ case Icode_REG_IND_C0:
+ indexReg = 0;
+ continue Loop;
+ case Icode_REG_IND_C1:
+ indexReg = 1;
+ continue Loop;
+ case Icode_REG_IND_C2:
+ indexReg = 2;
+ continue Loop;
+ case Icode_REG_IND_C3:
+ indexReg = 3;
+ continue Loop;
+ case Icode_REG_IND_C4:
+ indexReg = 4;
+ continue Loop;
+ case Icode_REG_IND_C5:
+ indexReg = 5;
+ continue Loop;
+ case Icode_REG_IND1:
+ indexReg = 0xFF & iCode[frame.pc];
+ ++frame.pc;
+ continue Loop;
+ case Icode_REG_IND2:
+ indexReg = getIndex(iCode, frame.pc);
+ frame.pc += 2;
+ continue Loop;
+ case Icode_REG_IND4:
+ indexReg = getInt(iCode, frame.pc);
+ frame.pc += 4;
+ continue Loop;
+ case Icode_REG_STR_C0:
+ stringReg = strings[0];
+ continue Loop;
+ case Icode_REG_STR_C1:
+ stringReg = strings[1];
+ continue Loop;
+ case Icode_REG_STR_C2:
+ stringReg = strings[2];
+ continue Loop;
+ case Icode_REG_STR_C3:
+ stringReg = strings[3];
+ continue Loop;
+ case Icode_REG_STR1:
+ stringReg = strings[0xFF & iCode[frame.pc]];
+ ++frame.pc;
+ continue Loop;
+ case Icode_REG_STR2:
+ stringReg = strings[getIndex(iCode, frame.pc)];
+ frame.pc += 2;
+ continue Loop;
+ case Icode_REG_STR4:
+ stringReg = strings[getInt(iCode, frame.pc)];
+ frame.pc += 4;
+ continue Loop;
+ default :
+ dumpICode(frame.idata);
+ throw new RuntimeException(
+ "Unknown icode : "+op+" @ pc : "+(frame.pc-1));
+} // end of interpreter switch
+
+ } // end of jumplessRun label block
+
+ // This should be reachable only for jump implementation
+ // when pc points to encoded target offset
+ if (instructionCounting) {
+ addInstructionCount(cx, frame, 2);
+ }
+ int offset = getShort(iCode, frame.pc);
+ if (offset != 0) {
+ // -1 accounts for pc pointing to jump opcode + 1
+ frame.pc += offset - 1;
+ } else {
+ frame.pc = frame.idata.longJumps.
+ getExistingInt(frame.pc);
+ }
+ if (instructionCounting) {
+ frame.pcPrevBranch = frame.pc;
+ }
+ continue Loop;
+
+ } // end of Loop: for
+
+ exitFrame(cx, frame, null);
+ interpreterResult = frame.result;
+ interpreterResultDbl = frame.resultDbl;
+ if (frame.parentFrame != null) {
+ frame = frame.parentFrame;
+ if (frame.frozen) {
+ frame = frame.cloneFrozen();
+ }
+ setCallResult(
+ frame, interpreterResult, interpreterResultDbl);
+ interpreterResult = null; // Help GC
+ continue StateLoop;
+ }
+ break StateLoop;
+
+ } // end of interpreter withoutExceptions: try
+ catch (Throwable ex) {
+ if (throwable != null) {
+ // This is serious bug and it is better to track it ASAP
+ ex.printStackTrace(System.err);
+ throw new IllegalStateException();
+ }
+ throwable = ex;
+ }
+
+ // This should be reachable only after above catch or from
+ // finally when it needs to propagate exception or from
+ // explicit throw
+ if (throwable == null) Kit.codeBug();
+
+ // Exception type
+ final int EX_CATCH_STATE = 2; // Can execute JS catch
+ final int EX_FINALLY_STATE = 1; // Can execute JS finally
+ final int EX_NO_JS_STATE = 0; // Terminate JS execution
+
+ int exState;
+ ContinuationJump cjump = null;
+
+ if (generatorState != null &&
+ generatorState.operation == NativeGenerator.GENERATOR_CLOSE &&
+ throwable == generatorState.value)
+ {
+ exState = EX_FINALLY_STATE;
+ } else if (throwable instanceof JavaScriptException) {
+ exState = EX_CATCH_STATE;
+ } else if (throwable instanceof EcmaError) {
+ // an offical ECMA error object,
+ exState = EX_CATCH_STATE;
+ } else if (throwable instanceof EvaluatorException) {
+ exState = EX_CATCH_STATE;
+ } else if (throwable instanceof RuntimeException) {
+ exState = cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)
+ ? EX_CATCH_STATE
+ : EX_FINALLY_STATE;
+ } else if (throwable instanceof Error) {
+ exState = cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)
+ ? EX_CATCH_STATE
+ : EX_NO_JS_STATE;
+ } else if (throwable instanceof ContinuationJump) {
+ // It must be ContinuationJump
+ exState = EX_FINALLY_STATE;
+ cjump = (ContinuationJump)throwable;
+ } else {
+ exState = cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)
+ ? EX_CATCH_STATE
+ : EX_FINALLY_STATE;
+ }
+
+ if (instructionCounting) {
+ try {
+ addInstructionCount(cx, frame, EXCEPTION_COST);
+ } catch (RuntimeException ex) {
+ throwable = ex;
+ exState = EX_FINALLY_STATE;
+ } catch (Error ex) {
+ // Error from instruction counting
+ // => unconditionally terminate JS
+ throwable = ex;
+ cjump = null;
+ exState = EX_NO_JS_STATE;
+ }
+ }
+ if (frame.debuggerFrame != null
+ && throwable instanceof RuntimeException)
+ {
+ // Call debugger only for RuntimeException
+ RuntimeException rex = (RuntimeException)throwable;
+ try {
+ frame.debuggerFrame.onExceptionThrown(cx, rex);
+ } catch (Throwable ex) {
+ // Any exception from debugger
+ // => unconditionally terminate JS
+ throwable = ex;
+ cjump = null;
+ exState = EX_NO_JS_STATE;
+ }
+ }
+
+ for (;;) {
+ if (exState != EX_NO_JS_STATE) {
+ boolean onlyFinally = (exState != EX_CATCH_STATE);
+ indexReg = getExceptionHandler(frame, onlyFinally);
+ if (indexReg >= 0) {
+ // We caught an exception, restart the loop
+ // with exception pending the processing at the loop
+ // start
+ continue StateLoop;
+ }
+ }
+ // No allowed exception handlers in this frame, unwind
+ // to parent and try to look there
+
+ exitFrame(cx, frame, throwable);
+
+ frame = frame.parentFrame;
+ if (frame == null) { break; }
+ if (cjump != null && cjump.branchFrame == frame) {
+ // Continuation branch point was hit,
+ // restart the state loop to reenter continuation
+ indexReg = -1;
+ continue StateLoop;
+ }
+ }
+
+ // No more frames, rethrow the exception or deal with continuation
+ if (cjump != null) {
+ if (cjump.branchFrame != null) {
+ // The above loop should locate the top frame
+ Kit.codeBug();
+ }
+ if (cjump.capturedFrame != null) {
+ // Restarting detached continuation
+ indexReg = -1;
+ continue StateLoop;
+ }
+ // Return continuation result to the caller
+ interpreterResult = cjump.result;
+ interpreterResultDbl = cjump.resultDbl;
+ throwable = null;
+ }
+ break StateLoop;
+
+ } // end of StateLoop: for(;;)
+
+ // Do cleanups/restorations before the final return or throw
+
+ if (cx.previousInterpreterInvocations != null
+ && cx.previousInterpreterInvocations.size() != 0)
+ {
+ cx.lastInterpreterFrame
+ = cx.previousInterpreterInvocations.pop();
+ } else {
+ // It was the last interpreter frame on the stack
+ cx.lastInterpreterFrame = null;
+ // Force GC of the value cx.previousInterpreterInvocations
+ cx.previousInterpreterInvocations = null;
+ }
+
+ if (throwable != null) {
+ if (throwable instanceof RuntimeException) {
+ throw (RuntimeException)throwable;
+ } else {
+ // Must be instance of Error or code bug
+ throw (Error)throwable;
+ }
+ }
+
+ return (interpreterResult != DBL_MRK)
+ ? interpreterResult
+ : ScriptRuntime.wrapNumber(interpreterResultDbl);
+ }
+
+ /**
+ * Call __noSuchMethod__.
+ */
+ private static CallFrame initFrameForNoSuchMethod(Context cx,
+ CallFrame frame, int indexReg, Object[] stack, double[] sDbl,
+ int stackTop, int op, Scriptable funThisObj, Scriptable calleeScope,
+ NoSuchMethodShim noSuchMethodShim, InterpretedFunction ifun)
+ {
+ // create an args array from the stack
+ Object[] argsArray = null;
+ // exactly like getArgsArray except that the first argument
+ // is the method name from the shim
+ int shift = stackTop + 2;
+ Object[] elements = new Object[indexReg];
+ for (int i=0; i < indexReg; ++i, ++shift) {
+ Object val = stack[shift];
+ if (val == UniqueTag.DOUBLE_MARK) {
+ val = ScriptRuntime.wrapNumber(sDbl[shift]);
+ }
+ elements[i] = val;
+ }
+ argsArray = new Object[2];
+ argsArray[0] = noSuchMethodShim.methodName;
+ argsArray[1] = cx.newArray(calleeScope, elements);
+
+ // exactly the same as if it's a regular InterpretedFunction
+ CallFrame callParentFrame = frame;
+ CallFrame calleeFrame = new CallFrame();
+ if (op == Icode_TAIL_CALL) {
+ callParentFrame = frame.parentFrame;
+ exitFrame(cx, frame, null);
+ }
+ // init the frame with the underlying method with the
+ // adjusted args array and shim's function
+ initFrame(cx, calleeScope, funThisObj, argsArray, null,
+ 0, 2, ifun, callParentFrame, calleeFrame);
+ if (op != Icode_TAIL_CALL) {
+ frame.savedStackTop = stackTop;
+ frame.savedCallOp = op;
+ }
+ return calleeFrame;
+ }
+
+ private static boolean shallowEquals(Object[] stack, double[] sDbl,
+ int stackTop)
+ {
+ Object rhs = stack[stackTop + 1];
+ Object lhs = stack[stackTop];
+ final Object DBL_MRK = UniqueTag.DOUBLE_MARK;
+ double rdbl, ldbl;
+ if (rhs == DBL_MRK) {
+ rdbl = sDbl[stackTop + 1];
+ if (lhs == DBL_MRK) {
+ ldbl = sDbl[stackTop];
+ } else if (lhs instanceof Number) {
+ ldbl = ((Number)lhs).doubleValue();
+ } else {
+ return false;
+ }
+ } else if (lhs == DBL_MRK) {
+ ldbl = sDbl[stackTop];
+ if (rhs == DBL_MRK) {
+ rdbl = sDbl[stackTop + 1];
+ } else if (rhs instanceof Number) {
+ rdbl = ((Number)rhs).doubleValue();
+ } else {
+ return false;
+ }
+ } else {
+ return ScriptRuntime.shallowEq(lhs, rhs);
+ }
+ return (ldbl == rdbl);
+ }
+
+ private static CallFrame processThrowable(Context cx, Object throwable,
+ CallFrame frame, int indexReg,
+ boolean instructionCounting)
+ {
+ // Recovering from exception, indexReg contains
+ // the index of handler
+
+ if (indexReg >= 0) {
+ // Normal exception handler, transfer
+ // control appropriately
+
+ if (frame.frozen) {
+ // XXX Deal with exceptios!!!
+ frame = frame.cloneFrozen();
+ }
+
+ int[] table = frame.idata.itsExceptionTable;
+
+ frame.pc = table[indexReg + EXCEPTION_HANDLER_SLOT];
+ if (instructionCounting) {
+ frame.pcPrevBranch = frame.pc;
+ }
+
+ frame.savedStackTop = frame.emptyStackTop;
+ int scopeLocal = frame.localShift
+ + table[indexReg
+ + EXCEPTION_SCOPE_SLOT];
+ int exLocal = frame.localShift
+ + table[indexReg
+ + EXCEPTION_LOCAL_SLOT];
+ frame.scope = (Scriptable)frame.stack[scopeLocal];
+ frame.stack[exLocal] = throwable;
+
+ throwable = null;
+ } else {
+ // Continuation restoration
+ ContinuationJump cjump = (ContinuationJump)throwable;
+
+ // Clear throwable to indicate that exceptions are OK
+ throwable = null;
+
+ if (cjump.branchFrame != frame) Kit.codeBug();
+
+ // Check that we have at least one frozen frame
+ // in the case of detached continuation restoration:
+ // unwind code ensure that
+ if (cjump.capturedFrame == null) Kit.codeBug();
+
+ // Need to rewind branchFrame, capturedFrame
+ // and all frames in between
+ int rewindCount = cjump.capturedFrame.frameIndex + 1;
+ if (cjump.branchFrame != null) {
+ rewindCount -= cjump.branchFrame.frameIndex;
+ }
+
+ int enterCount = 0;
+ CallFrame[] enterFrames = null;
+
+ CallFrame x = cjump.capturedFrame;
+ for (int i = 0; i != rewindCount; ++i) {
+ if (!x.frozen) Kit.codeBug();
+ if (isFrameEnterExitRequired(x)) {
+ if (enterFrames == null) {
+ // Allocate enough space to store the rest
+ // of rewind frames in case all of them
+ // would require to enter
+ enterFrames = new CallFrame[rewindCount
+ - i];
+ }
+ enterFrames[enterCount] = x;
+ ++enterCount;
+ }
+ x = x.parentFrame;
+ }
+
+ while (enterCount != 0) {
+ // execute enter: walk enterFrames in the reverse
+ // order since they were stored starting from
+ // the capturedFrame, not branchFrame
+ --enterCount;
+ x = enterFrames[enterCount];
+ enterFrame(cx, x, ScriptRuntime.emptyArgs, true);
+ }
+
+ // Continuation jump is almost done: capturedFrame
+ // points to the call to the function that captured
+ // continuation, so clone capturedFrame and
+ // emulate return that function with the suplied result
+ frame = cjump.capturedFrame.cloneFrozen();
+ setCallResult(frame, cjump.result, cjump.resultDbl);
+ // restart the execution
+ }
+ frame.throwable = throwable;
+ return frame;
+ }
+
+ private static Object freezeGenerator(Context cx, CallFrame frame,
+ int stackTop,
+ GeneratorState generatorState)
+ {
+ if (generatorState.operation == NativeGenerator.GENERATOR_CLOSE) {
+ // Error: no yields when generator is closing
+ throw ScriptRuntime.typeError0("msg.yield.closing");
+ }
+ // return to our caller (which should be a method of NativeGenerator)
+ frame.frozen = true;
+ frame.result = frame.stack[stackTop];
+ frame.resultDbl = frame.sDbl[stackTop];
+ frame.savedStackTop = stackTop;
+ frame.pc--; // we want to come back here when we resume
+ ScriptRuntime.exitActivationFunction(cx);
+ return (frame.result != UniqueTag.DOUBLE_MARK)
+ ? frame.result
+ : ScriptRuntime.wrapNumber(frame.resultDbl);
+ }
+
+ private static Object thawGenerator(CallFrame frame, int stackTop,
+ GeneratorState generatorState, int op)
+ {
+ // we are resuming execution
+ frame.frozen = false;
+ int sourceLine = getIndex(frame.idata.itsICode, frame.pc);
+ frame.pc += 2; // skip line number data
+ if (generatorState.operation == NativeGenerator.GENERATOR_THROW) {
+ // processing a call to <generator>.throw(exception): must
+ // act as if exception was thrown from resumption point
+ return new JavaScriptException(generatorState.value,
+ frame.idata.itsSourceFile,
+ sourceLine);
+ }
+ if (generatorState.operation == NativeGenerator.GENERATOR_CLOSE) {
+ return generatorState.value;
+ }
+ if (generatorState.operation != NativeGenerator.GENERATOR_SEND)
+ throw Kit.codeBug();
+ if (op == Token.YIELD)
+ frame.stack[stackTop] = generatorState.value;
+ return Scriptable.NOT_FOUND;
+ }
+
+ private static CallFrame initFrameForApplyOrCall(Context cx, CallFrame frame,
+ int indexReg, Object[] stack, double[] sDbl, int stackTop, int op,
+ Scriptable calleeScope, IdFunctionObject ifun,
+ InterpretedFunction iApplyCallable)
+ {
+ Scriptable applyThis;
+ if (indexReg != 0) {
+ Object obj = stack[stackTop + 2];
+ if (obj == UniqueTag.DOUBLE_MARK)
+ obj = ScriptRuntime.wrapNumber(sDbl[stackTop + 2]);
+ applyThis = ScriptRuntime.toObjectOrNull(cx, obj);
+ }
+ else {
+ applyThis = null;
+ }
+ if (applyThis == null) {
+ // This covers the case of args[0] == (null|undefined) as well.
+ applyThis = ScriptRuntime.getTopCallScope(cx);
+ }
+ if(op == Icode_TAIL_CALL) {
+ exitFrame(cx, frame, null);
+ frame = frame.parentFrame;
+ }
+ else {
+ frame.savedStackTop = stackTop;
+ frame.savedCallOp = op;
+ }
+ CallFrame calleeFrame = new CallFrame();
+ if(BaseFunction.isApply(ifun)) {
+ Object[] callArgs = indexReg < 2 ? ScriptRuntime.emptyArgs :
+ ScriptRuntime.getApplyArguments(cx, stack[stackTop + 3]);
+ initFrame(cx, calleeScope, applyThis, callArgs, null, 0,
+ callArgs.length, iApplyCallable, frame, calleeFrame);
+ }
+ else {
+ // Shift args left
+ for(int i = 1; i < indexReg; ++i) {
+ stack[stackTop + 1 + i] = stack[stackTop + 2 + i];
+ sDbl[stackTop + 1 + i] = sDbl[stackTop + 2 + i];
+ }
+ int argCount = indexReg < 2 ? 0 : indexReg - 1;
+ initFrame(cx, calleeScope, applyThis, stack, sDbl, stackTop + 2,
+ argCount, iApplyCallable, frame, calleeFrame);
+ }
+
+ frame = calleeFrame;
+ return frame;
+ }
+
+ private static void initFrame(Context cx, Scriptable callerScope,
+ Scriptable thisObj,
+ Object[] args, double[] argsDbl,
+ int argShift, int argCount,
+ InterpretedFunction fnOrScript,
+ CallFrame parentFrame, CallFrame frame)
+ {
+ InterpreterData idata = fnOrScript.idata;
+
+ boolean useActivation = idata.itsNeedsActivation;
+ DebugFrame debuggerFrame = null;
+ if (cx.debugger != null) {
+ debuggerFrame = cx.debugger.getFrame(cx, idata);
+ if (debuggerFrame != null) {
+ useActivation = true;
+ }
+ }
+
+ if (useActivation) {
+ // Copy args to new array to pass to enterActivationFunction
+ // or debuggerFrame.onEnter
+ if (argsDbl != null) {
+ args = getArgsArray(args, argsDbl, argShift, argCount);
+ }
+ argShift = 0;
+ argsDbl = null;
+ }
+
+ Scriptable scope;
+ if (idata.itsFunctionType != 0) {
+ if (!idata.useDynamicScope) {
+ scope = fnOrScript.getParentScope();
+ } else {
+ scope = callerScope;
+ }
+
+ if (useActivation) {
+ scope = ScriptRuntime.createFunctionActivation(
+ fnOrScript, scope, args);
+ }
+ } else {
+ scope = callerScope;
+ ScriptRuntime.initScript(fnOrScript, thisObj, cx, scope,
+ fnOrScript.idata.evalScriptFlag);
+ }
+
+ if (idata.itsNestedFunctions != null) {
+ if (idata.itsFunctionType != 0 && !idata.itsNeedsActivation)
+ Kit.codeBug();
+ for (int i = 0; i < idata.itsNestedFunctions.length; i++) {
+ InterpreterData fdata = idata.itsNestedFunctions[i];
+ if (fdata.itsFunctionType == FunctionNode.FUNCTION_STATEMENT) {
+ initFunction(cx, scope, fnOrScript, i);
+ }
+ }
+ }
+
+ Scriptable[] scriptRegExps = null;
+ if (idata.itsRegExpLiterals != null) {
+ // Wrapped regexps for functions are stored in
+ // InterpretedFunction
+ // but for script which should not contain references to scope
+ // the regexps re-wrapped during each script execution
+ if (idata.itsFunctionType != 0) {
+ scriptRegExps = fnOrScript.functionRegExps;
+ } else {
+ scriptRegExps = fnOrScript.createRegExpWraps(cx, scope);
+ }
+ }
+
+ // Initialize args, vars, locals and stack
+
+ int emptyStackTop = idata.itsMaxVars + idata.itsMaxLocals - 1;
+ int maxFrameArray = idata.itsMaxFrameArray;
+ if (maxFrameArray != emptyStackTop + idata.itsMaxStack + 1)
+ Kit.codeBug();
+
+ Object[] stack;
+ int[] stackAttributes;
+ double[] sDbl;
+ boolean stackReuse;
+ if (frame.stack != null && maxFrameArray <= frame.stack.length) {
+ // Reuse stacks from old frame
+ stackReuse = true;
+ stack = frame.stack;
+ stackAttributes = frame.stackAttributes;
+ sDbl = frame.sDbl;
+ } else {
+ stackReuse = false;
+ stack = new Object[maxFrameArray];
+ stackAttributes = new int[maxFrameArray];
+ sDbl = new double[maxFrameArray];
+ }
+
+ int varCount = idata.getParamAndVarCount();
+ for (int i = 0; i < varCount; i++) {
+ if (idata.getParamOrVarConst(i))
+ stackAttributes[i] = ScriptableObject.CONST;
+ }
+ int definedArgs = idata.argCount;
+ if (definedArgs > argCount) { definedArgs = argCount; }
+
+ // Fill the frame structure
+
+ frame.parentFrame = parentFrame;
+ frame.frameIndex = (parentFrame == null)
+ ? 0 : parentFrame.frameIndex + 1;
+ if(frame.frameIndex > cx.getMaximumInterpreterStackDepth())
+ {
+ throw Context.reportRuntimeError("Exceeded maximum stack depth");
+ }
+ frame.frozen = false;
+
+ frame.fnOrScript = fnOrScript;
+ frame.idata = idata;
+
+ frame.stack = stack;
+ frame.stackAttributes = stackAttributes;
+ frame.sDbl = sDbl;
+ frame.varSource = frame;
+ frame.localShift = idata.itsMaxVars;
+ frame.emptyStackTop = emptyStackTop;
+
+ frame.debuggerFrame = debuggerFrame;
+ frame.useActivation = useActivation;
+
+ frame.thisObj = thisObj;
+ frame.scriptRegExps = scriptRegExps;
+
+ // Initialize initial values of variables that change during
+ // interpretation.
+ frame.result = Undefined.instance;
+ frame.pc = 0;
+ frame.pcPrevBranch = 0;
+ frame.pcSourceLineStart = idata.firstLinePC;
+ frame.scope = scope;
+
+ frame.savedStackTop = emptyStackTop;
+ frame.savedCallOp = 0;
+
+ System.arraycopy(args, argShift, stack, 0, definedArgs);
+ if (argsDbl != null) {
+ System.arraycopy(argsDbl, argShift, sDbl, 0, definedArgs);
+ }
+ for (int i = definedArgs; i != idata.itsMaxVars; ++i) {
+ stack[i] = Undefined.instance;
+ }
+ if (stackReuse) {
+ // Clean the stack part and space beyond stack if any
+ // of the old array to allow to GC objects there
+ for (int i = emptyStackTop + 1; i != stack.length; ++i) {
+ stack[i] = null;
+ }
+ }
+
+ enterFrame(cx, frame, args, false);
+ }
+
+ private static boolean isFrameEnterExitRequired(CallFrame frame)
+ {
+ return frame.debuggerFrame != null || frame.idata.itsNeedsActivation;
+ }
+
+ private static void enterFrame(Context cx, CallFrame frame, Object[] args,
+ boolean continuationRestart)
+ {
+ boolean usesActivation = frame.idata.itsNeedsActivation;
+ boolean isDebugged = frame.debuggerFrame != null;
+ if(usesActivation || isDebugged) {
+ Scriptable scope = frame.scope;
+ if(scope == null) {
+ Kit.codeBug();
+ } else if (continuationRestart) {
+ // Walk the parent chain of frame.scope until a NativeCall is
+ // found. Normally, frame.scope is a NativeCall when called
+ // from initFrame() for a debugged or activatable function.
+ // However, when called from interpretLoop() as part of
+ // restarting a continuation, it can also be a NativeWith if
+ // the continuation was captured within a "with" or "catch"
+ // block ("catch" implicitly uses NativeWith to create a scope
+ // to expose the exception variable).
+ for(;;) {
+ if(scope instanceof NativeWith) {
+ scope = scope.getParentScope();
+ if (scope == null || (frame.parentFrame != null &&
+ frame.parentFrame.scope == scope))
+ {
+ // If we get here, we didn't find a NativeCall in
+ // the call chain before reaching parent frame's
+ // scope. This should not be possible.
+ Kit.codeBug();
+ break; // Never reached, but keeps the static analyzer happy about "scope" not being null 5 lines above.
+ }
+ }
+ else {
+ break;
+ }
+ }
+ }
+ if (isDebugged) {
+ frame.debuggerFrame.onEnter(cx, scope, frame.thisObj, args);
+ }
+ // Enter activation only when itsNeedsActivation true,
+ // since debugger should not interfere with activation
+ // chaining
+ if (usesActivation) {
+ ScriptRuntime.enterActivationFunction(cx, scope);
+ }
+ }
+ }
+
+ private static void exitFrame(Context cx, CallFrame frame,
+ Object throwable)
+ {
+ if (frame.idata.itsNeedsActivation) {
+ ScriptRuntime.exitActivationFunction(cx);
+ }
+
+ if (frame.debuggerFrame != null) {
+ try {
+ if (throwable instanceof Throwable) {
+ frame.debuggerFrame.onExit(cx, true, throwable);
+ } else {
+ Object result;
+ ContinuationJump cjump = (ContinuationJump)throwable;
+ if (cjump == null) {
+ result = frame.result;
+ } else {
+ result = cjump.result;
+ }
+ if (result == UniqueTag.DOUBLE_MARK) {
+ double resultDbl;
+ if (cjump == null) {
+ resultDbl = frame.resultDbl;
+ } else {
+ resultDbl = cjump.resultDbl;
+ }
+ result = ScriptRuntime.wrapNumber(resultDbl);
+ }
+ frame.debuggerFrame.onExit(cx, false, result);
+ }
+ } catch (Throwable ex) {
+ System.err.println(
+"RHINO USAGE WARNING: onExit terminated with exception");
+ ex.printStackTrace(System.err);
+ }
+ }
+ }
+
+ private static void setCallResult(CallFrame frame,
+ Object callResult,
+ double callResultDbl)
+ {
+ if (frame.savedCallOp == Token.CALL) {
+ frame.stack[frame.savedStackTop] = callResult;
+ frame.sDbl[frame.savedStackTop] = callResultDbl;
+ } else if (frame.savedCallOp == Token.NEW) {
+ // If construct returns scriptable,
+ // then it replaces on stack top saved original instance
+ // of the object.
+ if (callResult instanceof Scriptable) {
+ frame.stack[frame.savedStackTop] = callResult;
+ }
+ } else {
+ Kit.codeBug();
+ }
+ frame.savedCallOp = 0;
+ }
+
+ public static NativeContinuation captureContinuation(Context cx) {
+ if (cx.lastInterpreterFrame == null ||
+ !(cx.lastInterpreterFrame instanceof CallFrame))
+ {
+ throw new IllegalStateException("Interpreter frames not found");
+ }
+ return captureContinuation(cx, (CallFrame)cx.lastInterpreterFrame, true);
+ }
+
+ private static NativeContinuation captureContinuation(Context cx, CallFrame frame,
+ boolean requireContinuationsTopFrame)
+ {
+ NativeContinuation c = new NativeContinuation();
+ ScriptRuntime.setObjectProtoAndParent(
+ c, ScriptRuntime.getTopCallScope(cx));
+
+ // Make sure that all frames are frozen
+ CallFrame x = frame;
+ CallFrame outermost = frame;
+ while (x != null && !x.frozen) {
+ x.frozen = true;
+ // Allow to GC unused stack space
+ for (int i = x.savedStackTop + 1; i != x.stack.length; ++i) {
+ // Allow to GC unused stack space
+ x.stack[i] = null;
+ x.stackAttributes[i] = ScriptableObject.EMPTY;
+ }
+ if (x.savedCallOp == Token.CALL) {
+ // the call will always overwrite the stack top with the result
+ x.stack[x.savedStackTop] = null;
+ } else {
+ if (x.savedCallOp != Token.NEW) Kit.codeBug();
+ // the new operator uses stack top to store the constructed
+ // object so it shall not be cleared: see comments in
+ // setCallResult
+ }
+ outermost = x;
+ x = x.parentFrame;
+ }
+
+ if (requireContinuationsTopFrame) {
+ while (outermost.parentFrame != null)
+ outermost = outermost.parentFrame;
+
+ if (!outermost.isContinuationsTopFrame) {
+ throw new IllegalStateException("Cannot capture continuation " +
+ "from JavaScript code not called directly by " +
+ "executeScriptWithContinuations or " +
+ "callFunctionWithContinuations");
+ }
+ }
+
+ c.initImplementation(frame);
+ return c;
+ }
+
+ private static int stack_int32(CallFrame frame, int i)
+ {
+ Object x = frame.stack[i];
+ double value;
+ if (x == UniqueTag.DOUBLE_MARK) {
+ value = frame.sDbl[i];
+ } else {
+ value = ScriptRuntime.toNumber(x);
+ }
+ return ScriptRuntime.toInt32(value);
+ }
+
+ private static double stack_double(CallFrame frame, int i)
+ {
+ Object x = frame.stack[i];
+ if (x != UniqueTag.DOUBLE_MARK) {
+ return ScriptRuntime.toNumber(x);
+ } else {
+ return frame.sDbl[i];
+ }
+ }
+
+ private static boolean stack_boolean(CallFrame frame, int i)
+ {
+ Object x = frame.stack[i];
+ if (x == Boolean.TRUE) {
+ return true;
+ } else if (x == Boolean.FALSE) {
+ return false;
+ } else if (x == UniqueTag.DOUBLE_MARK) {
+ double d = frame.sDbl[i];
+ return d == d && d != 0.0;
+ } else if (x == null || x == Undefined.instance) {
+ return false;
+ } else if (x instanceof Number) {
+ double d = ((Number)x).doubleValue();
+ return (d == d && d != 0.0);
+ } else if (x instanceof Boolean) {
+ return ((Boolean)x).booleanValue();
+ } else {
+ return ScriptRuntime.toBoolean(x);
+ }
+ }
+
+ private static void do_add(Object[] stack, double[] sDbl, int stackTop,
+ Context cx)
+ {
+ Object rhs = stack[stackTop + 1];
+ Object lhs = stack[stackTop];
+ double d;
+ boolean leftRightOrder;
+ if (rhs == UniqueTag.DOUBLE_MARK) {
+ d = sDbl[stackTop + 1];
+ if (lhs == UniqueTag.DOUBLE_MARK) {
+ sDbl[stackTop] += d;
+ return;
+ }
+ leftRightOrder = true;
+ // fallthrough to object + number code
+ } else if (lhs == UniqueTag.DOUBLE_MARK) {
+ d = sDbl[stackTop];
+ lhs = rhs;
+ leftRightOrder = false;
+ // fallthrough to object + number code
+ } else {
+ if (lhs instanceof Scriptable || rhs instanceof Scriptable) {
+ stack[stackTop] = ScriptRuntime.add(lhs, rhs, cx);
+ } else if (lhs instanceof String) {
+ String lstr = (String)lhs;
+ String rstr = ScriptRuntime.toString(rhs);
+ stack[stackTop] = lstr.concat(rstr);
+ } else if (rhs instanceof String) {
+ String lstr = ScriptRuntime.toString(lhs);
+ String rstr = (String)rhs;
+ stack[stackTop] = lstr.concat(rstr);
+ } else {
+ double lDbl = (lhs instanceof Number)
+ ? ((Number)lhs).doubleValue() : ScriptRuntime.toNumber(lhs);
+ double rDbl = (rhs instanceof Number)
+ ? ((Number)rhs).doubleValue() : ScriptRuntime.toNumber(rhs);
+ stack[stackTop] = UniqueTag.DOUBLE_MARK;
+ sDbl[stackTop] = lDbl + rDbl;
+ }
+ return;
+ }
+
+ // handle object(lhs) + number(d) code
+ if (lhs instanceof Scriptable) {
+ rhs = ScriptRuntime.wrapNumber(d);
+ if (!leftRightOrder) {
+ Object tmp = lhs;
+ lhs = rhs;
+ rhs = tmp;
+ }
+ stack[stackTop] = ScriptRuntime.add(lhs, rhs, cx);
+ } else if (lhs instanceof String) {
+ String lstr = (String)lhs;
+ String rstr = ScriptRuntime.toString(d);
+ if (leftRightOrder) {
+ stack[stackTop] = lstr.concat(rstr);
+ } else {
+ stack[stackTop] = rstr.concat(lstr);
+ }
+ } else {
+ double lDbl = (lhs instanceof Number)
+ ? ((Number)lhs).doubleValue() : ScriptRuntime.toNumber(lhs);
+ stack[stackTop] = UniqueTag.DOUBLE_MARK;
+ sDbl[stackTop] = lDbl + d;
+ }
+ }
+
+ private static Object[] getArgsArray(Object[] stack, double[] sDbl,
+ int shift, int count)
+ {
+ if (count == 0) {
+ return ScriptRuntime.emptyArgs;
+ }
+ Object[] args = new Object[count];
+ for (int i = 0; i != count; ++i, ++shift) {
+ Object val = stack[shift];
+ if (val == UniqueTag.DOUBLE_MARK) {
+ val = ScriptRuntime.wrapNumber(sDbl[shift]);
+ }
+ args[i] = val;
+ }
+ return args;
+ }
+
+ private static void addInstructionCount(Context cx, CallFrame frame,
+ int extra)
+ {
+ cx.instructionCount += frame.pc - frame.pcPrevBranch + extra;
+ if (cx.instructionCount > cx.instructionThreshold) {
+ cx.observeInstructionCount(cx.instructionCount);
+ cx.instructionCount = 0;
+ }
+ }
+}
diff --git a/src/org/mozilla/javascript/InterpreterData.java b/src/org/mozilla/javascript/InterpreterData.java
new file mode 100644
index 0000000..7435b10
--- /dev/null
+++ b/src/org/mozilla/javascript/InterpreterData.java
@@ -0,0 +1,192 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Bob Jervis
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.Serializable;
+
+import org.mozilla.javascript.debug.DebuggableScript;
+
+final class InterpreterData implements Serializable, DebuggableScript
+{
+ static final long serialVersionUID = 5067677351589230234L;
+
+ static final int INITIAL_MAX_ICODE_LENGTH = 1024;
+ static final int INITIAL_STRINGTABLE_SIZE = 64;
+ static final int INITIAL_NUMBERTABLE_SIZE = 64;
+
+ InterpreterData(int languageVersion,
+ String sourceFile, String encodedSource)
+ {
+ this.languageVersion = languageVersion;
+ this.itsSourceFile = sourceFile;
+ this.encodedSource = encodedSource;
+
+ init();
+ }
+
+ InterpreterData(InterpreterData parent)
+ {
+ this.parentData = parent;
+ this.languageVersion = parent.languageVersion;
+ this.itsSourceFile = parent.itsSourceFile;
+ this.encodedSource = parent.encodedSource;
+
+ init();
+ }
+
+ private void init()
+ {
+ itsICode = new byte[INITIAL_MAX_ICODE_LENGTH];
+ itsStringTable = new String[INITIAL_STRINGTABLE_SIZE];
+ }
+
+ String itsName;
+ String itsSourceFile;
+ boolean itsNeedsActivation;
+ int itsFunctionType;
+
+ String[] itsStringTable;
+ double[] itsDoubleTable;
+ InterpreterData[] itsNestedFunctions;
+ Object[] itsRegExpLiterals;
+
+ byte[] itsICode;
+
+ int[] itsExceptionTable;
+
+ int itsMaxVars;
+ int itsMaxLocals;
+ int itsMaxStack;
+ int itsMaxFrameArray;
+
+ // see comments in NativeFuncion for definition of argNames and argCount
+ String[] argNames;
+ boolean[] argIsConst;
+ int argCount;
+
+ int itsMaxCalleeArgs;
+
+ String encodedSource;
+ int encodedSourceStart;
+ int encodedSourceEnd;
+
+ int languageVersion;
+
+ boolean useDynamicScope;
+
+ boolean topLevel;
+
+ Object[] literalIds;
+
+ UintMap longJumps;
+
+ int firstLinePC = -1; // PC for the first LINE icode
+
+ InterpreterData parentData;
+
+ boolean evalScriptFlag; // true if script corresponds to eval() code
+
+ public boolean isTopLevel()
+ {
+ return topLevel;
+ }
+
+ public boolean isFunction()
+ {
+ return itsFunctionType != 0;
+ }
+
+ public String getFunctionName()
+ {
+ return itsName;
+ }
+
+ public int getParamCount()
+ {
+ return argCount;
+ }
+
+ public int getParamAndVarCount()
+ {
+ return argNames.length;
+ }
+
+ public String getParamOrVarName(int index)
+ {
+ return argNames[index];
+ }
+
+ public boolean getParamOrVarConst(int index)
+ {
+ return argIsConst[index];
+ }
+
+ public String getSourceName()
+ {
+ return itsSourceFile;
+ }
+
+ public boolean isGeneratedScript()
+ {
+ return ScriptRuntime.isGeneratedScript(itsSourceFile);
+ }
+
+ public int[] getLineNumbers()
+ {
+ return Interpreter.getLineNumbers(this);
+ }
+
+ public int getFunctionCount()
+ {
+ return (itsNestedFunctions == null) ? 0 : itsNestedFunctions.length;
+ }
+
+ public DebuggableScript getFunction(int index)
+ {
+ return itsNestedFunctions[index];
+ }
+
+ public DebuggableScript getParent()
+ {
+ return parentData;
+ }
+
+}
diff --git a/src/org/mozilla/javascript/JavaAdapter.java b/src/org/mozilla/javascript/JavaAdapter.java
new file mode 100644
index 0000000..be1b363
--- /dev/null
+++ b/src/org/mozilla/javascript/JavaAdapter.java
@@ -0,0 +1,1140 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Patrick Beard
+ * Norris Boyd
+ * Igor Bukanov
+ * Mike McCabe
+ * Matthias Radestock
+ * Andi Vajda
+ * Andrew Wason
+ * Kemal Bayram
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import org.mozilla.classfile.*;
+import java.lang.reflect.*;
+import java.io.*;
+import java.security.*;
+import java.util.*;
+
+public final class JavaAdapter implements IdFunctionCall
+{
+ /**
+ * Provides a key with which to distinguish previously generated
+ * adapter classes stored in a hash table.
+ */
+ static class JavaAdapterSignature
+ {
+ Class<?> superClass;
+ Class<?>[] interfaces;
+ ObjToIntMap names;
+
+ JavaAdapterSignature(Class<?> superClass, Class<?>[] interfaces,
+ ObjToIntMap names)
+ {
+ this.superClass = superClass;
+ this.interfaces = interfaces;
+ this.names = names;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (!(obj instanceof JavaAdapterSignature))
+ return false;
+ JavaAdapterSignature sig = (JavaAdapterSignature) obj;
+ if (superClass != sig.superClass)
+ return false;
+ if (interfaces != sig.interfaces) {
+ if (interfaces.length != sig.interfaces.length)
+ return false;
+ for (int i=0; i < interfaces.length; i++)
+ if (interfaces[i] != sig.interfaces[i])
+ return false;
+ }
+ if (names.size() != sig.names.size())
+ return false;
+ ObjToIntMap.Iterator iter = new ObjToIntMap.Iterator(names);
+ for (iter.start(); !iter.done(); iter.next()) {
+ String name = (String)iter.getKey();
+ int arity = iter.getValue();
+ if (arity != names.get(name, arity + 1))
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return superClass.hashCode()
+ | (0x9e3779b9 * (names.size() | (interfaces.length << 16)));
+ }
+ }
+
+ public static void init(Context cx, Scriptable scope, boolean sealed)
+ {
+ JavaAdapter obj = new JavaAdapter();
+ IdFunctionObject ctor = new IdFunctionObject(obj, FTAG, Id_JavaAdapter,
+ "JavaAdapter", 1, scope);
+ ctor.markAsConstructor(null);
+ if (sealed) {
+ ctor.sealObject();
+ }
+ ctor.exportAsScopeProperty();
+ }
+
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (f.hasTag(FTAG)) {
+ if (f.methodId() == Id_JavaAdapter) {
+ return js_createAdapter(cx, scope, args);
+ }
+ }
+ throw f.unknown();
+ }
+
+ public static Object convertResult(Object result, Class<?> c)
+ {
+ if (result == Undefined.instance &&
+ (c != ScriptRuntime.ObjectClass &&
+ c != ScriptRuntime.StringClass))
+ {
+ // Avoid an error for an undefined value; return null instead.
+ return null;
+ }
+ return Context.jsToJava(result, c);
+ }
+
+ public static Scriptable createAdapterWrapper(Scriptable obj,
+ Object adapter)
+ {
+ Scriptable scope = ScriptableObject.getTopLevelScope(obj);
+ NativeJavaObject res = new NativeJavaObject(scope, adapter, null, true);
+ res.setPrototype(obj);
+ return res;
+ }
+
+ public static Object getAdapterSelf(Class<?> adapterClass, Object adapter)
+ throws NoSuchFieldException, IllegalAccessException
+ {
+ Field self = adapterClass.getDeclaredField("self");
+ return self.get(adapter);
+ }
+
+ static Object js_createAdapter(Context cx, Scriptable scope, Object[] args)
+ {
+ int N = args.length;
+ if (N == 0) {
+ throw ScriptRuntime.typeError0("msg.adapter.zero.args");
+ }
+
+ Class<?> superClass = null;
+ Class<?>[] intfs = new Class[N - 1];
+ int interfaceCount = 0;
+ for (int i = 0; i != N - 1; ++i) {
+ Object arg = args[i];
+ if (!(arg instanceof NativeJavaClass)) {
+ throw ScriptRuntime.typeError2("msg.not.java.class.arg",
+ String.valueOf(i),
+ ScriptRuntime.toString(arg));
+ }
+ Class<?> c = ((NativeJavaClass) arg).getClassObject();
+ if (!c.isInterface()) {
+ if (superClass != null) {
+ throw ScriptRuntime.typeError2("msg.only.one.super",
+ superClass.getName(), c.getName());
+ }
+ superClass = c;
+ } else {
+ intfs[interfaceCount++] = c;
+ }
+ }
+
+ if (superClass == null)
+ superClass = ScriptRuntime.ObjectClass;
+
+ Class<?>[] interfaces = new Class[interfaceCount];
+ System.arraycopy(intfs, 0, interfaces, 0, interfaceCount);
+ Scriptable obj = ScriptRuntime.toObject(cx, scope, args[N - 1]);
+
+ Class<?> adapterClass = getAdapterClass(scope, superClass, interfaces,
+ obj);
+
+ Class<?>[] ctorParms = {
+ ScriptRuntime.ContextFactoryClass,
+ ScriptRuntime.ScriptableClass
+ };
+ Object[] ctorArgs = { cx.getFactory(), obj };
+ try {
+ Object adapter = adapterClass.getConstructor(ctorParms).
+ newInstance(ctorArgs);
+ return getAdapterSelf(adapterClass, adapter);
+ } catch (Exception ex) {
+ throw Context.throwAsScriptRuntimeEx(ex);
+ }
+ }
+
+ // Needed by NativeJavaObject serializer
+ public static void writeAdapterObject(Object javaObject,
+ ObjectOutputStream out)
+ throws IOException
+ {
+ Class<?> cl = javaObject.getClass();
+ out.writeObject(cl.getSuperclass().getName());
+
+ Class<?>[] interfaces = cl.getInterfaces();
+ String[] interfaceNames = new String[interfaces.length];
+
+ for (int i=0; i < interfaces.length; i++)
+ interfaceNames[i] = interfaces[i].getName();
+
+ out.writeObject(interfaceNames);
+
+ try {
+ Object delegee = cl.getField("delegee").get(javaObject);
+ out.writeObject(delegee);
+ return;
+ } catch (IllegalAccessException e) {
+ } catch (NoSuchFieldException e) {
+ }
+ throw new IOException();
+ }
+
+ // Needed by NativeJavaObject de-serializer
+ public static Object readAdapterObject(Scriptable self,
+ ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ ContextFactory factory;
+ Context cx = Context.getCurrentContext();
+ if (cx != null) {
+ factory = cx.getFactory();
+ } else {
+ factory = null;
+ }
+
+ Class<?> superClass = Class.forName((String)in.readObject());
+
+ String[] interfaceNames = (String[])in.readObject();
+ Class<?>[] interfaces = new Class[interfaceNames.length];
+
+ for (int i=0; i < interfaceNames.length; i++)
+ interfaces[i] = Class.forName(interfaceNames[i]);
+
+ Scriptable delegee = (Scriptable)in.readObject();
+
+ Class<?> adapterClass = getAdapterClass(self, superClass, interfaces,
+ delegee);
+
+ Class<?>[] ctorParms = {
+ ScriptRuntime.ContextFactoryClass,
+ ScriptRuntime.ScriptableClass,
+ ScriptRuntime.ScriptableClass
+ };
+ Object[] ctorArgs = { factory, delegee, self };
+ try {
+ return adapterClass.getConstructor(ctorParms).newInstance(ctorArgs);
+ } catch(InstantiationException e) {
+ } catch(IllegalAccessException e) {
+ } catch(InvocationTargetException e) {
+ } catch(NoSuchMethodException e) {
+ }
+
+ throw new ClassNotFoundException("adapter");
+ }
+
+ private static ObjToIntMap getObjectFunctionNames(Scriptable obj)
+ {
+ Object[] ids = ScriptableObject.getPropertyIds(obj);
+ ObjToIntMap map = new ObjToIntMap(ids.length);
+ for (int i = 0; i != ids.length; ++i) {
+ if (!(ids[i] instanceof String))
+ continue;
+ String id = (String) ids[i];
+ Object value = ScriptableObject.getProperty(obj, id);
+ if (value instanceof Function) {
+ Function f = (Function)value;
+ int length = ScriptRuntime.toInt32(
+ ScriptableObject.getProperty(f, "length"));
+ if (length < 0) {
+ length = 0;
+ }
+ map.put(id, length);
+ }
+ }
+ return map;
+ }
+
+ private static Class<?> getAdapterClass(Scriptable scope, Class<?> superClass,
+ Class<?>[] interfaces, Scriptable obj)
+ {
+ ClassCache cache = ClassCache.get(scope);
+ Map<JavaAdapterSignature,Class<?>> generated
+ = cache.getInterfaceAdapterCacheMap();
+
+ ObjToIntMap names = getObjectFunctionNames(obj);
+ JavaAdapterSignature sig;
+ sig = new JavaAdapterSignature(superClass, interfaces, names);
+ Class<?> adapterClass = generated.get(sig);
+ if (adapterClass == null) {
+ String adapterName = "adapter"
+ + cache.newClassSerialNumber();
+ byte[] code = createAdapterCode(names, adapterName,
+ superClass, interfaces, null);
+
+ adapterClass = loadAdapterClass(adapterName, code);
+ if (cache.isCachingEnabled()) {
+ generated.put(sig, adapterClass);
+ }
+ }
+ return adapterClass;
+ }
+
+ public static byte[] createAdapterCode(ObjToIntMap functionNames,
+ String adapterName,
+ Class<?> superClass,
+ Class<?>[] interfaces,
+ String scriptClassName)
+ {
+ ClassFileWriter cfw = new ClassFileWriter(adapterName,
+ superClass.getName(),
+ "<adapter>");
+ cfw.addField("factory", "Lorg/mozilla/javascript/ContextFactory;",
+ (short) (ClassFileWriter.ACC_PUBLIC |
+ ClassFileWriter.ACC_FINAL));
+ cfw.addField("delegee", "Lorg/mozilla/javascript/Scriptable;",
+ (short) (ClassFileWriter.ACC_PUBLIC |
+ ClassFileWriter.ACC_FINAL));
+ cfw.addField("self", "Lorg/mozilla/javascript/Scriptable;",
+ (short) (ClassFileWriter.ACC_PUBLIC |
+ ClassFileWriter.ACC_FINAL));
+ int interfacesCount = interfaces == null ? 0 : interfaces.length;
+ for (int i=0; i < interfacesCount; i++) {
+ if (interfaces[i] != null)
+ cfw.addInterface(interfaces[i].getName());
+ }
+
+ String superName = superClass.getName().replace('.', '/');
+ generateCtor(cfw, adapterName, superName);
+ generateSerialCtor(cfw, adapterName, superName);
+ if (scriptClassName != null)
+ generateEmptyCtor(cfw, adapterName, superName, scriptClassName);
+
+ ObjToIntMap generatedOverrides = new ObjToIntMap();
+ ObjToIntMap generatedMethods = new ObjToIntMap();
+
+ // generate methods to satisfy all specified interfaces.
+ for (int i = 0; i < interfacesCount; i++) {
+ Method[] methods = interfaces[i].getMethods();
+ for (int j = 0; j < methods.length; j++) {
+ Method method = methods[j];
+ int mods = method.getModifiers();
+ if (Modifier.isStatic(mods) || Modifier.isFinal(mods)) {
+ continue;
+ }
+ String methodName = method.getName();
+ Class<?>[] argTypes = method.getParameterTypes();
+ if (!functionNames.has(methodName)) {
+ try {
+ superClass.getMethod(methodName, argTypes);
+ // The class we're extending implements this method and
+ // the JavaScript object doesn't have an override. See
+ // bug 61226.
+ continue;
+ } catch (NoSuchMethodException e) {
+ // Not implemented by superclass; fall through
+ }
+ }
+ // make sure to generate only one instance of a particular
+ // method/signature.
+ String methodSignature = getMethodSignature(method, argTypes);
+ String methodKey = methodName + methodSignature;
+ if (! generatedOverrides.has(methodKey)) {
+ generateMethod(cfw, adapterName, methodName,
+ argTypes, method.getReturnType());
+ generatedOverrides.put(methodKey, 0);
+ generatedMethods.put(methodName, 0);
+ }
+ }
+ }
+
+ // Now, go through the superclass's methods, checking for abstract
+ // methods or additional methods to override.
+
+ // generate any additional overrides that the object might contain.
+ Method[] methods = getOverridableMethods(superClass);
+ for (int j = 0; j < methods.length; j++) {
+ Method method = methods[j];
+ int mods = method.getModifiers();
+ // if a method is marked abstract, must implement it or the
+ // resulting class won't be instantiable. otherwise, if the object
+ // has a property of the same name, then an override is intended.
+ boolean isAbstractMethod = Modifier.isAbstract(mods);
+ String methodName = method.getName();
+ if (isAbstractMethod || functionNames.has(methodName)) {
+ // make sure to generate only one instance of a particular
+ // method/signature.
+ Class<?>[] argTypes = method.getParameterTypes();
+ String methodSignature = getMethodSignature(method, argTypes);
+ String methodKey = methodName + methodSignature;
+ if (! generatedOverrides.has(methodKey)) {
+ generateMethod(cfw, adapterName, methodName,
+ argTypes, method.getReturnType());
+ generatedOverrides.put(methodKey, 0);
+ generatedMethods.put(methodName, 0);
+
+ // if a method was overridden, generate a "super$method"
+ // which lets the delegate call the superclass' version.
+ if (!isAbstractMethod) {
+ generateSuper(cfw, adapterName, superName,
+ methodName, methodSignature,
+ argTypes, method.getReturnType());
+ }
+ }
+ }
+ }
+
+ // Generate Java methods for remaining properties that are not
+ // overrides.
+ ObjToIntMap.Iterator iter = new ObjToIntMap.Iterator(functionNames);
+ for (iter.start(); !iter.done(); iter.next()) {
+ String functionName = (String)iter.getKey();
+ if (generatedMethods.has(functionName))
+ continue;
+ int length = iter.getValue();
+ Class<?>[] parms = new Class[length];
+ for (int k=0; k < length; k++)
+ parms[k] = ScriptRuntime.ObjectClass;
+ generateMethod(cfw, adapterName, functionName, parms,
+ ScriptRuntime.ObjectClass);
+ }
+ return cfw.toByteArray();
+ }
+
+ static Method[] getOverridableMethods(Class<?> c)
+ {
+ ArrayList<Method> list = new ArrayList<Method>();
+ HashSet<String> skip = new HashSet<String>();
+ while (c != null) {
+ appendOverridableMethods(c, list, skip);
+ for (Class<?> intf: c.getInterfaces())
+ appendOverridableMethods(intf, list, skip);
+ c = c.getSuperclass();
+ }
+ return list.toArray(new Method[list.size()]);
+ }
+
+ private static void appendOverridableMethods(Class<?> c,
+ ArrayList<Method> list, HashSet<String> skip)
+ {
+ Method[] methods = c.getDeclaredMethods();
+ for (int i = 0; i < methods.length; i++) {
+ String methodKey = methods[i].getName() +
+ getMethodSignature(methods[i],
+ methods[i].getParameterTypes());
+ if (skip.contains(methodKey))
+ continue; // skip this method
+ int mods = methods[i].getModifiers();
+ if (Modifier.isStatic(mods))
+ continue;
+ if (Modifier.isFinal(mods)) {
+ // Make sure we don't add a final method to the list
+ // of overridable methods.
+ skip.add(methodKey);
+ continue;
+ }
+ if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) {
+ list.add(methods[i]);
+ skip.add(methodKey);
+ }
+ }
+ }
+
+ static Class<?> loadAdapterClass(String className, byte[] classBytes)
+ {
+ Object staticDomain;
+ Class<?> domainClass = SecurityController.getStaticSecurityDomainClass();
+ if(domainClass == CodeSource.class || domainClass == ProtectionDomain.class) {
+ ProtectionDomain protectionDomain = JavaAdapter.class.getProtectionDomain();
+ if(domainClass == CodeSource.class) {
+ staticDomain = protectionDomain == null ? null : protectionDomain.getCodeSource();
+ }
+ else {
+ staticDomain = protectionDomain;
+ }
+ }
+ else {
+ staticDomain = null;
+ }
+ GeneratedClassLoader loader = SecurityController.createLoader(null,
+ staticDomain);
+ Class<?> result = loader.defineClass(className, classBytes);
+ loader.linkClass(result);
+ return result;
+ }
+
+ public static Function getFunction(Scriptable obj, String functionName)
+ {
+ Object x = ScriptableObject.getProperty(obj, functionName);
+ if (x == Scriptable.NOT_FOUND) {
+ // This method used to swallow the exception from calling
+ // an undefined method. People have come to depend on this
+ // somewhat dubious behavior. It allows people to avoid
+ // implementing listener methods that they don't care about,
+ // for instance.
+ return null;
+ }
+ if (!(x instanceof Function))
+ throw ScriptRuntime.notFunctionError(x, functionName);
+
+ return (Function)x;
+ }
+
+ /**
+ * Utility method which dynamically binds a Context to the current thread,
+ * if none already exists.
+ */
+ public static Object callMethod(ContextFactory factory,
+ final Scriptable thisObj,
+ final Function f, final Object[] args,
+ final long argsToWrap)
+ {
+ if (f == null) {
+ // See comments in getFunction
+ return Undefined.instance;
+ }
+ if (factory == null) {
+ factory = ContextFactory.getGlobal();
+ }
+
+ final Scriptable scope = f.getParentScope();
+ if (argsToWrap == 0) {
+ return Context.call(factory, f, scope, thisObj, args);
+ }
+
+ Context cx = Context.getCurrentContext();
+ if (cx != null) {
+ return doCall(cx, scope, thisObj, f, args, argsToWrap);
+ } else {
+ return factory.call(new ContextAction() {
+ public Object run(Context cx)
+ {
+ return doCall(cx, scope, thisObj, f, args, argsToWrap);
+ }
+ });
+ }
+ }
+
+ private static Object doCall(Context cx, Scriptable scope,
+ Scriptable thisObj, Function f,
+ Object[] args, long argsToWrap)
+ {
+ // Wrap the rest of objects
+ for (int i = 0; i != args.length; ++i) {
+ if (0 != (argsToWrap & (1 << i))) {
+ Object arg = args[i];
+ if (!(arg instanceof Scriptable)) {
+ args[i] = cx.getWrapFactory().wrap(cx, scope, arg,
+ null);
+ }
+ }
+ }
+ return f.call(cx, scope, thisObj, args);
+ }
+
+ public static Scriptable runScript(final Script script)
+ {
+ return (Scriptable)ContextFactory.getGlobal().call(
+ new ContextAction() {
+ public Object run(Context cx)
+ {
+ ScriptableObject global = ScriptRuntime.getGlobal(cx);
+ script.exec(cx, global);
+ return global;
+ }
+ });
+ }
+
+ private static void generateCtor(ClassFileWriter cfw, String adapterName,
+ String superName)
+ {
+ cfw.startMethod("<init>",
+ "(Lorg/mozilla/javascript/ContextFactory;"
+ +"Lorg/mozilla/javascript/Scriptable;)V",
+ ClassFileWriter.ACC_PUBLIC);
+
+ // Invoke base class constructor
+ cfw.add(ByteCode.ALOAD_0); // this
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");
+
+ // Save parameter in instance variable "factory"
+ cfw.add(ByteCode.ALOAD_0); // this
+ cfw.add(ByteCode.ALOAD_1); // first arg: ContextFactory instance
+ cfw.add(ByteCode.PUTFIELD, adapterName, "factory",
+ "Lorg/mozilla/javascript/ContextFactory;");
+
+ // Save parameter in instance variable "delegee"
+ cfw.add(ByteCode.ALOAD_0); // this
+ cfw.add(ByteCode.ALOAD_2); // second arg: Scriptable delegee
+ cfw.add(ByteCode.PUTFIELD, adapterName, "delegee",
+ "Lorg/mozilla/javascript/Scriptable;");
+
+ cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self
+ // create a wrapper object to be used as "this" in method calls
+ cfw.add(ByteCode.ALOAD_2); // the Scriptable delegee
+ cfw.add(ByteCode.ALOAD_0); // this
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/JavaAdapter",
+ "createAdapterWrapper",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/Object;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.add(ByteCode.PUTFIELD, adapterName, "self",
+ "Lorg/mozilla/javascript/Scriptable;");
+
+ cfw.add(ByteCode.RETURN);
+ cfw.stopMethod((short)3); // 3: this + factory + delegee
+ }
+
+ private static void generateSerialCtor(ClassFileWriter cfw,
+ String adapterName,
+ String superName)
+ {
+ cfw.startMethod("<init>",
+ "(Lorg/mozilla/javascript/ContextFactory;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")V",
+ ClassFileWriter.ACC_PUBLIC);
+
+ // Invoke base class constructor
+ cfw.add(ByteCode.ALOAD_0); // this
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");
+
+ // Save parameter in instance variable "factory"
+ cfw.add(ByteCode.ALOAD_0); // this
+ cfw.add(ByteCode.ALOAD_1); // first arg: ContextFactory instance
+ cfw.add(ByteCode.PUTFIELD, adapterName, "factory",
+ "Lorg/mozilla/javascript/ContextFactory;");
+
+ // Save parameter in instance variable "delegee"
+ cfw.add(ByteCode.ALOAD_0); // this
+ cfw.add(ByteCode.ALOAD_2); // second arg: Scriptable delegee
+ cfw.add(ByteCode.PUTFIELD, adapterName, "delegee",
+ "Lorg/mozilla/javascript/Scriptable;");
+ // save self
+ cfw.add(ByteCode.ALOAD_0); // this
+ cfw.add(ByteCode.ALOAD_3); // second arg: Scriptable self
+ cfw.add(ByteCode.PUTFIELD, adapterName, "self",
+ "Lorg/mozilla/javascript/Scriptable;");
+
+ cfw.add(ByteCode.RETURN);
+ cfw.stopMethod((short)4); // 4: this + factory + delegee + self
+ }
+
+ private static void generateEmptyCtor(ClassFileWriter cfw,
+ String adapterName,
+ String superName,
+ String scriptClassName)
+ {
+ cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC);
+
+ // Invoke base class constructor
+ cfw.add(ByteCode.ALOAD_0); // this
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");
+
+ // Set factory to null to use current global when necessary
+ cfw.add(ByteCode.ALOAD_0);
+ cfw.add(ByteCode.ACONST_NULL);
+ cfw.add(ByteCode.PUTFIELD, adapterName, "factory",
+ "Lorg/mozilla/javascript/ContextFactory;");
+
+ // Load script class
+ cfw.add(ByteCode.NEW, scriptClassName);
+ cfw.add(ByteCode.DUP);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, scriptClassName, "<init>", "()V");
+
+ // Run script and save resulting scope
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/JavaAdapter",
+ "runScript",
+ "(Lorg/mozilla/javascript/Script;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.add(ByteCode.ASTORE_1);
+
+ // Save the Scriptable in instance variable "delegee"
+ cfw.add(ByteCode.ALOAD_0); // this
+ cfw.add(ByteCode.ALOAD_1); // the Scriptable
+ cfw.add(ByteCode.PUTFIELD, adapterName, "delegee",
+ "Lorg/mozilla/javascript/Scriptable;");
+
+ cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self
+ // create a wrapper object to be used as "this" in method calls
+ cfw.add(ByteCode.ALOAD_1); // the Scriptable
+ cfw.add(ByteCode.ALOAD_0); // this
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/JavaAdapter",
+ "createAdapterWrapper",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/Object;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.add(ByteCode.PUTFIELD, adapterName, "self",
+ "Lorg/mozilla/javascript/Scriptable;");
+
+ cfw.add(ByteCode.RETURN);
+ cfw.stopMethod((short)2); // this + delegee
+ }
+
+ /**
+ * Generates code to wrap Java arguments into Object[].
+ * Non-primitive Java types are left as-is pending conversion
+ * in the helper method. Leaves the array object on the top of the stack.
+ */
+ static void generatePushWrappedArgs(ClassFileWriter cfw,
+ Class<?>[] argTypes,
+ int arrayLength)
+ {
+ // push arguments
+ cfw.addPush(arrayLength);
+ cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
+ int paramOffset = 1;
+ for (int i = 0; i != argTypes.length; ++i) {
+ cfw.add(ByteCode.DUP); // duplicate array reference
+ cfw.addPush(i);
+ paramOffset += generateWrapArg(cfw, paramOffset, argTypes[i]);
+ cfw.add(ByteCode.AASTORE);
+ }
+ }
+
+ /**
+ * Generates code to wrap Java argument into Object.
+ * Non-primitive Java types are left unconverted pending conversion
+ * in the helper method. Leaves the wrapper object on the top of the stack.
+ */
+ private static int generateWrapArg(ClassFileWriter cfw, int paramOffset,
+ Class<?> argType)
+ {
+ int size = 1;
+ if (!argType.isPrimitive()) {
+ cfw.add(ByteCode.ALOAD, paramOffset);
+
+ } else if (argType == Boolean.TYPE) {
+ // wrap boolean values with java.lang.Boolean.
+ cfw.add(ByteCode.NEW, "java/lang/Boolean");
+ cfw.add(ByteCode.DUP);
+ cfw.add(ByteCode.ILOAD, paramOffset);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Boolean",
+ "<init>", "(Z)V");
+
+ } else if (argType == Character.TYPE) {
+ // Create a string of length 1 using the character parameter.
+ cfw.add(ByteCode.ILOAD, paramOffset);
+ cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/String",
+ "valueOf", "(C)Ljava/lang/String;");
+
+ } else {
+ // convert all numeric values to java.lang.Double.
+ cfw.add(ByteCode.NEW, "java/lang/Double");
+ cfw.add(ByteCode.DUP);
+ String typeName = argType.getName();
+ switch (typeName.charAt(0)) {
+ case 'b':
+ case 's':
+ case 'i':
+ // load an int value, convert to double.
+ cfw.add(ByteCode.ILOAD, paramOffset);
+ cfw.add(ByteCode.I2D);
+ break;
+ case 'l':
+ // load a long, convert to double.
+ cfw.add(ByteCode.LLOAD, paramOffset);
+ cfw.add(ByteCode.L2D);
+ size = 2;
+ break;
+ case 'f':
+ // load a float, convert to double.
+ cfw.add(ByteCode.FLOAD, paramOffset);
+ cfw.add(ByteCode.F2D);
+ break;
+ case 'd':
+ cfw.add(ByteCode.DLOAD, paramOffset);
+ size = 2;
+ break;
+ }
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Double",
+ "<init>", "(D)V");
+ }
+ return size;
+ }
+
+ /**
+ * Generates code to convert a wrapped value type to a primitive type.
+ * Handles unwrapping java.lang.Boolean, and java.lang.Number types.
+ * Generates the appropriate RETURN bytecode.
+ */
+ static void generateReturnResult(ClassFileWriter cfw, Class<?> retType,
+ boolean callConvertResult)
+ {
+ // wrap boolean values with java.lang.Boolean, convert all other
+ // primitive values to java.lang.Double.
+ if (retType == Void.TYPE) {
+ cfw.add(ByteCode.POP);
+ cfw.add(ByteCode.RETURN);
+
+ } else if (retType == Boolean.TYPE) {
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/Context",
+ "toBoolean", "(Ljava/lang/Object;)Z");
+ cfw.add(ByteCode.IRETURN);
+
+ } else if (retType == Character.TYPE) {
+ // characters are represented as strings in JavaScript.
+ // return the first character.
+ // first convert the value to a string if possible.
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/Context",
+ "toString",
+ "(Ljava/lang/Object;)Ljava/lang/String;");
+ cfw.add(ByteCode.ICONST_0);
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/String",
+ "charAt", "(I)C");
+ cfw.add(ByteCode.IRETURN);
+
+ } else if (retType.isPrimitive()) {
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/Context",
+ "toNumber", "(Ljava/lang/Object;)D");
+ String typeName = retType.getName();
+ switch (typeName.charAt(0)) {
+ case 'b':
+ case 's':
+ case 'i':
+ cfw.add(ByteCode.D2I);
+ cfw.add(ByteCode.IRETURN);
+ break;
+ case 'l':
+ cfw.add(ByteCode.D2L);
+ cfw.add(ByteCode.LRETURN);
+ break;
+ case 'f':
+ cfw.add(ByteCode.D2F);
+ cfw.add(ByteCode.FRETURN);
+ break;
+ case 'd':
+ cfw.add(ByteCode.DRETURN);
+ break;
+ default:
+ throw new RuntimeException("Unexpected return type " +
+ retType.toString());
+ }
+
+ } else {
+ String retTypeStr = retType.getName();
+ if (callConvertResult) {
+ cfw.addLoadConstant(retTypeStr);
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "java/lang/Class",
+ "forName",
+ "(Ljava/lang/String;)Ljava/lang/Class;");
+
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/JavaAdapter",
+ "convertResult",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Class;"
+ +")Ljava/lang/Object;");
+ }
+ // Now cast to return type
+ cfw.add(ByteCode.CHECKCAST, retTypeStr);
+ cfw.add(ByteCode.ARETURN);
+ }
+ }
+
+ private static void generateMethod(ClassFileWriter cfw, String genName,
+ String methodName, Class<?>[] parms,
+ Class<?> returnType)
+ {
+ StringBuffer sb = new StringBuffer();
+ int paramsEnd = appendMethodSignature(parms, returnType, sb);
+ String methodSignature = sb.toString();
+ cfw.startMethod(methodName, methodSignature,
+ ClassFileWriter.ACC_PUBLIC);
+
+ // Prepare stack to call method
+
+ // push factory
+ cfw.add(ByteCode.ALOAD_0);
+ cfw.add(ByteCode.GETFIELD, genName, "factory",
+ "Lorg/mozilla/javascript/ContextFactory;");
+
+ // push self
+ cfw.add(ByteCode.ALOAD_0);
+ cfw.add(ByteCode.GETFIELD, genName, "self",
+ "Lorg/mozilla/javascript/Scriptable;");
+
+ // push function
+ cfw.add(ByteCode.ALOAD_0);
+ cfw.add(ByteCode.GETFIELD, genName, "delegee",
+ "Lorg/mozilla/javascript/Scriptable;");
+ cfw.addPush(methodName);
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/JavaAdapter",
+ "getFunction",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +")Lorg/mozilla/javascript/Function;");
+
+ // push arguments
+ generatePushWrappedArgs(cfw, parms, parms.length);
+
+ // push bits to indicate which parameters should be wrapped
+ if (parms.length > 64) {
+ // If it will be an issue, then passing a static boolean array
+ // can be an option, but for now using simple bitmask
+ throw Context.reportRuntimeError0(
+ "JavaAdapter can not subclass methods with more then"
+ +" 64 arguments.");
+ }
+ long convertionMask = 0;
+ for (int i = 0; i != parms.length; ++i) {
+ if (!parms[i].isPrimitive()) {
+ convertionMask |= (1 << i);
+ }
+ }
+ cfw.addPush(convertionMask);
+
+ // go through utility method, which creates a Context to run the
+ // method in.
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/JavaAdapter",
+ "callMethod",
+ "(Lorg/mozilla/javascript/ContextFactory;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Function;"
+ +"[Ljava/lang/Object;"
+ +"J"
+ +")Ljava/lang/Object;");
+
+ generateReturnResult(cfw, returnType, true);
+
+ cfw.stopMethod((short)paramsEnd);
+ }
+
+ /**
+ * Generates code to push typed parameters onto the operand stack
+ * prior to a direct Java method call.
+ */
+ private static int generatePushParam(ClassFileWriter cfw, int paramOffset,
+ Class<?> paramType)
+ {
+ if (!paramType.isPrimitive()) {
+ cfw.addALoad(paramOffset);
+ return 1;
+ }
+ String typeName = paramType.getName();
+ switch (typeName.charAt(0)) {
+ case 'z':
+ case 'b':
+ case 'c':
+ case 's':
+ case 'i':
+ // load an int value, convert to double.
+ cfw.addILoad(paramOffset);
+ return 1;
+ case 'l':
+ // load a long, convert to double.
+ cfw.addLLoad(paramOffset);
+ return 2;
+ case 'f':
+ // load a float, convert to double.
+ cfw.addFLoad(paramOffset);
+ return 1;
+ case 'd':
+ cfw.addDLoad(paramOffset);
+ return 2;
+ }
+ throw Kit.codeBug();
+ }
+
+ /**
+ * Generates code to return a Java type, after calling a Java method
+ * that returns the same type.
+ * Generates the appropriate RETURN bytecode.
+ */
+ private static void generatePopResult(ClassFileWriter cfw,
+ Class<?> retType)
+ {
+ if (retType.isPrimitive()) {
+ String typeName = retType.getName();
+ switch (typeName.charAt(0)) {
+ case 'b':
+ case 'c':
+ case 's':
+ case 'i':
+ case 'z':
+ cfw.add(ByteCode.IRETURN);
+ break;
+ case 'l':
+ cfw.add(ByteCode.LRETURN);
+ break;
+ case 'f':
+ cfw.add(ByteCode.FRETURN);
+ break;
+ case 'd':
+ cfw.add(ByteCode.DRETURN);
+ break;
+ }
+ } else {
+ cfw.add(ByteCode.ARETURN);
+ }
+ }
+
+ /**
+ * Generates a method called "super$methodName()" which can be called
+ * from JavaScript that is equivalent to calling "super.methodName()"
+ * from Java. Eventually, this may be supported directly in JavaScript.
+ */
+ private static void generateSuper(ClassFileWriter cfw,
+ String genName, String superName,
+ String methodName, String methodSignature,
+ Class<?>[] parms, Class<?> returnType)
+ {
+ cfw.startMethod("super$" + methodName, methodSignature,
+ ClassFileWriter.ACC_PUBLIC);
+
+ // push "this"
+ cfw.add(ByteCode.ALOAD, 0);
+
+ // push the rest of the parameters.
+ int paramOffset = 1;
+ for (int i = 0; i < parms.length; i++) {
+ paramOffset += generatePushParam(cfw, paramOffset, parms[i]);
+ }
+
+ // call the superclass implementation of the method.
+ cfw.addInvoke(ByteCode.INVOKESPECIAL,
+ superName,
+ methodName,
+ methodSignature);
+
+ // now, handle the return type appropriately.
+ Class<?> retType = returnType;
+ if (!retType.equals(Void.TYPE)) {
+ generatePopResult(cfw, retType);
+ } else {
+ cfw.add(ByteCode.RETURN);
+ }
+ cfw.stopMethod((short)(paramOffset + 1));
+ }
+
+ /**
+ * Returns a fully qualified method name concatenated with its signature.
+ */
+ private static String getMethodSignature(Method method, Class<?>[] argTypes)
+ {
+ StringBuffer sb = new StringBuffer();
+ appendMethodSignature(argTypes, method.getReturnType(), sb);
+ return sb.toString();
+ }
+
+ static int appendMethodSignature(Class<?>[] argTypes,
+ Class<?> returnType,
+ StringBuffer sb)
+ {
+ sb.append('(');
+ int firstLocal = 1 + argTypes.length; // includes this.
+ for (int i = 0; i < argTypes.length; i++) {
+ Class<?> type = argTypes[i];
+ appendTypeString(sb, type);
+ if (type == Long.TYPE || type == Double.TYPE) {
+ // adjust for duble slot
+ ++firstLocal;
+ }
+ }
+ sb.append(')');
+ appendTypeString(sb, returnType);
+ return firstLocal;
+ }
+
+ private static StringBuffer appendTypeString(StringBuffer sb, Class<?> type)
+ {
+ while (type.isArray()) {
+ sb.append('[');
+ type = type.getComponentType();
+ }
+ if (type.isPrimitive()) {
+ char typeLetter;
+ if (type == Boolean.TYPE) {
+ typeLetter = 'Z';
+ } else if (type == Long.TYPE) {
+ typeLetter = 'J';
+ } else {
+ String typeName = type.getName();
+ typeLetter = Character.toUpperCase(typeName.charAt(0));
+ }
+ sb.append(typeLetter);
+ } else {
+ sb.append('L');
+ sb.append(type.getName().replace('.', '/'));
+ sb.append(';');
+ }
+ return sb;
+ }
+
+ static int[] getArgsToConvert(Class<?>[] argTypes)
+ {
+ int count = 0;
+ for (int i = 0; i != argTypes.length; ++i) {
+ if (!argTypes[i].isPrimitive())
+ ++count;
+ }
+ if (count == 0)
+ return null;
+ int[] array = new int[count];
+ count = 0;
+ for (int i = 0; i != argTypes.length; ++i) {
+ if (!argTypes[i].isPrimitive())
+ array[count++] = i;
+ }
+ return array;
+ }
+
+ private static final Object FTAG = "JavaAdapter";
+ private static final int Id_JavaAdapter = 1;
+}
diff --git a/src/org/mozilla/javascript/JavaMembers.java b/src/org/mozilla/javascript/JavaMembers.java
new file mode 100644
index 0000000..4744a1c
--- /dev/null
+++ b/src/org/mozilla/javascript/JavaMembers.java
@@ -0,0 +1,933 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Cameron McCormack
+ * Frank Mitchell
+ * Mike Shaver
+ * Kurt Westerfeld
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+/**
+ *
+ * @author Mike Shaver
+ * @author Norris Boyd
+ * @see NativeJavaObject
+ * @see NativeJavaClass
+ */
+class JavaMembers
+{
+ JavaMembers(Scriptable scope, Class<?> cl)
+ {
+ this(scope, cl, false);
+ }
+
+ JavaMembers(Scriptable scope, Class<?> cl, boolean includeProtected)
+ {
+ try {
+ Context cx = ContextFactory.getGlobal().enterContext();
+ ClassShutter shutter = cx.getClassShutter();
+ if (shutter != null && !shutter.visibleToScripts(cl.getName())) {
+ throw Context.reportRuntimeError1("msg.access.prohibited",
+ cl.getName());
+ }
+ this.includePrivate = cx.hasFeature(
+ Context.FEATURE_ENHANCED_JAVA_ACCESS);
+ this.members = new HashMap<String,Object>();
+ this.staticMembers = new HashMap<String,Object>();
+ this.cl = cl;
+ reflect(scope, includeProtected);
+ } finally {
+ Context.exit();
+ }
+ }
+
+ boolean has(String name, boolean isStatic)
+ {
+ Map<String,Object> ht = isStatic ? staticMembers : members;
+ Object obj = ht.get(name);
+ if (obj != null) {
+ return true;
+ }
+ return findExplicitFunction(name, isStatic) != null;
+ }
+
+ Object get(Scriptable scope, String name, Object javaObject,
+ boolean isStatic)
+ {
+ Map<String,Object> ht = isStatic ? staticMembers : members;
+ Object member = ht.get(name);
+ if (!isStatic && member == null) {
+ // Try to get static member from instance (LC3)
+ member = staticMembers.get(name);
+ }
+ if (member == null) {
+ member = this.getExplicitFunction(scope, name,
+ javaObject, isStatic);
+ if (member == null)
+ return Scriptable.NOT_FOUND;
+ }
+ if (member instanceof Scriptable) {
+ return member;
+ }
+ Context cx = Context.getContext();
+ Object rval;
+ Class<?> type;
+ try {
+ if (member instanceof BeanProperty) {
+ BeanProperty bp = (BeanProperty) member;
+ if (bp.getter == null)
+ return Scriptable.NOT_FOUND;
+ rval = bp.getter.invoke(javaObject, Context.emptyArgs);
+ type = bp.getter.method().getReturnType();
+ } else {
+ Field field = (Field) member;
+ rval = field.get(isStatic ? null : javaObject);
+ type = field.getType();
+ }
+ } catch (Exception ex) {
+ throw Context.throwAsScriptRuntimeEx(ex);
+ }
+ // Need to wrap the object before we return it.
+ scope = ScriptableObject.getTopLevelScope(scope);
+ return cx.getWrapFactory().wrap(cx, scope, rval, type);
+ }
+
+ void put(Scriptable scope, String name, Object javaObject,
+ Object value, boolean isStatic)
+ {
+ Map<String,Object> ht = isStatic ? staticMembers : members;
+ Object member = ht.get(name);
+ if (!isStatic && member == null) {
+ // Try to get static member from instance (LC3)
+ member = staticMembers.get(name);
+ }
+ if (member == null)
+ throw reportMemberNotFound(name);
+ if (member instanceof FieldAndMethods) {
+ FieldAndMethods fam = (FieldAndMethods) ht.get(name);
+ member = fam.field;
+ }
+
+ // Is this a bean property "set"?
+ if (member instanceof BeanProperty) {
+ BeanProperty bp = (BeanProperty)member;
+ if (bp.setter == null) {
+ throw reportMemberNotFound(name);
+ }
+ // If there's only one setter or if the value is null, use the
+ // main setter. Otherwise, let the NativeJavaMethod decide which
+ // setter to use:
+ if (bp.setters == null || value == null) {
+ Class<?> setType = bp.setter.argTypes[0];
+ Object[] args = { Context.jsToJava(value, setType) };
+ try {
+ bp.setter.invoke(javaObject, args);
+ } catch (Exception ex) {
+ throw Context.throwAsScriptRuntimeEx(ex);
+ }
+ } else {
+ Object[] args = { value };
+ bp.setters.call(Context.getContext(),
+ ScriptableObject.getTopLevelScope(scope),
+ scope, args);
+ }
+ }
+ else {
+ if (!(member instanceof Field)) {
+ String str = (member == null) ? "msg.java.internal.private"
+ : "msg.java.method.assign";
+ throw Context.reportRuntimeError1(str, name);
+ }
+ Field field = (Field)member;
+ Object javaValue = Context.jsToJava(value, field.getType());
+ try {
+ field.set(javaObject, javaValue);
+ } catch (IllegalAccessException accessEx) {
+ if ((field.getModifiers() & Modifier.FINAL) != 0) {
+ // treat Java final the same as JavaScript [[READONLY]]
+ return;
+ }
+ throw Context.throwAsScriptRuntimeEx(accessEx);
+ } catch (IllegalArgumentException argEx) {
+ throw Context.reportRuntimeError3(
+ "msg.java.internal.field.type",
+ value.getClass().getName(), field,
+ javaObject.getClass().getName());
+ }
+ }
+ }
+
+ Object[] getIds(boolean isStatic)
+ {
+ Map<String,Object> map = isStatic ? staticMembers : members;
+ return map.keySet().toArray(new Object[map.size()]);
+ }
+
+ static String javaSignature(Class<?> type)
+ {
+ if (!type.isArray()) {
+ return type.getName();
+ } else {
+ int arrayDimension = 0;
+ do {
+ ++arrayDimension;
+ type = type.getComponentType();
+ } while (type.isArray());
+ String name = type.getName();
+ String suffix = "[]";
+ if (arrayDimension == 1) {
+ return name.concat(suffix);
+ } else {
+ int length = name.length() + arrayDimension * suffix.length();
+ StringBuffer sb = new StringBuffer(length);
+ sb.append(name);
+ while (arrayDimension != 0) {
+ --arrayDimension;
+ sb.append(suffix);
+ }
+ return sb.toString();
+ }
+ }
+ }
+
+ static String liveConnectSignature(Class<?>[] argTypes)
+ {
+ int N = argTypes.length;
+ if (N == 0) { return "()"; }
+ StringBuffer sb = new StringBuffer();
+ sb.append('(');
+ for (int i = 0; i != N; ++i) {
+ if (i != 0) {
+ sb.append(',');
+ }
+ sb.append(javaSignature(argTypes[i]));
+ }
+ sb.append(')');
+ return sb.toString();
+ }
+
+ private MemberBox findExplicitFunction(String name, boolean isStatic)
+ {
+ int sigStart = name.indexOf('(');
+ if (sigStart < 0) { return null; }
+
+ Map<String,Object> ht = isStatic ? staticMembers : members;
+ MemberBox[] methodsOrCtors = null;
+ boolean isCtor = (isStatic && sigStart == 0);
+
+ if (isCtor) {
+ // Explicit request for an overloaded constructor
+ methodsOrCtors = ctors;
+ } else {
+ // Explicit request for an overloaded method
+ String trueName = name.substring(0,sigStart);
+ Object obj = ht.get(trueName);
+ if (!isStatic && obj == null) {
+ // Try to get static member from instance (LC3)
+ obj = staticMembers.get(trueName);
+ }
+ if (obj instanceof NativeJavaMethod) {
+ NativeJavaMethod njm = (NativeJavaMethod)obj;
+ methodsOrCtors = njm.methods;
+ }
+ }
+
+ if (methodsOrCtors != null) {
+ for (int i = 0; i < methodsOrCtors.length; i++) {
+ Class<?>[] type = methodsOrCtors[i].argTypes;
+ String sig = liveConnectSignature(type);
+ if (sigStart + sig.length() == name.length()
+ && name.regionMatches(sigStart, sig, 0, sig.length()))
+ {
+ return methodsOrCtors[i];
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private Object getExplicitFunction(Scriptable scope, String name,
+ Object javaObject, boolean isStatic)
+ {
+ Map<String,Object> ht = isStatic ? staticMembers : members;
+ Object member = null;
+ MemberBox methodOrCtor = findExplicitFunction(name, isStatic);
+
+ if (methodOrCtor != null) {
+ Scriptable prototype =
+ ScriptableObject.getFunctionPrototype(scope);
+
+ if (methodOrCtor.isCtor()) {
+ NativeJavaConstructor fun =
+ new NativeJavaConstructor(methodOrCtor);
+ fun.setPrototype(prototype);
+ member = fun;
+ ht.put(name, fun);
+ } else {
+ String trueName = methodOrCtor.getName();
+ member = ht.get(trueName);
+
+ if (member instanceof NativeJavaMethod &&
+ ((NativeJavaMethod)member).methods.length > 1 ) {
+ NativeJavaMethod fun =
+ new NativeJavaMethod(methodOrCtor, name);
+ fun.setPrototype(prototype);
+ ht.put(name, fun);
+ member = fun;
+ }
+ }
+ }
+
+ return member;
+ }
+
+ /**
+ * Retrieves mapping of methods to accessible methods for a class.
+ * In case the class is not public, retrieves methods with same
+ * signature as its public methods from public superclasses and
+ * interfaces (if they exist). Basically upcasts every method to the
+ * nearest accessible method.
+ */
+ private static Method[] discoverAccessibleMethods(Class<?> clazz,
+ boolean includeProtected,
+ boolean includePrivate)
+ {
+ Map<MethodSignature,Method> map = new HashMap<MethodSignature,Method>();
+ discoverAccessibleMethods(clazz, map, includeProtected, includePrivate);
+ return map.values().toArray(new Method[map.size()]);
+ }
+
+ private static void discoverAccessibleMethods(Class<?> clazz,
+ Map<MethodSignature,Method> map, boolean includeProtected,
+ boolean includePrivate)
+ {
+ if (Modifier.isPublic(clazz.getModifiers()) || includePrivate) {
+ try {
+ if (includeProtected || includePrivate) {
+ while (clazz != null) {
+ try {
+ Method[] methods = clazz.getDeclaredMethods();
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+ int mods = method.getModifiers();
+
+ if (Modifier.isPublic(mods) ||
+ Modifier.isProtected(mods) ||
+ includePrivate)
+ {
+ if (includePrivate)
+ method.setAccessible(true);
+ map.put(new MethodSignature(method), method);
+ }
+ }
+ clazz = clazz.getSuperclass();
+ } catch (SecurityException e) {
+ // Some security settings (i.e., applets) disallow
+ // access to Class.getDeclaredMethods. Fall back to
+ // Class.getMethods.
+ Method[] methods = clazz.getMethods();
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+ MethodSignature sig
+ = new MethodSignature(method);
+ if (map.get(sig) == null)
+ map.put(sig, method);
+ }
+ break; // getMethods gets superclass methods, no
+ // need to loop any more
+ }
+ }
+ } else {
+ Method[] methods = clazz.getMethods();
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+ MethodSignature sig = new MethodSignature(method);
+ map.put(sig, method);
+ }
+ }
+ return;
+ } catch (SecurityException e) {
+ Context.reportWarning(
+ "Could not discover accessible methods of class " +
+ clazz.getName() + " due to lack of privileges, " +
+ "attemping superclasses/interfaces.");
+ // Fall through and attempt to discover superclass/interface
+ // methods
+ }
+ }
+
+ Class<?>[] interfaces = clazz.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+ discoverAccessibleMethods(interfaces[i], map, includeProtected,
+ includePrivate);
+ }
+ Class<?> superclass = clazz.getSuperclass();
+ if (superclass != null) {
+ discoverAccessibleMethods(superclass, map, includeProtected,
+ includePrivate);
+ }
+ }
+
+ private static final class MethodSignature
+ {
+ private final String name;
+ private final Class<?>[] args;
+
+ private MethodSignature(String name, Class<?>[] args)
+ {
+ this.name = name;
+ this.args = args;
+ }
+
+ MethodSignature(Method method)
+ {
+ this(method.getName(), method.getParameterTypes());
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if(o instanceof MethodSignature)
+ {
+ MethodSignature ms = (MethodSignature)o;
+ return ms.name.equals(name) && Arrays.equals(args, ms.args);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return name.hashCode() ^ args.length;
+ }
+ }
+
+ private void reflect(Scriptable scope, boolean includeProtected)
+ {
+ // We reflect methods first, because we want overloaded field/method
+ // names to be allocated to the NativeJavaMethod before the field
+ // gets in the way.
+
+ Method[] methods = discoverAccessibleMethods(cl, includeProtected,
+ includePrivate);
+ for (int i = 0; i < methods.length; i++) {
+ Method method = methods[i];
+ int mods = method.getModifiers();
+ boolean isStatic = Modifier.isStatic(mods);
+ Map<String,Object> ht = isStatic ? staticMembers : members;
+ String name = method.getName();
+ Object value = ht.get(name);
+ if (value == null) {
+ ht.put(name, method);
+ } else {
+ ObjArray overloadedMethods;
+ if (value instanceof ObjArray) {
+ overloadedMethods = (ObjArray)value;
+ } else {
+ if (!(value instanceof Method)) Kit.codeBug();
+ // value should be instance of Method as at this stage
+ // staticMembers and members can only contain methods
+ overloadedMethods = new ObjArray();
+ overloadedMethods.add(value);
+ ht.put(name, overloadedMethods);
+ }
+ overloadedMethods.add(method);
+ }
+ }
+
+ // replace Method instances by wrapped NativeJavaMethod objects
+ // first in staticMembers and then in members
+ for (int tableCursor = 0; tableCursor != 2; ++tableCursor) {
+ boolean isStatic = (tableCursor == 0);
+ Map<String,Object> ht = isStatic ? staticMembers : members;
+ for (String name: ht.keySet()) {
+ MemberBox[] methodBoxes;
+ Object value = ht.get(name);
+ if (value instanceof Method) {
+ methodBoxes = new MemberBox[1];
+ methodBoxes[0] = new MemberBox((Method)value);
+ } else {
+ ObjArray overloadedMethods = (ObjArray)value;
+ int N = overloadedMethods.size();
+ if (N < 2) Kit.codeBug();
+ methodBoxes = new MemberBox[N];
+ for (int i = 0; i != N; ++i) {
+ Method method = (Method)overloadedMethods.get(i);
+ methodBoxes[i] = new MemberBox(method);
+ }
+ }
+ NativeJavaMethod fun = new NativeJavaMethod(methodBoxes);
+ if (scope != null) {
+ ScriptRuntime.setFunctionProtoAndParent(fun, scope);
+ }
+ ht.put(name, fun);
+ }
+ }
+
+ // Reflect fields.
+ Field[] fields = getAccessibleFields();
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ String name = field.getName();
+ int mods = field.getModifiers();
+ if (!includePrivate && !Modifier.isPublic(mods)) {
+ continue;
+ }
+ try {
+ boolean isStatic = Modifier.isStatic(mods);
+ Map<String,Object> ht = isStatic ? staticMembers : members;
+ Object member = ht.get(name);
+ if (member == null) {
+ ht.put(name, field);
+ } else if (member instanceof NativeJavaMethod) {
+ NativeJavaMethod method = (NativeJavaMethod) member;
+ FieldAndMethods fam
+ = new FieldAndMethods(scope, method.methods, field);
+ Map<String,FieldAndMethods> fmht = isStatic ? staticFieldAndMethods
+ : fieldAndMethods;
+ if (fmht == null) {
+ fmht = new HashMap<String,FieldAndMethods>();
+ if (isStatic) {
+ staticFieldAndMethods = fmht;
+ } else {
+ fieldAndMethods = fmht;
+ }
+ }
+ fmht.put(name, fam);
+ ht.put(name, fam);
+ } else if (member instanceof Field) {
+ Field oldField = (Field) member;
+ // If this newly reflected field shadows an inherited field,
+ // then replace it. Otherwise, since access to the field
+ // would be ambiguous from Java, no field should be
+ // reflected.
+ // For now, the first field found wins, unless another field
+ // explicitly shadows it.
+ if (oldField.getDeclaringClass().
+ isAssignableFrom(field.getDeclaringClass()))
+ {
+ ht.put(name, field);
+ }
+ } else {
+ // "unknown member type"
+ Kit.codeBug();
+ }
+ } catch (SecurityException e) {
+ // skip this field
+ Context.reportWarning("Could not access field "
+ + name + " of class " + cl.getName() +
+ " due to lack of privileges.");
+ }
+ }
+
+ // Create bean properties from corresponding get/set methods first for
+ // static members and then for instance members
+ for (int tableCursor = 0; tableCursor != 2; ++tableCursor) {
+ boolean isStatic = (tableCursor == 0);
+ Map<String,Object> ht = isStatic ? staticMembers : members;
+
+ Map<String,BeanProperty> toAdd = new HashMap<String,BeanProperty>();
+
+ // Now, For each member, make "bean" properties.
+ for (String name: ht.keySet()) {
+ // Is this a getter?
+ boolean memberIsGetMethod = name.startsWith("get");
+ boolean memberIsSetMethod = name.startsWith("set");
+ boolean memberIsIsMethod = name.startsWith("is");
+ if (memberIsGetMethod || memberIsIsMethod
+ || memberIsSetMethod) {
+ // Double check name component.
+ String nameComponent
+ = name.substring(memberIsIsMethod ? 2 : 3);
+ if (nameComponent.length() == 0)
+ continue;
+
+ // Make the bean property name.
+ String beanPropertyName = nameComponent;
+ char ch0 = nameComponent.charAt(0);
+ if (Character.isUpperCase(ch0)) {
+ if (nameComponent.length() == 1) {
+ beanPropertyName = nameComponent.toLowerCase();
+ } else {
+ char ch1 = nameComponent.charAt(1);
+ if (!Character.isUpperCase(ch1)) {
+ beanPropertyName = Character.toLowerCase(ch0)
+ +nameComponent.substring(1);
+ }
+ }
+ }
+
+ // If we already have a member by this name, don't do this
+ // property.
+ if (toAdd.containsKey(beanPropertyName))
+ continue;
+ Object v = ht.get(beanPropertyName);
+ if (v != null) {
+ // A private field shouldn't mask a public getter/setter
+ if (!includePrivate || !(v instanceof Member) ||
+ !Modifier.isPrivate(((Member)v).getModifiers()))
+
+ {
+ continue;
+ }
+ }
+
+ // Find the getter method, or if there is none, the is-
+ // method.
+ MemberBox getter = null;
+ getter = findGetter(isStatic, ht, "get", nameComponent);
+ // If there was no valid getter, check for an is- method.
+ if (getter == null) {
+ getter = findGetter(isStatic, ht, "is", nameComponent);
+ }
+
+ // setter
+ MemberBox setter = null;
+ NativeJavaMethod setters = null;
+ String setterName = "set".concat(nameComponent);
+
+ if (ht.containsKey(setterName)) {
+ // Is this value a method?
+ Object member = ht.get(setterName);
+ if (member instanceof NativeJavaMethod) {
+ NativeJavaMethod njmSet = (NativeJavaMethod)member;
+ if (getter != null) {
+ // We have a getter. Now, do we have a matching
+ // setter?
+ Class<?> type = getter.method().getReturnType();
+ setter = extractSetMethod(type, njmSet.methods,
+ isStatic);
+ } else {
+ // No getter, find any set method
+ setter = extractSetMethod(njmSet.methods,
+ isStatic);
+ }
+ if (njmSet.methods.length > 1) {
+ setters = njmSet;
+ }
+ }
+ }
+ // Make the property.
+ BeanProperty bp = new BeanProperty(getter, setter,
+ setters);
+ toAdd.put(beanPropertyName, bp);
+ }
+ }
+
+ // Add the new bean properties.
+ for (String key: toAdd.keySet()) {
+ Object value = toAdd.get(key);
+ ht.put(key, value);
+ }
+ }
+
+ // Reflect constructors
+ Constructor<?>[] constructors = getAccessibleConstructors();
+ ctors = new MemberBox[constructors.length];
+ for (int i = 0; i != constructors.length; ++i) {
+ ctors[i] = new MemberBox(constructors[i]);
+ }
+ }
+
+ private Constructor<?>[] getAccessibleConstructors()
+ {
+ // The JVM currently doesn't allow changing access on java.lang.Class
+ // constructors, so don't try
+ if (includePrivate && cl != ScriptRuntime.ClassClass) {
+ try {
+ Constructor<?>[] cons = cl.getDeclaredConstructors();
+ Constructor.setAccessible(cons, true);
+
+ return cons;
+ } catch (SecurityException e) {
+ // Fall through to !includePrivate case
+ Context.reportWarning("Could not access constructor " +
+ " of class " + cl.getName() +
+ " due to lack of privileges.");
+ }
+ }
+ return cl.getConstructors();
+ }
+
+ private Field[] getAccessibleFields() {
+ if (includePrivate) {
+ try {
+ List<Field> fieldsList = new ArrayList<Field>();
+ Class<?> currentClass = cl;
+
+ while (currentClass != null) {
+ // get all declared fields in this class, make them
+ // accessible, and save
+ Field[] declared = currentClass.getDeclaredFields();
+ for (int i = 0; i < declared.length; i++) {
+ declared[i].setAccessible(true);
+ fieldsList.add(declared[i]);
+ }
+ // walk up superclass chain. no need to deal specially with
+ // interfaces, since they can't have fields
+ currentClass = currentClass.getSuperclass();
+ }
+
+ return fieldsList.toArray(new Field[fieldsList.size()]);
+ } catch (SecurityException e) {
+ // fall through to !includePrivate case
+ }
+ }
+ return cl.getFields();
+ }
+
+ private MemberBox findGetter(boolean isStatic, Map<String,Object> ht, String prefix,
+ String propertyName)
+ {
+ String getterName = prefix.concat(propertyName);
+ if (ht.containsKey(getterName)) {
+ // Check that the getter is a method.
+ Object member = ht.get(getterName);
+ if (member instanceof NativeJavaMethod) {
+ NativeJavaMethod njmGet = (NativeJavaMethod) member;
+ return extractGetMethod(njmGet.methods, isStatic);
+ }
+ }
+ return null;
+ }
+
+ private static MemberBox extractGetMethod(MemberBox[] methods,
+ boolean isStatic)
+ {
+ // Inspect the list of all MemberBox for the only one having no
+ // parameters
+ for (int methodIdx = 0; methodIdx < methods.length; methodIdx++) {
+ MemberBox method = methods[methodIdx];
+ // Does getter method have an empty parameter list with a return
+ // value (eg. a getSomething() or isSomething())?
+ if (method.argTypes.length == 0
+ && (!isStatic || method.isStatic()))
+ {
+ Class<?> type = method.method().getReturnType();
+ if (type != Void.TYPE) {
+ return method;
+ }
+ break;
+ }
+ }
+ return null;
+ }
+
+ private static MemberBox extractSetMethod(Class<?> type, MemberBox[] methods,
+ boolean isStatic)
+ {
+ //
+ // Note: it may be preferable to allow NativeJavaMethod.findFunction()
+ // to find the appropriate setter; unfortunately, it requires an
+ // instance of the target arg to determine that.
+ //
+
+ // Make two passes: one to find a method with direct type assignment,
+ // and one to find a widening conversion.
+ for (int pass = 1; pass <= 2; ++pass) {
+ for (int i = 0; i < methods.length; ++i) {
+ MemberBox method = methods[i];
+ if (!isStatic || method.isStatic()) {
+ Class<?>[] params = method.argTypes;
+ if (params.length == 1) {
+ if (pass == 1) {
+ if (params[0] == type) {
+ return method;
+ }
+ } else {
+ if (pass != 2) Kit.codeBug();
+ if (params[0].isAssignableFrom(type)) {
+ return method;
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private static MemberBox extractSetMethod(MemberBox[] methods,
+ boolean isStatic)
+ {
+
+ for (int i = 0; i < methods.length; ++i) {
+ MemberBox method = methods[i];
+ if (!isStatic || method.isStatic()) {
+ if (method.method().getReturnType() == Void.TYPE) {
+ if (method.argTypes.length == 1) {
+ return method;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ Map<String,FieldAndMethods> getFieldAndMethodsObjects(Scriptable scope,
+ Object javaObject, boolean isStatic)
+ {
+ Map<String,FieldAndMethods> ht = isStatic ? staticFieldAndMethods : fieldAndMethods;
+ if (ht == null)
+ return null;
+ int len = ht.size();
+ Map<String,FieldAndMethods> result = new HashMap<String,FieldAndMethods>(len);
+ for (FieldAndMethods fam: ht.values()) {
+ FieldAndMethods famNew = new FieldAndMethods(scope, fam.methods,
+ fam.field);
+ famNew.javaObject = javaObject;
+ result.put(fam.field.getName(), famNew);
+ }
+ return result;
+ }
+
+ static JavaMembers lookupClass(Scriptable scope, Class<?> dynamicType,
+ Class<?> staticType, boolean includeProtected)
+ {
+ JavaMembers members;
+ scope = ScriptableObject.getTopLevelScope(scope);
+ ClassCache cache = ClassCache.get(scope);
+ Map<Class<?>,JavaMembers> ct = cache.getClassCacheMap();
+
+ Class<?> cl = dynamicType;
+ for (;;) {
+ members = ct.get(cl);
+ if (members != null) {
+ return members;
+ }
+ try {
+ members = new JavaMembers(scope, cl, includeProtected);
+ break;
+ } catch (SecurityException e) {
+ // Reflection may fail for objects that are in a restricted
+ // access package (e.g. sun.*). If we get a security
+ // exception, try again with the static type if it is interface.
+ // Otherwise, try superclass
+ if (staticType != null && staticType.isInterface()) {
+ cl = staticType;
+ staticType = null; // try staticType only once
+ } else {
+ Class<?> parent = cl.getSuperclass();
+ if (parent == null) {
+ if (cl.isInterface()) {
+ // last resort after failed staticType interface
+ parent = ScriptRuntime.ObjectClass;
+ } else {
+ throw e;
+ }
+ }
+ cl = parent;
+ }
+ }
+ }
+
+ if (cache.isCachingEnabled())
+ ct.put(cl, members);
+ return members;
+ }
+
+ RuntimeException reportMemberNotFound(String memberName)
+ {
+ return Context.reportRuntimeError2(
+ "msg.java.member.not.found", cl.getName(), memberName);
+ }
+
+ private Class<?> cl;
+ private Map<String,Object> members;
+ private Map<String,FieldAndMethods> fieldAndMethods;
+ private Map<String,Object> staticMembers;
+ private Map<String,FieldAndMethods> staticFieldAndMethods;
+ MemberBox[] ctors;
+ private boolean includePrivate;
+}
+
+class BeanProperty
+{
+ BeanProperty(MemberBox getter, MemberBox setter, NativeJavaMethod setters)
+ {
+ this.getter = getter;
+ this.setter = setter;
+ this.setters = setters;
+ }
+
+ MemberBox getter;
+ MemberBox setter;
+ NativeJavaMethod setters;
+}
+
+class FieldAndMethods extends NativeJavaMethod
+{
+ static final long serialVersionUID = -9222428244284796755L;
+
+ FieldAndMethods(Scriptable scope, MemberBox[] methods, Field field)
+ {
+ super(methods);
+ this.field = field;
+ setParentScope(scope);
+ setPrototype(ScriptableObject.getFunctionPrototype(scope));
+ }
+
+ @Override
+ public Object getDefaultValue(Class<?> hint)
+ {
+ if (hint == ScriptRuntime.FunctionClass)
+ return this;
+ Object rval;
+ Class<?> type;
+ try {
+ rval = field.get(javaObject);
+ type = field.getType();
+ } catch (IllegalAccessException accEx) {
+ throw Context.reportRuntimeError1(
+ "msg.java.internal.private", field.getName());
+ }
+ Context cx = Context.getContext();
+ rval = cx.getWrapFactory().wrap(cx, this, rval, type);
+ if (rval instanceof Scriptable) {
+ rval = ((Scriptable) rval).getDefaultValue(hint);
+ }
+ return rval;
+ }
+
+ Field field;
+ Object javaObject;
+}
diff --git a/src/org/mozilla/javascript/JavaScriptException.java b/src/org/mozilla/javascript/JavaScriptException.java
new file mode 100644
index 0000000..99d696e
--- /dev/null
+++ b/src/org/mozilla/javascript/JavaScriptException.java
@@ -0,0 +1,118 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Bojan Cekrlic
+ * Hannes Wallnoefer
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * Java reflection of JavaScript exceptions.
+ * Instances of this class are thrown by the JavaScript 'throw' keyword.
+ *
+ * @author Mike McCabe
+ */
+public class JavaScriptException extends RhinoException
+{
+ static final long serialVersionUID = -7666130513694669293L;
+
+ /**
+ * @deprecated
+ * Use {@link WrappedException#WrappedException(Throwable)} to report
+ * exceptions in Java code.
+ */
+ public JavaScriptException(Object value)
+ {
+ this(value, "", 0);
+ }
+
+ /**
+ * Create a JavaScript exception wrapping the given JavaScript value
+ *
+ * @param value the JavaScript value thrown.
+ */
+ public JavaScriptException(Object value, String sourceName, int lineNumber)
+ {
+ recordErrorOrigin(sourceName, lineNumber, null, 0);
+ this.value = value;
+ }
+
+ @Override
+ public String details()
+ {
+ try {
+ return ScriptRuntime.toString(value);
+ } catch (RuntimeException rte) {
+ // ScriptRuntime.toString may throw a RuntimeException
+ if (value == null) {
+ return "null";
+ } else if (value instanceof Scriptable) {
+ return ScriptRuntime.defaultObjectToString((Scriptable)value);
+ } else {
+ return value.toString();
+ }
+ }
+ }
+
+ /**
+ * @return the value wrapped by this exception
+ */
+ public Object getValue()
+ {
+ return value;
+ }
+
+ /**
+ * @deprecated Use {@link RhinoException#sourceName()} from the super class.
+ */
+ public String getSourceName()
+ {
+ return sourceName();
+ }
+
+ /**
+ * @deprecated Use {@link RhinoException#lineNumber()} from the super class.
+ */
+ public int getLineNumber()
+ {
+ return lineNumber();
+ }
+
+ private Object value;
+}
diff --git a/src/org/mozilla/javascript/Kit.java b/src/org/mozilla/javascript/Kit.java
new file mode 100644
index 0000000..6c9b696
--- /dev/null
+++ b/src/org/mozilla/javascript/Kit.java
@@ -0,0 +1,455 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov, igor at fastmail.fm
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * Collection of utilities
+ */
+
+public class Kit
+{
+ /**
+ * Reflection of Throwable.initCause(Throwable) from JDK 1.4
+ * or nul if it is not available.
+ */
+ private static Method Throwable_initCause = null;
+
+ static {
+ // Are we running on a JDK 1.4 or later system?
+ try {
+ Class<?> ThrowableClass = Kit.classOrNull("java.lang.Throwable");
+ Class<?>[] signature = { ThrowableClass };
+ Throwable_initCause
+ = ThrowableClass.getMethod("initCause", signature);
+ } catch (Exception ex) {
+ // Assume any exceptions means the method does not exist.
+ }
+ }
+
+ public static Class<?> classOrNull(String className)
+ {
+ try {
+ return Class.forName(className);
+ } catch (ClassNotFoundException ex) {
+ } catch (SecurityException ex) {
+ } catch (LinkageError ex) {
+ } catch (IllegalArgumentException e) {
+ // Can be thrown if name has characters that a class name
+ // can not contain
+ }
+ return null;
+ }
+
+ /**
+ * Attempt to load the class of the given name. Note that the type parameter
+ * isn't checked.
+ */
+ public static Class<?> classOrNull(ClassLoader loader, String className)
+ {
+ try {
+ return loader.loadClass(className);
+ } catch (ClassNotFoundException ex) {
+ } catch (SecurityException ex) {
+ } catch (LinkageError ex) {
+ } catch (IllegalArgumentException e) {
+ // Can be thrown if name has characters that a class name
+ // can not contain
+ }
+ return null;
+ }
+
+ static Object newInstanceOrNull(Class<?> cl)
+ {
+ try {
+ return cl.newInstance();
+ } catch (SecurityException x) {
+ } catch (LinkageError ex) {
+ } catch (InstantiationException x) {
+ } catch (IllegalAccessException x) {
+ }
+ return null;
+ }
+
+ /**
+ * Check that testClass is accessible from the given loader.
+ */
+ static boolean testIfCanLoadRhinoClasses(ClassLoader loader)
+ {
+ Class<?> testClass = ScriptRuntime.ContextFactoryClass;
+ Class<?> x = Kit.classOrNull(loader, testClass.getName());
+ if (x != testClass) {
+ // The check covers the case when x == null =>
+ // loader does not know about testClass or the case
+ // when x != null && x != testClass =>
+ // loader loads a class unrelated to testClass
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * If initCause methods exists in Throwable, call
+ * <tt>ex.initCause(cause)</tt> or otherwise do nothing.
+ * @return The <tt>ex</tt> argument.
+ */
+ public static RuntimeException initCause(RuntimeException ex,
+ Throwable cause)
+ {
+ if (Throwable_initCause != null) {
+ Object[] args = { cause };
+ try {
+ Throwable_initCause.invoke(ex, args);
+ } catch (Exception e) {
+ // Ignore any exceptions
+ }
+ }
+ return ex;
+ }
+
+ /**
+ * If character <tt>c</tt> is a hexadecimal digit, return
+ * <tt>accumulator</tt> * 16 plus corresponding
+ * number. Otherise return -1.
+ */
+ public static int xDigitToInt(int c, int accumulator)
+ {
+ check: {
+ // Use 0..9 < A..Z < a..z
+ if (c <= '9') {
+ c -= '0';
+ if (0 <= c) { break check; }
+ } else if (c <= 'F') {
+ if ('A' <= c) {
+ c -= ('A' - 10);
+ break check;
+ }
+ } else if (c <= 'f') {
+ if ('a' <= c) {
+ c -= ('a' - 10);
+ break check;
+ }
+ }
+ return -1;
+ }
+ return (accumulator << 4) | c;
+ }
+
+ /**
+ * Add <i>listener</i> to <i>bag</i> of listeners.
+ * The function does not modify <i>bag</i> and return a new collection
+ * containing <i>listener</i> and all listeners from <i>bag</i>.
+ * Bag without listeners always represented as the null value.
+ * <p>
+ * Usage example:
+ * <pre>
+ * private volatile Object changeListeners;
+ *
+ * public void addMyListener(PropertyChangeListener l)
+ * {
+ * synchronized (this) {
+ * changeListeners = Kit.addListener(changeListeners, l);
+ * }
+ * }
+ *
+ * public void removeTextListener(PropertyChangeListener l)
+ * {
+ * synchronized (this) {
+ * changeListeners = Kit.removeListener(changeListeners, l);
+ * }
+ * }
+ *
+ * public void fireChangeEvent(Object oldValue, Object newValue)
+ * {
+ * // Get immune local copy
+ * Object listeners = changeListeners;
+ * if (listeners != null) {
+ * PropertyChangeEvent e = new PropertyChangeEvent(
+ * this, "someProperty" oldValue, newValue);
+ * for (int i = 0; ; ++i) {
+ * Object l = Kit.getListener(listeners, i);
+ * if (l == null)
+ * break;
+ * ((PropertyChangeListener)l).propertyChange(e);
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * @param listener Listener to add to <i>bag</i>
+ * @param bag Current collection of listeners.
+ * @return A new bag containing all listeners from <i>bag</i> and
+ * <i>listener</i>.
+ * @see #removeListener(Object bag, Object listener)
+ * @see #getListener(Object bag, int index)
+ */
+ public static Object addListener(Object bag, Object listener)
+ {
+ if (listener == null) throw new IllegalArgumentException();
+ if (listener instanceof Object[]) throw new IllegalArgumentException();
+
+ if (bag == null) {
+ bag = listener;
+ } else if (!(bag instanceof Object[])) {
+ bag = new Object[] { bag, listener };
+ } else {
+ Object[] array = (Object[])bag;
+ int L = array.length;
+ // bag has at least 2 elements if it is array
+ if (L < 2) throw new IllegalArgumentException();
+ Object[] tmp = new Object[L + 1];
+ System.arraycopy(array, 0, tmp, 0, L);
+ tmp[L] = listener;
+ bag = tmp;
+ }
+
+ return bag;
+ }
+
+ /**
+ * Remove <i>listener</i> from <i>bag</i> of listeners.
+ * The function does not modify <i>bag</i> and return a new collection
+ * containing all listeners from <i>bag</i> except <i>listener</i>.
+ * If <i>bag</i> does not contain <i>listener</i>, the function returns
+ * <i>bag</i>.
+ * <p>
+ * For usage example, see {@link #addListener(Object bag, Object listener)}.
+ *
+ * @param listener Listener to remove from <i>bag</i>
+ * @param bag Current collection of listeners.
+ * @return A new bag containing all listeners from <i>bag</i> except
+ * <i>listener</i>.
+ * @see #addListener(Object bag, Object listener)
+ * @see #getListener(Object bag, int index)
+ */
+ public static Object removeListener(Object bag, Object listener)
+ {
+ if (listener == null) throw new IllegalArgumentException();
+ if (listener instanceof Object[]) throw new IllegalArgumentException();
+
+ if (bag == listener) {
+ bag = null;
+ } else if (bag instanceof Object[]) {
+ Object[] array = (Object[])bag;
+ int L = array.length;
+ // bag has at least 2 elements if it is array
+ if (L < 2) throw new IllegalArgumentException();
+ if (L == 2) {
+ if (array[1] == listener) {
+ bag = array[0];
+ } else if (array[0] == listener) {
+ bag = array[1];
+ }
+ } else {
+ int i = L;
+ do {
+ --i;
+ if (array[i] == listener) {
+ Object[] tmp = new Object[L - 1];
+ System.arraycopy(array, 0, tmp, 0, i);
+ System.arraycopy(array, i + 1, tmp, i, L - (i + 1));
+ bag = tmp;
+ break;
+ }
+ } while (i != 0);
+ }
+ }
+
+ return bag;
+ }
+
+ /**
+ * Get listener at <i>index</i> position in <i>bag</i> or null if
+ * <i>index</i> equals to number of listeners in <i>bag</i>.
+ * <p>
+ * For usage example, see {@link #addListener(Object bag, Object listener)}.
+ *
+ * @param bag Current collection of listeners.
+ * @param index Index of the listener to access.
+ * @return Listener at the given index or null.
+ * @see #addListener(Object bag, Object listener)
+ * @see #removeListener(Object bag, Object listener)
+ */
+ public static Object getListener(Object bag, int index)
+ {
+ if (index == 0) {
+ if (bag == null)
+ return null;
+ if (!(bag instanceof Object[]))
+ return bag;
+ Object[] array = (Object[])bag;
+ // bag has at least 2 elements if it is array
+ if (array.length < 2) throw new IllegalArgumentException();
+ return array[0];
+ } else if (index == 1) {
+ if (!(bag instanceof Object[])) {
+ if (bag == null) throw new IllegalArgumentException();
+ return null;
+ }
+ Object[] array = (Object[])bag;
+ // the array access will check for index on its own
+ return array[1];
+ } else {
+ // bag has to array
+ Object[] array = (Object[])bag;
+ int L = array.length;
+ if (L < 2) throw new IllegalArgumentException();
+ if (index == L)
+ return null;
+ return array[index];
+ }
+ }
+
+ static Object initHash(Map<Object,Object> h, Object key, Object initialValue)
+ {
+ synchronized (h) {
+ Object current = h.get(key);
+ if (current == null) {
+ h.put(key, initialValue);
+ } else {
+ initialValue = current;
+ }
+ }
+ return initialValue;
+ }
+
+ private final static class ComplexKey
+ {
+ private Object key1;
+ private Object key2;
+ private int hash;
+
+ ComplexKey(Object key1, Object key2)
+ {
+ this.key1 = key1;
+ this.key2 = key2;
+ }
+
+ @Override
+ public boolean equals(Object anotherObj)
+ {
+ if (!(anotherObj instanceof ComplexKey))
+ return false;
+ ComplexKey another = (ComplexKey)anotherObj;
+ return key1.equals(another.key1) && key2.equals(another.key2);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ if (hash == 0) {
+ hash = key1.hashCode() ^ key2.hashCode();
+ }
+ return hash;
+ }
+ }
+
+ public static Object makeHashKeyFromPair(Object key1, Object key2)
+ {
+ if (key1 == null) throw new IllegalArgumentException();
+ if (key2 == null) throw new IllegalArgumentException();
+ return new ComplexKey(key1, key2);
+ }
+
+ public static String readReader(Reader r)
+ throws IOException
+ {
+ char[] buffer = new char[512];
+ int cursor = 0;
+ for (;;) {
+ int n = r.read(buffer, cursor, buffer.length - cursor);
+ if (n < 0) { break; }
+ cursor += n;
+ if (cursor == buffer.length) {
+ char[] tmp = new char[buffer.length * 2];
+ System.arraycopy(buffer, 0, tmp, 0, cursor);
+ buffer = tmp;
+ }
+ }
+ return new String(buffer, 0, cursor);
+ }
+
+ public static byte[] readStream(InputStream is, int initialBufferCapacity)
+ throws IOException
+ {
+ if (initialBufferCapacity <= 0) {
+ throw new IllegalArgumentException(
+ "Bad initialBufferCapacity: "+initialBufferCapacity);
+ }
+ byte[] buffer = new byte[initialBufferCapacity];
+ int cursor = 0;
+ for (;;) {
+ int n = is.read(buffer, cursor, buffer.length - cursor);
+ if (n < 0) { break; }
+ cursor += n;
+ if (cursor == buffer.length) {
+ byte[] tmp = new byte[buffer.length * 2];
+ System.arraycopy(buffer, 0, tmp, 0, cursor);
+ buffer = tmp;
+ }
+ }
+ if (cursor != buffer.length) {
+ byte[] tmp = new byte[cursor];
+ System.arraycopy(buffer, 0, tmp, 0, cursor);
+ buffer = tmp;
+ }
+ return buffer;
+ }
+
+ /**
+ * Throws RuntimeException to indicate failed assertion.
+ * The function never returns and its return type is RuntimeException
+ * only to be able to write <tt>throw Kit.codeBug()</tt> if plain
+ * <tt>Kit.codeBug()</tt> triggers unreachable code error.
+ */
+ public static RuntimeException codeBug()
+ throws RuntimeException
+ {
+ RuntimeException ex = new IllegalStateException("FAILED ASSERTION");
+ // Print stack trace ASAP
+ ex.printStackTrace(System.err);
+ throw ex;
+ }
+}
+
diff --git a/src/org/mozilla/javascript/LazilyLoadedCtor.java b/src/org/mozilla/javascript/LazilyLoadedCtor.java
new file mode 100644
index 0000000..d6df9e2
--- /dev/null
+++ b/src/org/mozilla/javascript/LazilyLoadedCtor.java
@@ -0,0 +1,141 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.*;
+
+/**
+ * Avoid loading classes unless they are used.
+ *
+ * <p> This improves startup time and average memory usage.
+ */
+public final class LazilyLoadedCtor implements java.io.Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private static final int STATE_BEFORE_INIT = 0;
+ private static final int STATE_INITIALIZING = 1;
+ private static final int STATE_WITH_VALUE = 2;
+
+ private final ScriptableObject scope;
+ private final String propertyName;
+ private final String className;
+ private final boolean sealed;
+ private Object initializedValue;
+ private int state;
+
+ public LazilyLoadedCtor(ScriptableObject scope, String propertyName,
+ String className, boolean sealed)
+ {
+
+ this.scope = scope;
+ this.propertyName = propertyName;
+ this.className = className;
+ this.sealed = sealed;
+ this.state = STATE_BEFORE_INIT;
+
+ scope.addLazilyInitializedValue(propertyName, 0, this,
+ ScriptableObject.DONTENUM);
+ }
+
+ void init()
+ {
+ synchronized (this) {
+ if (state == STATE_INITIALIZING)
+ throw new IllegalStateException(
+ "Recursive initialization for "+propertyName);
+ if (state == STATE_BEFORE_INIT) {
+ state = STATE_INITIALIZING;
+ // Set value now to have something to set in finally block if
+ // buildValue throws.
+ Object value = Scriptable.NOT_FOUND;
+ try {
+ value = buildValue();
+ } finally {
+ initializedValue = value;
+ state = STATE_WITH_VALUE;
+ }
+ }
+ }
+ }
+
+ Object getValue()
+ {
+ if (state != STATE_WITH_VALUE)
+ throw new IllegalStateException(propertyName);
+ return initializedValue;
+ }
+
+ private Object buildValue()
+ {
+ Class<? extends Scriptable> cl = cast(Kit.classOrNull(className));
+ if (cl != null) {
+ try {
+ Object value = ScriptableObject.buildClassCtor(scope, cl,
+ sealed, false);
+ if (value != null) {
+ return value;
+ }
+ else {
+ // cl has own static initializer which is expected
+ // to set the property on its own.
+ value = scope.get(propertyName, scope);
+ if (value != Scriptable.NOT_FOUND)
+ return value;
+ }
+ } catch (InvocationTargetException ex) {
+ Throwable target = ex.getTargetException();
+ if (target instanceof RuntimeException) {
+ throw (RuntimeException)target;
+ }
+ } catch (RhinoException ex) {
+ } catch (InstantiationException ex) {
+ } catch (IllegalAccessException ex) {
+ } catch (SecurityException ex) {
+ }
+ }
+ return Scriptable.NOT_FOUND;
+ }
+
+ @SuppressWarnings({"unchecked"})
+ private Class<? extends Scriptable> cast(Class<?> cl) {
+ return (Class<? extends Scriptable>)cl;
+ }
+
+}
diff --git a/src/org/mozilla/javascript/MemberBox.java b/src/org/mozilla/javascript/MemberBox.java
new file mode 100644
index 0000000..8a50fe8
--- /dev/null
+++ b/src/org/mozilla/javascript/MemberBox.java
@@ -0,0 +1,372 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ * Felix Meschberger
+ * Norris Boyd
+ * Ulrike Mueller <umueller at demandware.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.*;
+import java.io.*;
+
+/**
+ * Wrappper class for Method and Constructor instances to cache
+ * getParameterTypes() results, recover from IllegalAccessException
+ * in some cases and provide serialization support.
+ *
+ * @author Igor Bukanov
+ */
+
+final class MemberBox implements Serializable
+{
+ static final long serialVersionUID = 6358550398665688245L;
+
+ private transient Member memberObject;
+ transient Class<?>[] argTypes;
+ transient Object delegateTo;
+ transient boolean vararg;
+
+
+ MemberBox(Method method)
+ {
+ init(method);
+ }
+
+ MemberBox(Constructor<?> constructor)
+ {
+ init(constructor);
+ }
+
+ private void init(Method method)
+ {
+ this.memberObject = method;
+ this.argTypes = method.getParameterTypes();
+ this.vararg = VMBridge.instance.isVarArgs(method);
+ }
+
+ private void init(Constructor<?> constructor)
+ {
+ this.memberObject = constructor;
+ this.argTypes = constructor.getParameterTypes();
+ this.vararg = VMBridge.instance.isVarArgs(constructor);
+ }
+
+ Method method()
+ {
+ return (Method)memberObject;
+ }
+
+ Constructor<?> ctor()
+ {
+ return (Constructor<?>)memberObject;
+ }
+
+ Member member()
+ {
+ return memberObject;
+ }
+
+ boolean isMethod()
+ {
+ return memberObject instanceof Method;
+ }
+
+ boolean isCtor()
+ {
+ return memberObject instanceof Constructor;
+ }
+
+ boolean isStatic()
+ {
+ return Modifier.isStatic(memberObject.getModifiers());
+ }
+
+ String getName()
+ {
+ return memberObject.getName();
+ }
+
+ Class<?> getDeclaringClass()
+ {
+ return memberObject.getDeclaringClass();
+ }
+
+ String toJavaDeclaration()
+ {
+ StringBuffer sb = new StringBuffer();
+ if (isMethod()) {
+ Method method = method();
+ sb.append(method.getReturnType());
+ sb.append(' ');
+ sb.append(method.getName());
+ } else {
+ Constructor<?> ctor = ctor();
+ String name = ctor.getDeclaringClass().getName();
+ int lastDot = name.lastIndexOf('.');
+ if (lastDot >= 0) {
+ name = name.substring(lastDot + 1);
+ }
+ sb.append(name);
+ }
+ sb.append(JavaMembers.liveConnectSignature(argTypes));
+ return sb.toString();
+ }
+
+ @Override
+ public String toString()
+ {
+ return memberObject.toString();
+ }
+
+ Object invoke(Object target, Object[] args)
+ {
+ Method method = method();
+ try {
+ try {
+ return method.invoke(target, args);
+ } catch (IllegalAccessException ex) {
+ Method accessible = searchAccessibleMethod(method, argTypes);
+ if (accessible != null) {
+ memberObject = accessible;
+ method = accessible;
+ } else {
+ if (!VMBridge.instance.tryToMakeAccessible(method)) {
+ throw Context.throwAsScriptRuntimeEx(ex);
+ }
+ }
+ // Retry after recovery
+ return method.invoke(target, args);
+ }
+ } catch (InvocationTargetException ite) {
+ // Must allow ContinuationPending exceptions to propagate unhindered
+ Throwable e = ite;
+ do {
+ e = ((InvocationTargetException) e).getTargetException();
+ } while ((e instanceof InvocationTargetException));
+ if (e instanceof ContinuationPending)
+ throw (ContinuationPending) e;
+ throw Context.throwAsScriptRuntimeEx(e);
+ } catch (Exception ex) {
+ throw Context.throwAsScriptRuntimeEx(ex);
+ }
+ }
+
+ Object newInstance(Object[] args)
+ {
+ Constructor<?> ctor = ctor();
+ try {
+ try {
+ return ctor.newInstance(args);
+ } catch (IllegalAccessException ex) {
+ if (!VMBridge.instance.tryToMakeAccessible(ctor)) {
+ throw Context.throwAsScriptRuntimeEx(ex);
+ }
+ }
+ return ctor.newInstance(args);
+ } catch (Exception ex) {
+ throw Context.throwAsScriptRuntimeEx(ex);
+ }
+ }
+
+ private static Method searchAccessibleMethod(Method method, Class<?>[] params)
+ {
+ int modifiers = method.getModifiers();
+ if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
+ Class<?> c = method.getDeclaringClass();
+ if (!Modifier.isPublic(c.getModifiers())) {
+ String name = method.getName();
+ Class<?>[] intfs = c.getInterfaces();
+ for (int i = 0, N = intfs.length; i != N; ++i) {
+ Class<?> intf = intfs[i];
+ if (Modifier.isPublic(intf.getModifiers())) {
+ try {
+ return intf.getMethod(name, params);
+ } catch (NoSuchMethodException ex) {
+ } catch (SecurityException ex) { }
+ }
+ }
+ for (;;) {
+ c = c.getSuperclass();
+ if (c == null) { break; }
+ if (Modifier.isPublic(c.getModifiers())) {
+ try {
+ Method m = c.getMethod(name, params);
+ int mModifiers = m.getModifiers();
+ if (Modifier.isPublic(mModifiers)
+ && !Modifier.isStatic(mModifiers))
+ {
+ return m;
+ }
+ } catch (NoSuchMethodException ex) {
+ } catch (SecurityException ex) { }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+ Member member = readMember(in);
+ if (member instanceof Method) {
+ init((Method)member);
+ } else {
+ init((Constructor<?>)member);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out)
+ throws IOException
+ {
+ out.defaultWriteObject();
+ writeMember(out, memberObject);
+ }
+
+ /**
+ * Writes a Constructor or Method object.
+ *
+ * Methods and Constructors are not serializable, so we must serialize
+ * information about the class, the name, and the parameters and
+ * recreate upon deserialization.
+ */
+ private static void writeMember(ObjectOutputStream out, Member member)
+ throws IOException
+ {
+ if (member == null) {
+ out.writeBoolean(false);
+ return;
+ }
+ out.writeBoolean(true);
+ if (!(member instanceof Method || member instanceof Constructor))
+ throw new IllegalArgumentException("not Method or Constructor");
+ out.writeBoolean(member instanceof Method);
+ out.writeObject(member.getName());
+ out.writeObject(member.getDeclaringClass());
+ if (member instanceof Method) {
+ writeParameters(out, ((Method) member).getParameterTypes());
+ } else {
+ writeParameters(out, ((Constructor<?>) member).getParameterTypes());
+ }
+ }
+
+ /**
+ * Reads a Method or a Constructor from the stream.
+ */
+ private static Member readMember(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ if (!in.readBoolean())
+ return null;
+ boolean isMethod = in.readBoolean();
+ String name = (String) in.readObject();
+ Class<?> declaring = (Class<?>) in.readObject();
+ Class<?>[] parms = readParameters(in);
+ try {
+ if (isMethod) {
+ return declaring.getMethod(name, parms);
+ } else {
+ return declaring.getConstructor(parms);
+ }
+ } catch (NoSuchMethodException e) {
+ throw new IOException("Cannot find member: " + e);
+ }
+ }
+
+ private static final Class<?>[] primitives = {
+ Boolean.TYPE,
+ Byte.TYPE,
+ Character.TYPE,
+ Double.TYPE,
+ Float.TYPE,
+ Integer.TYPE,
+ Long.TYPE,
+ Short.TYPE,
+ Void.TYPE
+ };
+
+ /**
+ * Writes an array of parameter types to the stream.
+ *
+ * Requires special handling because primitive types cannot be
+ * found upon deserialization by the default Java implementation.
+ */
+ private static void writeParameters(ObjectOutputStream out, Class<?>[] parms)
+ throws IOException
+ {
+ out.writeShort(parms.length);
+ outer:
+ for (int i=0; i < parms.length; i++) {
+ Class<?> parm = parms[i];
+ boolean primitive = parm.isPrimitive();
+ out.writeBoolean(primitive);
+ if (!primitive) {
+ out.writeObject(parm);
+ continue;
+ }
+ for (int j=0; j < primitives.length; j++) {
+ if (parm.equals(primitives[j])) {
+ out.writeByte(j);
+ continue outer;
+ }
+ }
+ throw new IllegalArgumentException("Primitive " + parm +
+ " not found");
+ }
+ }
+
+ /**
+ * Reads an array of parameter types from the stream.
+ */
+ private static Class<?>[] readParameters(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ Class<?>[] result = new Class[in.readShort()];
+ for (int i=0; i < result.length; i++) {
+ if (!in.readBoolean()) {
+ result[i] = (Class<?>) in.readObject();
+ continue;
+ }
+ result[i] = primitives[in.readByte()];
+ }
+ return result;
+ }
+}
+
diff --git a/src/org/mozilla/javascript/NativeArray.java b/src/org/mozilla/javascript/NativeArray.java
new file mode 100644
index 0000000..dc0b0e6
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeArray.java
@@ -0,0 +1,1745 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Mike McCabe
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.util.Arrays;
+
+/**
+ * This class implements the Array native object.
+ * @author Norris Boyd
+ * @author Mike McCabe
+ */
+public class NativeArray extends IdScriptableObject
+{
+ static final long serialVersionUID = 7331366857676127338L;
+
+ /*
+ * Optimization possibilities and open issues:
+ * - Long vs. double schizophrenia. I suspect it might be better
+ * to use double throughout.
+ *
+ * - Functions that need a new Array call "new Array" in the
+ * current scope rather than using a hardwired constructor;
+ * "Array" could be redefined. It turns out that js calls the
+ * equivalent of "new Array" in the current scope, except that it
+ * always gets at least an object back, even when Array == null.
+ */
+
+ private static final Object ARRAY_TAG = "Array";
+ private static final Integer NEGATIVE_ONE = new Integer(-1);
+
+ static void init(Scriptable scope, boolean sealed)
+ {
+ NativeArray obj = new NativeArray(0);
+ obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+ }
+
+ static int getMaximumInitialCapacity() {
+ return maximumInitialCapacity;
+ }
+
+ static void setMaximumInitialCapacity(int maximumInitialCapacity) {
+ NativeArray.maximumInitialCapacity = maximumInitialCapacity;
+ }
+
+ public NativeArray(long lengthArg)
+ {
+ denseOnly = lengthArg <= maximumInitialCapacity;
+ if (denseOnly) {
+ int intLength = (int) lengthArg;
+ if (intLength < DEFAULT_INITIAL_CAPACITY)
+ intLength = DEFAULT_INITIAL_CAPACITY;
+ dense = new Object[intLength];
+ Arrays.fill(dense, Scriptable.NOT_FOUND);
+ }
+ length = lengthArg;
+ }
+
+ public NativeArray(Object[] array)
+ {
+ denseOnly = true;
+ dense = array;
+ length = array.length;
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return "Array";
+ }
+
+ private static final int
+ Id_length = 1,
+ MAX_INSTANCE_ID = 1;
+
+ @Override
+ protected int getMaxInstanceId()
+ {
+ return MAX_INSTANCE_ID;
+ }
+
+ @Override
+ protected int findInstanceIdInfo(String s)
+ {
+ if (s.equals("length")) {
+ return instanceIdInfo(DONTENUM | PERMANENT, Id_length);
+ }
+ return super.findInstanceIdInfo(s);
+ }
+
+ @Override
+ protected String getInstanceIdName(int id)
+ {
+ if (id == Id_length) { return "length"; }
+ return super.getInstanceIdName(id);
+ }
+
+ @Override
+ protected Object getInstanceIdValue(int id)
+ {
+ if (id == Id_length) {
+ return ScriptRuntime.wrapNumber(length);
+ }
+ return super.getInstanceIdValue(id);
+ }
+
+ @Override
+ protected void setInstanceIdValue(int id, Object value)
+ {
+ if (id == Id_length) {
+ setLength(value); return;
+ }
+ super.setInstanceIdValue(id, value);
+ }
+
+ @Override
+ protected void fillConstructorProperties(IdFunctionObject ctor)
+ {
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_join,
+ "join", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_reverse,
+ "reverse", 1);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_sort,
+ "sort", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_push,
+ "push", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_pop,
+ "pop", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_shift,
+ "shift", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_unshift,
+ "unshift", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_splice,
+ "splice", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_concat,
+ "concat", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_slice,
+ "slice", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_indexOf,
+ "indexOf", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_lastIndexOf,
+ "lastIndexOf", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_every,
+ "every", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_filter,
+ "filter", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_forEach,
+ "forEach", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_map,
+ "map", 2);
+ addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_some,
+ "some", 2);
+ super.fillConstructorProperties(ctor);
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_constructor: arity=1; s="constructor"; break;
+ case Id_toString: arity=0; s="toString"; break;
+ case Id_toLocaleString: arity=1; s="toLocaleString"; break;
+ case Id_toSource: arity=0; s="toSource"; break;
+ case Id_join: arity=1; s="join"; break;
+ case Id_reverse: arity=0; s="reverse"; break;
+ case Id_sort: arity=1; s="sort"; break;
+ case Id_push: arity=1; s="push"; break;
+ case Id_pop: arity=1; s="pop"; break;
+ case Id_shift: arity=1; s="shift"; break;
+ case Id_unshift: arity=1; s="unshift"; break;
+ case Id_splice: arity=1; s="splice"; break;
+ case Id_concat: arity=1; s="concat"; break;
+ case Id_slice: arity=1; s="slice"; break;
+ case Id_indexOf: arity=1; s="indexOf"; break;
+ case Id_lastIndexOf: arity=1; s="lastIndexOf"; break;
+ case Id_every: arity=1; s="every"; break;
+ case Id_filter: arity=1; s="filter"; break;
+ case Id_forEach: arity=1; s="forEach"; break;
+ case Id_map: arity=1; s="map"; break;
+ case Id_some: arity=1; s="some"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(ARRAY_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(ARRAY_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+ again:
+ for (;;) {
+ switch (id) {
+ case ConstructorId_join:
+ case ConstructorId_reverse:
+ case ConstructorId_sort:
+ case ConstructorId_push:
+ case ConstructorId_pop:
+ case ConstructorId_shift:
+ case ConstructorId_unshift:
+ case ConstructorId_splice:
+ case ConstructorId_concat:
+ case ConstructorId_slice:
+ case ConstructorId_indexOf:
+ case ConstructorId_lastIndexOf:
+ case ConstructorId_every:
+ case ConstructorId_filter:
+ case ConstructorId_forEach:
+ case ConstructorId_map:
+ case ConstructorId_some: {
+ thisObj = ScriptRuntime.toObject(scope, args[0]);
+ Object[] newArgs = new Object[args.length-1];
+ for (int i=0; i < newArgs.length; i++)
+ newArgs[i] = args[i+1];
+ args = newArgs;
+ id = -id;
+ continue again;
+ }
+
+ case Id_constructor: {
+ boolean inNewExpr = (thisObj == null);
+ if (!inNewExpr) {
+ // IdFunctionObject.construct will set up parent, proto
+ return f.construct(cx, scope, args);
+ }
+ return jsConstructor(cx, scope, args);
+ }
+
+ case Id_toString:
+ return toStringHelper(cx, scope, thisObj,
+ cx.hasFeature(Context.FEATURE_TO_STRING_AS_SOURCE), false);
+
+ case Id_toLocaleString:
+ return toStringHelper(cx, scope, thisObj, false, true);
+
+ case Id_toSource:
+ return toStringHelper(cx, scope, thisObj, true, false);
+
+ case Id_join:
+ return js_join(cx, thisObj, args);
+
+ case Id_reverse:
+ return js_reverse(cx, thisObj, args);
+
+ case Id_sort:
+ return js_sort(cx, scope, thisObj, args);
+
+ case Id_push:
+ return js_push(cx, thisObj, args);
+
+ case Id_pop:
+ return js_pop(cx, thisObj, args);
+
+ case Id_shift:
+ return js_shift(cx, thisObj, args);
+
+ case Id_unshift:
+ return js_unshift(cx, thisObj, args);
+
+ case Id_splice:
+ return js_splice(cx, scope, thisObj, args);
+
+ case Id_concat:
+ return js_concat(cx, scope, thisObj, args);
+
+ case Id_slice:
+ return js_slice(cx, thisObj, args);
+
+ case Id_indexOf:
+ return indexOfHelper(cx, thisObj, args, false);
+
+ case Id_lastIndexOf:
+ return indexOfHelper(cx, thisObj, args, true);
+
+ case Id_every:
+ case Id_filter:
+ case Id_forEach:
+ case Id_map:
+ case Id_some:
+ return iterativeMethod(cx, id, scope, thisObj, args);
+ }
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+ }
+
+ @Override
+ public Object get(int index, Scriptable start)
+ {
+ if (!denseOnly && isGetterOrSetter(null, index, false))
+ return super.get(index, start);
+ if (dense != null && 0 <= index && index < dense.length)
+ return dense[index];
+ return super.get(index, start);
+ }
+
+ @Override
+ public boolean has(int index, Scriptable start)
+ {
+ if (!denseOnly && isGetterOrSetter(null, index, false))
+ return super.has(index, start);
+ if (dense != null && 0 <= index && index < dense.length)
+ return dense[index] != NOT_FOUND;
+ return super.has(index, start);
+ }
+
+ // if id is an array index (ECMA 15.4.0), return the number,
+ // otherwise return -1L
+ private static long toArrayIndex(String id)
+ {
+ double d = ScriptRuntime.toNumber(id);
+ if (d == d) {
+ long index = ScriptRuntime.toUint32(d);
+ if (index == d && index != 4294967295L) {
+ // Assume that ScriptRuntime.toString(index) is the same
+ // as java.lang.Long.toString(index) for long
+ if (Long.toString(index).equals(id)) {
+ return index;
+ }
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public void put(String id, Scriptable start, Object value)
+ {
+ super.put(id, start, value);
+ if (start == this) {
+ // If the object is sealed, super will throw exception
+ long index = toArrayIndex(id);
+ if (index >= length) {
+ length = index + 1;
+ denseOnly = false;
+ }
+ }
+ }
+
+ private boolean ensureCapacity(int capacity)
+ {
+ if (capacity > dense.length) {
+ if (capacity > MAX_PRE_GROW_SIZE) {
+ denseOnly = false;
+ return false;
+ }
+ capacity = Math.max(capacity, (int)(dense.length * GROW_FACTOR));
+ Object[] newDense = new Object[capacity];
+ System.arraycopy(dense, 0, newDense, 0, dense.length);
+ Arrays.fill(newDense, dense.length, newDense.length,
+ Scriptable.NOT_FOUND);
+ dense = newDense;
+ }
+ return true;
+ }
+
+ @Override
+ public void put(int index, Scriptable start, Object value)
+ {
+ if (start == this && !isSealed() && dense != null && 0 <= index &&
+ (denseOnly || !isGetterOrSetter(null, index, true)))
+ {
+ if (index < dense.length) {
+ dense[index] = value;
+ if (this.length <= index)
+ this.length = (long)index + 1;
+ return;
+ } else if (denseOnly && index < dense.length * GROW_FACTOR &&
+ ensureCapacity(index+1))
+ {
+ dense[index] = value;
+ this.length = (long)index + 1;
+ return;
+ } else {
+ denseOnly = false;
+ }
+ }
+ super.put(index, start, value);
+ if (start == this) {
+ // only set the array length if given an array index (ECMA 15.4.0)
+ if (this.length <= index) {
+ // avoid overflowing index!
+ this.length = (long)index + 1;
+ }
+ }
+ }
+
+ @Override
+ public void delete(int index)
+ {
+ if (dense != null && 0 <= index && index < dense.length &&
+ !isSealed() && (denseOnly || !isGetterOrSetter(null, index, true)))
+ {
+ dense[index] = NOT_FOUND;
+ } else {
+ super.delete(index);
+ }
+ }
+
+ @Override
+ public Object[] getIds()
+ {
+ Object[] superIds = super.getIds();
+ if (dense == null) { return superIds; }
+ int N = dense.length;
+ long currentLength = length;
+ if (N > currentLength) {
+ N = (int)currentLength;
+ }
+ if (N == 0) { return superIds; }
+ int superLength = superIds.length;
+ Object[] ids = new Object[N + superLength];
+
+ int presentCount = 0;
+ for (int i = 0; i != N; ++i) {
+ // Replace existing elements by their indexes
+ if (dense[i] != NOT_FOUND) {
+ ids[presentCount] = new Integer(i);
+ ++presentCount;
+ }
+ }
+ if (presentCount != N) {
+ // dense contains deleted elems, need to shrink the result
+ Object[] tmp = new Object[presentCount + superLength];
+ System.arraycopy(ids, 0, tmp, 0, presentCount);
+ ids = tmp;
+ }
+ System.arraycopy(superIds, 0, ids, presentCount, superLength);
+ return ids;
+ }
+
+ @Override
+ public Object getDefaultValue(Class<?> hint)
+ {
+ if (hint == ScriptRuntime.NumberClass) {
+ Context cx = Context.getContext();
+ if (cx.getLanguageVersion() == Context.VERSION_1_2)
+ return new Long(length);
+ }
+ return super.getDefaultValue(hint);
+ }
+
+ /**
+ * See ECMA 15.4.1,2
+ */
+ private static Object jsConstructor(Context cx, Scriptable scope,
+ Object[] args)
+ {
+ if (args.length == 0)
+ return new NativeArray(0);
+
+ // Only use 1 arg as first element for version 1.2; for
+ // any other version (including 1.3) follow ECMA and use it as
+ // a length.
+ if (cx.getLanguageVersion() == Context.VERSION_1_2) {
+ return new NativeArray(args);
+ } else {
+ Object arg0 = args[0];
+ if (args.length > 1 || !(arg0 instanceof Number)) {
+ return new NativeArray(args);
+ } else {
+ long len = ScriptRuntime.toUint32(arg0);
+ if (len != ((Number)arg0).doubleValue())
+ throw Context.reportRuntimeError0("msg.arraylength.bad");
+ return new NativeArray(len);
+ }
+ }
+ }
+
+ public long getLength() {
+ return length;
+ }
+
+ /** @deprecated Use {@link #getLength()} instead. */
+ public long jsGet_length() {
+ return getLength();
+ }
+
+ /**
+ * Change the value of the internal flag that determines whether all
+ * storage is handed by a dense backing array rather than an associative
+ * store.
+ * @param denseOnly new value for denseOnly flag
+ * @throws IllegalArgumentException if an attempt is made to enable
+ * denseOnly after it was disabled; NativeArray code is not written
+ * to handle switching back to a dense representation
+ */
+ void setDenseOnly(boolean denseOnly) {
+ if (denseOnly && !this.denseOnly)
+ throw new IllegalArgumentException();
+ this.denseOnly = denseOnly;
+ }
+
+ private void setLength(Object val) {
+ /* XXX do we satisfy this?
+ * 15.4.5.1 [[Put]](P, V):
+ * 1. Call the [[CanPut]] method of A with name P.
+ * 2. If Result(1) is false, return.
+ * ?
+ */
+
+ double d = ScriptRuntime.toNumber(val);
+ long longVal = ScriptRuntime.toUint32(d);
+ if (longVal != d)
+ throw Context.reportRuntimeError0("msg.arraylength.bad");
+
+ if (denseOnly) {
+ if (longVal < length) {
+ // downcast okay because denseOnly
+ Arrays.fill(dense, (int) longVal, dense.length, NOT_FOUND);
+ length = longVal;
+ return;
+ } else if (longVal < MAX_PRE_GROW_SIZE &&
+ longVal < (length * GROW_FACTOR) &&
+ ensureCapacity((int)longVal))
+ {
+ length = longVal;
+ return;
+ } else {
+ denseOnly = false;
+ }
+ }
+ if (longVal < length) {
+ // remove all properties between longVal and length
+ if (length - longVal > 0x1000) {
+ // assume that the representation is sparse
+ Object[] e = getIds(); // will only find in object itself
+ for (int i=0; i < e.length; i++) {
+ Object id = e[i];
+ if (id instanceof String) {
+ // > MAXINT will appear as string
+ String strId = (String)id;
+ long index = toArrayIndex(strId);
+ if (index >= longVal)
+ delete(strId);
+ } else {
+ int index = ((Integer)id).intValue();
+ if (index >= longVal)
+ delete(index);
+ }
+ }
+ } else {
+ // assume a dense representation
+ for (long i = longVal; i < length; i++) {
+ deleteElem(this, i);
+ }
+ }
+ }
+ length = longVal;
+ }
+
+ /* Support for generic Array-ish objects. Most of the Array
+ * functions try to be generic; anything that has a length
+ * property is assumed to be an array.
+ * getLengthProperty returns 0 if obj does not have the length property
+ * or its value is not convertible to a number.
+ */
+ static long getLengthProperty(Context cx, Scriptable obj) {
+ // These will both give numeric lengths within Uint32 range.
+ if (obj instanceof NativeString) {
+ return ((NativeString)obj).getLength();
+ } else if (obj instanceof NativeArray) {
+ return ((NativeArray)obj).getLength();
+ }
+ return ScriptRuntime.toUint32(
+ ScriptRuntime.getObjectProp(obj, "length", cx));
+ }
+
+ private static Object setLengthProperty(Context cx, Scriptable target,
+ long length)
+ {
+ return ScriptRuntime.setObjectProp(
+ target, "length", ScriptRuntime.wrapNumber(length), cx);
+ }
+
+ /* Utility functions to encapsulate index > Integer.MAX_VALUE
+ * handling. Also avoids unnecessary object creation that would
+ * be necessary to use the general ScriptRuntime.get/setElem
+ * functions... though this is probably premature optimization.
+ */
+ private static void deleteElem(Scriptable target, long index) {
+ int i = (int)index;
+ if (i == index) { target.delete(i); }
+ else { target.delete(Long.toString(index)); }
+ }
+
+ private static Object getElem(Context cx, Scriptable target, long index)
+ {
+ if (index > Integer.MAX_VALUE) {
+ String id = Long.toString(index);
+ return ScriptRuntime.getObjectProp(target, id, cx);
+ } else {
+ return ScriptRuntime.getObjectIndex(target, (int)index, cx);
+ }
+ }
+
+ private static void setElem(Context cx, Scriptable target, long index,
+ Object value)
+ {
+ if (index > Integer.MAX_VALUE) {
+ String id = Long.toString(index);
+ ScriptRuntime.setObjectProp(target, id, value, cx);
+ } else {
+ ScriptRuntime.setObjectIndex(target, (int)index, value, cx);
+ }
+ }
+
+ private static String toStringHelper(Context cx, Scriptable scope,
+ Scriptable thisObj,
+ boolean toSource, boolean toLocale)
+ {
+ /* It's probably redundant to handle long lengths in this
+ * function; StringBuilders are limited to 2^31 in java.
+ */
+
+ long length = getLengthProperty(cx, thisObj);
+
+ StringBuilder result = new StringBuilder(256);
+
+ // whether to return '4,unquoted,5' or '[4, "quoted", 5]'
+ String separator;
+
+ if (toSource) {
+ result.append('[');
+ separator = ", ";
+ } else {
+ separator = ",";
+ }
+
+ boolean haslast = false;
+ long i = 0;
+
+ boolean toplevel, iterating;
+ if (cx.iterating == null) {
+ toplevel = true;
+ iterating = false;
+ cx.iterating = new ObjToIntMap(31);
+ } else {
+ toplevel = false;
+ iterating = cx.iterating.has(thisObj);
+ }
+
+ // Make sure cx.iterating is set to null when done
+ // so we don't leak memory
+ try {
+ if (!iterating) {
+ cx.iterating.put(thisObj, 0); // stop recursion.
+ for (i = 0; i < length; i++) {
+ if (i > 0) result.append(separator);
+ Object elem = getElem(cx, thisObj, i);
+ if (elem == null || elem == Undefined.instance) {
+ haslast = false;
+ continue;
+ }
+ haslast = true;
+
+ if (toSource) {
+ result.append(ScriptRuntime.uneval(cx, scope, elem));
+
+ } else if (elem instanceof String) {
+ String s = (String)elem;
+ if (toSource) {
+ result.append('\"');
+ result.append(ScriptRuntime.escapeString(s));
+ result.append('\"');
+ } else {
+ result.append(s);
+ }
+
+ } else {
+ if (toLocale)
+ {
+ Callable fun;
+ Scriptable funThis;
+ fun = ScriptRuntime.getPropFunctionAndThis(
+ elem, "toLocaleString", cx);
+ funThis = ScriptRuntime.lastStoredScriptable(cx);
+ elem = fun.call(cx, scope, funThis,
+ ScriptRuntime.emptyArgs);
+ }
+ result.append(ScriptRuntime.toString(elem));
+ }
+ }
+ }
+ } finally {
+ if (toplevel) {
+ cx.iterating = null;
+ }
+ }
+
+ if (toSource) {
+ //for [,,].length behavior; we want toString to be symmetric.
+ if (!haslast && i > 0)
+ result.append(", ]");
+ else
+ result.append(']');
+ }
+ return result.toString();
+ }
+
+ /**
+ * See ECMA 15.4.4.3
+ */
+ private static String js_join(Context cx, Scriptable thisObj,
+ Object[] args)
+ {
+ long llength = getLengthProperty(cx, thisObj);
+ int length = (int)llength;
+ if (llength != length) {
+ throw Context.reportRuntimeError1(
+ "msg.arraylength.too.big", String.valueOf(llength));
+ }
+ // if no args, use "," as separator
+ String separator = (args.length < 1 || args[0] == Undefined.instance)
+ ? ","
+ : ScriptRuntime.toString(args[0]);
+ if (thisObj instanceof NativeArray) {
+ NativeArray na = (NativeArray) thisObj;
+ if (na.denseOnly) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ if (i != 0) {
+ sb.append(separator);
+ }
+ if (i < na.dense.length) {
+ Object temp = na.dense[i];
+ if (temp != null && temp != Undefined.instance &&
+ temp != Scriptable.NOT_FOUND)
+ {
+ sb.append(ScriptRuntime.toString(temp));
+ }
+ }
+ }
+ return sb.toString();
+ }
+ }
+ if (length == 0) {
+ return "";
+ }
+ String[] buf = new String[length];
+ int total_size = 0;
+ for (int i = 0; i != length; i++) {
+ Object temp = getElem(cx, thisObj, i);
+ if (temp != null && temp != Undefined.instance) {
+ String str = ScriptRuntime.toString(temp);
+ total_size += str.length();
+ buf[i] = str;
+ }
+ }
+ total_size += (length - 1) * separator.length();
+ StringBuilder sb = new StringBuilder(total_size);
+ for (int i = 0; i != length; i++) {
+ if (i != 0) {
+ sb.append(separator);
+ }
+ String str = buf[i];
+ if (str != null) {
+ // str == null for undefined or null
+ sb.append(str);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * See ECMA 15.4.4.4
+ */
+ private static Scriptable js_reverse(Context cx, Scriptable thisObj,
+ Object[] args)
+ {
+ if (thisObj instanceof NativeArray) {
+ NativeArray na = (NativeArray) thisObj;
+ if (na.denseOnly) {
+ for (int i=0, j=((int)na.length)-1; i < j; i++,j--) {
+ Object temp = na.dense[i];
+ na.dense[i] = na.dense[j];
+ na.dense[j] = temp;
+ }
+ return thisObj;
+ }
+ }
+ long len = getLengthProperty(cx, thisObj);
+
+ long half = len / 2;
+ for(long i=0; i < half; i++) {
+ long j = len - i - 1;
+ Object temp1 = getElem(cx, thisObj, i);
+ Object temp2 = getElem(cx, thisObj, j);
+ setElem(cx, thisObj, i, temp2);
+ setElem(cx, thisObj, j, temp1);
+ }
+ return thisObj;
+ }
+
+ /**
+ * See ECMA 15.4.4.5
+ */
+ private static Scriptable js_sort(Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ long length = getLengthProperty(cx, thisObj);
+
+ if (length <= 1) { return thisObj; }
+
+ Object compare;
+ Object[] cmpBuf;
+
+ if (args.length > 0 && Undefined.instance != args[0]) {
+ // sort with given compare function
+ compare = args[0];
+ cmpBuf = new Object[2]; // Buffer for cmp arguments
+ } else {
+ // sort with default compare
+ compare = null;
+ cmpBuf = null;
+ }
+ if (thisObj instanceof NativeArray) {
+ NativeArray na = (NativeArray) thisObj;
+ if (na.denseOnly) {
+ int ilength = (int) length;
+ heapsort(cx, scope, na.dense, ilength, compare, cmpBuf);
+ return thisObj;
+ }
+ }
+
+ // Should we use the extended sort function, or the faster one?
+ if (length >= Integer.MAX_VALUE) {
+ heapsort_extended(cx, scope, thisObj, length, compare, cmpBuf);
+ } else {
+ int ilength = (int)length;
+ // copy the JS array into a working array, so it can be
+ // sorted cheaply.
+ Object[] working = new Object[ilength];
+ for (int i = 0; i != ilength; ++i) {
+ working[i] = getElem(cx, thisObj, i);
+ }
+
+ heapsort(cx, scope, working, ilength, compare, cmpBuf);
+
+ // copy the working array back into thisObj
+ for (int i = 0; i != ilength; ++i) {
+ setElem(cx, thisObj, i, working[i]);
+ }
+ }
+ return thisObj;
+ }
+
+ // Return true only if x > y
+ private static boolean isBigger(Context cx, Scriptable scope,
+ Object x, Object y,
+ Object cmp, Object[] cmpBuf)
+ {
+ if (cmp == null) {
+ if (cmpBuf != null) Kit.codeBug();
+ } else {
+ if (cmpBuf == null || cmpBuf.length != 2) Kit.codeBug();
+ }
+
+ Object undef = Undefined.instance;
+ Object notfound = Scriptable.NOT_FOUND;
+
+ // sort undefined to end
+ if (y == undef || y == notfound) {
+ return false; // x can not be bigger then undef
+ } else if (x == undef || x == notfound) {
+ return true; // y != undef here, so x > y
+ }
+
+ if (cmp == null) {
+ // if no cmp function supplied, sort lexicographically
+ String a = ScriptRuntime.toString(x);
+ String b = ScriptRuntime.toString(y);
+ return a.compareTo(b) > 0;
+ }
+ else {
+ // assemble args and call supplied JS cmp function
+ cmpBuf[0] = x;
+ cmpBuf[1] = y;
+ Callable fun = ScriptRuntime.getValueFunctionAndThis(cmp, cx);
+ Scriptable funThis = ScriptRuntime.lastStoredScriptable(cx);
+
+ Object ret = fun.call(cx, scope, funThis, cmpBuf);
+ double d = ScriptRuntime.toNumber(ret);
+
+ // XXX what to do when cmp function returns NaN? ECMA states
+ // that it's then not a 'consistent comparison function'... but
+ // then what do we do? Back out and start over with the generic
+ // cmp function when we see a NaN? Throw an error?
+
+ // for now, just ignore it:
+
+ return d > 0;
+ }
+ }
+
+/** Heapsort implementation.
+ * See "Introduction to Algorithms" by Cormen, Leiserson, Rivest for details.
+ * Adjusted for zero based indexes.
+ */
+ private static void heapsort(Context cx, Scriptable scope,
+ Object[] array, int length,
+ Object cmp, Object[] cmpBuf)
+ {
+ if (length <= 1) Kit.codeBug();
+
+ // Build heap
+ for (int i = length / 2; i != 0;) {
+ --i;
+ Object pivot = array[i];
+ heapify(cx, scope, pivot, array, i, length, cmp, cmpBuf);
+ }
+
+ // Sort heap
+ for (int i = length; i != 1;) {
+ --i;
+ Object pivot = array[i];
+ array[i] = array[0];
+ heapify(cx, scope, pivot, array, 0, i, cmp, cmpBuf);
+ }
+ }
+
+/** pivot and child heaps of i should be made into heap starting at i,
+ * original array[i] is never used to have less array access during sorting.
+ */
+ private static void heapify(Context cx, Scriptable scope,
+ Object pivot, Object[] array, int i, int end,
+ Object cmp, Object[] cmpBuf)
+ {
+ for (;;) {
+ int child = i * 2 + 1;
+ if (child >= end) {
+ break;
+ }
+ Object childVal = array[child];
+ if (child + 1 < end) {
+ Object nextVal = array[child + 1];
+ if (isBigger(cx, scope, nextVal, childVal, cmp, cmpBuf)) {
+ ++child; childVal = nextVal;
+ }
+ }
+ if (!isBigger(cx, scope, childVal, pivot, cmp, cmpBuf)) {
+ break;
+ }
+ array[i] = childVal;
+ i = child;
+ }
+ array[i] = pivot;
+ }
+
+/** Version of heapsort that call getElem/setElem on target to query/assign
+ * array elements instead of Java array access
+ */
+ private static void heapsort_extended(Context cx, Scriptable scope,
+ Scriptable target, long length,
+ Object cmp, Object[] cmpBuf)
+ {
+ if (length <= 1) Kit.codeBug();
+
+ // Build heap
+ for (long i = length / 2; i != 0;) {
+ --i;
+ Object pivot = getElem(cx, target, i);
+ heapify_extended(cx, scope, pivot, target, i, length, cmp, cmpBuf);
+ }
+
+ // Sort heap
+ for (long i = length; i != 1;) {
+ --i;
+ Object pivot = getElem(cx, target, i);
+ setElem(cx, target, i, getElem(cx, target, 0));
+ heapify_extended(cx, scope, pivot, target, 0, i, cmp, cmpBuf);
+ }
+ }
+
+ private static void heapify_extended(Context cx, Scriptable scope,
+ Object pivot, Scriptable target,
+ long i, long end,
+ Object cmp, Object[] cmpBuf)
+ {
+ for (;;) {
+ long child = i * 2 + 1;
+ if (child >= end) {
+ break;
+ }
+ Object childVal = getElem(cx, target, child);
+ if (child + 1 < end) {
+ Object nextVal = getElem(cx, target, child + 1);
+ if (isBigger(cx, scope, nextVal, childVal, cmp, cmpBuf)) {
+ ++child; childVal = nextVal;
+ }
+ }
+ if (!isBigger(cx, scope, childVal, pivot, cmp, cmpBuf)) {
+ break;
+ }
+ setElem(cx, target, i, childVal);
+ i = child;
+ }
+ setElem(cx, target, i, pivot);
+ }
+
+ /**
+ * Non-ECMA methods.
+ */
+
+ private static Object js_push(Context cx, Scriptable thisObj,
+ Object[] args)
+ {
+ if (thisObj instanceof NativeArray) {
+ NativeArray na = (NativeArray) thisObj;
+ if (na.denseOnly &&
+ na.ensureCapacity((int) na.length + args.length))
+ {
+ for (int i = 0; i < args.length; i++) {
+ na.dense[(int)na.length++] = args[i];
+ }
+ return ScriptRuntime.wrapNumber(na.length);
+ }
+ }
+ long length = getLengthProperty(cx, thisObj);
+ for (int i = 0; i < args.length; i++) {
+ setElem(cx, thisObj, length + i, args[i]);
+ }
+
+ length += args.length;
+ Object lengthObj = setLengthProperty(cx, thisObj, length);
+
+ /*
+ * If JS1.2, follow Perl4 by returning the last thing pushed.
+ * Otherwise, return the new array length.
+ */
+ if (cx.getLanguageVersion() == Context.VERSION_1_2)
+ // if JS1.2 && no arguments, return undefined.
+ return args.length == 0
+ ? Undefined.instance
+ : args[args.length - 1];
+
+ else
+ return lengthObj;
+ }
+
+ private static Object js_pop(Context cx, Scriptable thisObj,
+ Object[] args)
+ {
+ Object result;
+ if (thisObj instanceof NativeArray) {
+ NativeArray na = (NativeArray) thisObj;
+ if (na.denseOnly && na.length > 0) {
+ na.length--;
+ result = na.dense[(int)na.length];
+ na.dense[(int)na.length] = NOT_FOUND;
+ return result;
+ }
+ }
+ long length = getLengthProperty(cx, thisObj);
+ if (length > 0) {
+ length--;
+
+ // Get the to-be-deleted property's value.
+ result = getElem(cx, thisObj, length);
+
+ // We don't need to delete the last property, because
+ // setLength does that for us.
+ } else {
+ result = Undefined.instance;
+ }
+ // necessary to match js even when length < 0; js pop will give a
+ // length property to any target it is called on.
+ setLengthProperty(cx, thisObj, length);
+
+ return result;
+ }
+
+ private static Object js_shift(Context cx, Scriptable thisObj,
+ Object[] args)
+ {
+ if (thisObj instanceof NativeArray) {
+ NativeArray na = (NativeArray) thisObj;
+ if (na.denseOnly && na.length > 0) {
+ na.length--;
+ Object result = na.dense[0];
+ System.arraycopy(na.dense, 1, na.dense, 0, (int)na.length);
+ na.dense[(int)na.length] = NOT_FOUND;
+ return result;
+ }
+ }
+ Object result;
+ long length = getLengthProperty(cx, thisObj);
+ if (length > 0) {
+ long i = 0;
+ length--;
+
+ // Get the to-be-deleted property's value.
+ result = getElem(cx, thisObj, i);
+
+ /*
+ * Slide down the array above the first element. Leave i
+ * set to point to the last element.
+ */
+ if (length > 0) {
+ for (i = 1; i <= length; i++) {
+ Object temp = getElem(cx, thisObj, i);
+ setElem(cx, thisObj, i - 1, temp);
+ }
+ }
+ // We don't need to delete the last property, because
+ // setLength does that for us.
+ } else {
+ result = Undefined.instance;
+ }
+ setLengthProperty(cx, thisObj, length);
+ return result;
+ }
+
+ private static Object js_unshift(Context cx, Scriptable thisObj,
+ Object[] args)
+ {
+ if (thisObj instanceof NativeArray) {
+ NativeArray na = (NativeArray) thisObj;
+ if (na.denseOnly &&
+ na.ensureCapacity((int)na.length + args.length))
+ {
+ System.arraycopy(na.dense, 0, na.dense, args.length,
+ (int) na.length);
+ for (int i = 0; i < args.length; i++) {
+ na.dense[i] = args[i];
+ }
+ na.length += args.length;
+ return ScriptRuntime.wrapNumber(na.length);
+ }
+ }
+ long length = getLengthProperty(cx, thisObj);
+ int argc = args.length;
+
+ if (args.length > 0) {
+ /* Slide up the array to make room for args at the bottom */
+ if (length > 0) {
+ for (long last = length - 1; last >= 0; last--) {
+ Object temp = getElem(cx, thisObj, last);
+ setElem(cx, thisObj, last + argc, temp);
+ }
+ }
+
+ /* Copy from argv to the bottom of the array. */
+ for (int i = 0; i < args.length; i++) {
+ setElem(cx, thisObj, i, args[i]);
+ }
+
+ /* Follow Perl by returning the new array length. */
+ length += args.length;
+ return setLengthProperty(cx, thisObj, length);
+ }
+ return ScriptRuntime.wrapNumber(length);
+ }
+
+ private static Object js_splice(Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ NativeArray na = null;
+ boolean denseMode = false;
+ if (thisObj instanceof NativeArray) {
+ na = (NativeArray) thisObj;
+ denseMode = na.denseOnly;
+ }
+
+ /* create an empty Array to return. */
+ scope = getTopLevelScope(scope);
+ int argc = args.length;
+ if (argc == 0)
+ return ScriptRuntime.newObject(cx, scope, "Array", null);
+ long length = getLengthProperty(cx, thisObj);
+
+ /* Convert the first argument into a starting index. */
+ long begin = toSliceIndex(ScriptRuntime.toInteger(args[0]), length);
+ argc--;
+
+ /* Convert the second argument into count */
+ long count;
+ if (args.length == 1) {
+ count = length - begin;
+ } else {
+ double dcount = ScriptRuntime.toInteger(args[1]);
+ if (dcount < 0) {
+ count = 0;
+ } else if (dcount > (length - begin)) {
+ count = length - begin;
+ } else {
+ count = (long)dcount;
+ }
+ argc--;
+ }
+
+ long end = begin + count;
+
+ /* If there are elements to remove, put them into the return value. */
+ Object result;
+ if (count != 0) {
+ if (count == 1
+ && (cx.getLanguageVersion() == Context.VERSION_1_2))
+ {
+ /*
+ * JS lacks "list context", whereby in Perl one turns the
+ * single scalar that's spliced out into an array just by
+ * assigning it to @single instead of $single, or by using it
+ * as Perl push's first argument, for instance.
+ *
+ * JS1.2 emulated Perl too closely and returned a non-Array for
+ * the single-splice-out case, requiring callers to test and
+ * wrap in [] if necessary. So JS1.3, default, and other
+ * versions all return an array of length 1 for uniformity.
+ */
+ result = getElem(cx, thisObj, begin);
+ } else {
+ if (denseMode) {
+ int intLen = (int) (end - begin);
+ Object[] copy = new Object[intLen];
+ System.arraycopy(na.dense, (int) begin, copy, 0, intLen);
+ result = cx.newArray(scope, copy);
+ } else {
+ Scriptable resultArray = ScriptRuntime.newObject(cx, scope,
+ "Array", null);
+ for (long last = begin; last != end; last++) {
+ Object temp = getElem(cx, thisObj, last);
+ setElem(cx, resultArray, last - begin, temp);
+ }
+ result = resultArray;
+ }
+ }
+ } else { // (count == 0)
+ if (cx.getLanguageVersion() == Context.VERSION_1_2) {
+ /* Emulate C JS1.2; if no elements are removed, return undefined. */
+ result = Undefined.instance;
+ } else {
+ result = ScriptRuntime.newObject(cx, scope, "Array", null);
+ }
+ }
+
+ /* Find the direction (up or down) to copy and make way for argv. */
+ long delta = argc - count;
+ if (denseMode && length + delta < Integer.MAX_VALUE &&
+ na.ensureCapacity((int) (length + delta)))
+ {
+ System.arraycopy(na.dense, (int) end, na.dense,
+ (int) (begin + argc), (int) (length - end));
+ if (argc > 0) {
+ System.arraycopy(args, 2, na.dense, (int) begin, argc);
+ }
+ if (delta < 0) {
+ Arrays.fill(na.dense, (int) (length + delta), (int) length,
+ NOT_FOUND);
+ }
+ na.length = length + delta;
+ return result;
+ }
+
+ if (delta > 0) {
+ for (long last = length - 1; last >= end; last--) {
+ Object temp = getElem(cx, thisObj, last);
+ setElem(cx, thisObj, last + delta, temp);
+ }
+ } else if (delta < 0) {
+ for (long last = end; last < length; last++) {
+ Object temp = getElem(cx, thisObj, last);
+ setElem(cx, thisObj, last + delta, temp);
+ }
+ }
+
+ /* Copy from argv into the hole to complete the splice. */
+ int argoffset = args.length - argc;
+ for (int i = 0; i < argc; i++) {
+ setElem(cx, thisObj, begin + i, args[i + argoffset]);
+ }
+
+ /* Update length in case we deleted elements from the end. */
+ setLengthProperty(cx, thisObj, length + delta);
+ return result;
+ }
+
+ /*
+ * See Ecma 262v3 15.4.4.4
+ */
+ private static Scriptable js_concat(Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ // create an empty Array to return.
+ scope = getTopLevelScope(scope);
+ Function ctor = ScriptRuntime.getExistingCtor(cx, scope, "Array");
+ Scriptable result = ctor.construct(cx, scope, ScriptRuntime.emptyArgs);
+ if (thisObj instanceof NativeArray && result instanceof NativeArray) {
+ NativeArray denseThis = (NativeArray) thisObj;
+ NativeArray denseResult = (NativeArray) result;
+ if (denseThis.denseOnly && denseResult.denseOnly) {
+ // First calculate length of resulting array
+ boolean canUseDense = true;
+ int length = (int) denseThis.length;
+ for (int i = 0; i < args.length && canUseDense; i++) {
+ if (args[i] instanceof NativeArray) {
+ // only try to use dense approach for Array-like
+ // objects that are actually NativeArrays
+ final NativeArray arg = (NativeArray) args[i];
+ canUseDense = arg.denseOnly;
+ length += arg.length;
+ } else {
+ length++;
+ }
+ }
+ if (canUseDense && denseResult.ensureCapacity(length)) {
+ System.arraycopy(denseThis.dense, 0, denseResult.dense,
+ 0, (int) denseThis.length);
+ int cursor = (int) denseThis.length;
+ for (int i = 0; i < args.length && canUseDense; i++) {
+ if (args[i] instanceof NativeArray) {
+ NativeArray arg = (NativeArray) args[i];
+ System.arraycopy(arg.dense, 0,
+ denseResult.dense, cursor,
+ (int)arg.length);
+ cursor += (int)arg.length;
+ } else {
+ denseResult.dense[cursor++] = args[i];
+ }
+ }
+ denseResult.length = length;
+ return result;
+ }
+ }
+ }
+
+ long length;
+ long slot = 0;
+
+ /* Put the target in the result array; only add it as an array
+ * if it looks like one.
+ */
+ if (ScriptRuntime.instanceOf(thisObj, ctor, cx)) {
+ length = getLengthProperty(cx, thisObj);
+
+ // Copy from the target object into the result
+ for (slot = 0; slot < length; slot++) {
+ Object temp = getElem(cx, thisObj, slot);
+ setElem(cx, result, slot, temp);
+ }
+ } else {
+ setElem(cx, result, slot++, thisObj);
+ }
+
+ /* Copy from the arguments into the result. If any argument
+ * has a numeric length property, treat it as an array and add
+ * elements separately; otherwise, just copy the argument.
+ */
+ for (int i = 0; i < args.length; i++) {
+ if (ScriptRuntime.instanceOf(args[i], ctor, cx)) {
+ // ScriptRuntime.instanceOf => instanceof Scriptable
+ Scriptable arg = (Scriptable)args[i];
+ length = getLengthProperty(cx, arg);
+ for (long j = 0; j < length; j++, slot++) {
+ Object temp = getElem(cx, arg, j);
+ setElem(cx, result, slot, temp);
+ }
+ } else {
+ setElem(cx, result, slot++, args[i]);
+ }
+ }
+ return result;
+ }
+
+ private Scriptable js_slice(Context cx, Scriptable thisObj,
+ Object[] args)
+ {
+ Scriptable scope = getTopLevelScope(this);
+ Scriptable result = ScriptRuntime.newObject(cx, scope, "Array", null);
+ long length = getLengthProperty(cx, thisObj);
+
+ long begin, end;
+ if (args.length == 0) {
+ begin = 0;
+ end = length;
+ } else {
+ begin = toSliceIndex(ScriptRuntime.toInteger(args[0]), length);
+ if (args.length == 1) {
+ end = length;
+ } else {
+ end = toSliceIndex(ScriptRuntime.toInteger(args[1]), length);
+ }
+ }
+
+ for (long slot = begin; slot < end; slot++) {
+ Object temp = getElem(cx, thisObj, slot);
+ setElem(cx, result, slot - begin, temp);
+ }
+
+ return result;
+ }
+
+ private static long toSliceIndex(double value, long length) {
+ long result;
+ if (value < 0.0) {
+ if (value + length < 0.0) {
+ result = 0;
+ } else {
+ result = (long)(value + length);
+ }
+ } else if (value > length) {
+ result = length;
+ } else {
+ result = (long)value;
+ }
+ return result;
+ }
+
+ /**
+ * Implements the methods "indexOf" and "lastIndexOf".
+ */
+ private Object indexOfHelper(Context cx, Scriptable thisObj,
+ Object[] args, boolean isLast)
+ {
+ Object compareTo = args.length > 0 ? args[0] : Undefined.instance;
+ long length = getLengthProperty(cx, thisObj);
+ long start;
+ if (isLast) {
+ // lastIndexOf
+ /*
+ * From http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array:lastIndexOf
+ * The index at which to start searching backwards. Defaults to the
+ * array's length, i.e. the whole array will be searched. If the
+ * index is greater than or equal to the length of the array, the
+ * whole array will be searched. If negative, it is taken as the
+ * offset from the end of the array. Note that even when the index
+ * is negative, the array is still searched from back to front. If
+ * the calculated index is less than 0, -1 is returned, i.e. the
+ * array will not be searched.
+ */
+ if (args.length < 2) {
+ // default
+ start = length-1;
+ } else {
+ start = ScriptRuntime.toInt32(ScriptRuntime.toNumber(args[1]));
+ if (start >= length)
+ start = length-1;
+ else if (start < 0)
+ start += length;
+ // Note that start may be negative, but that's okay
+ // as the result of -1 will fall out from the code below
+ }
+ } else {
+ // indexOf
+ /*
+ * From http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array:indexOf
+ * The index at which to begin the search. Defaults to 0, i.e. the
+ * whole array will be searched. If the index is greater than or
+ * equal to the length of the array, -1 is returned, i.e. the array
+ * will not be searched. If negative, it is taken as the offset from
+ * the end of the array. Note that even when the index is negative,
+ * the array is still searched from front to back. If the calculated
+ * index is less than 0, the whole array will be searched.
+ */
+ if (args.length < 2) {
+ // default
+ start = 0;
+ } else {
+ start = ScriptRuntime.toInt32(ScriptRuntime.toNumber(args[1]));
+ if (start < 0) {
+ start += length;
+ if (start < 0)
+ start = 0;
+ }
+ // Note that start may be > length-1, but that's okay
+ // as the result of -1 will fall out from the code below
+ }
+ }
+ if (thisObj instanceof NativeArray) {
+ NativeArray na = (NativeArray) thisObj;
+ if (na.denseOnly) {
+ if (isLast) {
+ for (int i=(int)start; i >= 0; i--) {
+ if (na.dense[i] != Scriptable.NOT_FOUND &&
+ ScriptRuntime.shallowEq(na.dense[i], compareTo))
+ {
+ return new Long(i);
+ }
+ }
+ } else {
+ for (int i=(int)start; i < length; i++) {
+ if (na.dense[i] != Scriptable.NOT_FOUND &&
+ ScriptRuntime.shallowEq(na.dense[i], compareTo))
+ {
+ return new Long(i);
+ }
+ }
+ }
+ return NEGATIVE_ONE;
+ }
+ }
+ if (isLast) {
+ for (long i=start; i >= 0; i--) {
+ if (ScriptRuntime.shallowEq(getElem(cx, thisObj, i), compareTo)) {
+ return new Long(i);
+ }
+ }
+ } else {
+ for (long i=start; i < length; i++) {
+ if (ScriptRuntime.shallowEq(getElem(cx, thisObj, i), compareTo)) {
+ return new Long(i);
+ }
+ }
+ }
+ return NEGATIVE_ONE;
+ }
+
+ /**
+ * Implements the methods "every", "filter", "forEach", "map", and "some".
+ */
+ private Object iterativeMethod(Context cx, int id, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ Object callbackArg = args.length > 0 ? args[0] : Undefined.instance;
+ if (callbackArg == null || !(callbackArg instanceof Function)) {
+ throw ScriptRuntime.notFunctionError(
+ ScriptRuntime.toString(callbackArg));
+ }
+ Function f = (Function) callbackArg;
+ Scriptable parent = ScriptableObject.getTopLevelScope(f);
+ Scriptable thisArg;
+ if (args.length < 2 || args[1] == null || args[1] == Undefined.instance)
+ {
+ thisArg = parent;
+ } else {
+ thisArg = ScriptRuntime.toObject(cx, scope, args[1]);
+ }
+ long length = getLengthProperty(cx, thisObj);
+ Scriptable array = ScriptRuntime.newObject(cx, scope, "Array", null);
+ long j=0;
+ for (long i=0; i < length; i++) {
+ Object[] innerArgs = new Object[3];
+ Object elem = (i > Integer.MAX_VALUE)
+ ? ScriptableObject.getProperty(thisObj, Long.toString(i))
+ : ScriptableObject.getProperty(thisObj, (int)i);
+ if (elem == Scriptable.NOT_FOUND) {
+ continue;
+ }
+ innerArgs[0] = elem;
+ innerArgs[1] = new Long(i);
+ innerArgs[2] = thisObj;
+ Object result = f.call(cx, parent, thisArg, innerArgs);
+ switch (id) {
+ case Id_every:
+ if (!ScriptRuntime.toBoolean(result))
+ return Boolean.FALSE;
+ break;
+ case Id_filter:
+ if (ScriptRuntime.toBoolean(result))
+ setElem(cx, array, j++, innerArgs[0]);
+ break;
+ case Id_forEach:
+ break;
+ case Id_map:
+ setElem(cx, array, i, result);
+ break;
+ case Id_some:
+ if (ScriptRuntime.toBoolean(result))
+ return Boolean.TRUE;
+ break;
+ }
+ }
+ switch (id) {
+ case Id_every:
+ return Boolean.TRUE;
+ case Id_filter:
+ case Id_map:
+ return array;
+ case Id_some:
+ return Boolean.FALSE;
+ case Id_forEach:
+ default:
+ return Undefined.instance;
+ }
+ }
+
+// #string_id_map#
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #generated# Last update: 2005-09-26 15:47:42 EDT
+ L0: { id = 0; String X = null; int c;
+ L: switch (s.length()) {
+ case 3: c=s.charAt(0);
+ if (c=='m') { if (s.charAt(2)=='p' && s.charAt(1)=='a') {id=Id_map; break L0;} }
+ else if (c=='p') { if (s.charAt(2)=='p' && s.charAt(1)=='o') {id=Id_pop; break L0;} }
+ break L;
+ case 4: switch (s.charAt(2)) {
+ case 'i': X="join";id=Id_join; break L;
+ case 'm': X="some";id=Id_some; break L;
+ case 'r': X="sort";id=Id_sort; break L;
+ case 's': X="push";id=Id_push; break L;
+ } break L;
+ case 5: c=s.charAt(1);
+ if (c=='h') { X="shift";id=Id_shift; }
+ else if (c=='l') { X="slice";id=Id_slice; }
+ else if (c=='v') { X="every";id=Id_every; }
+ break L;
+ case 6: c=s.charAt(0);
+ if (c=='c') { X="concat";id=Id_concat; }
+ else if (c=='f') { X="filter";id=Id_filter; }
+ else if (c=='s') { X="splice";id=Id_splice; }
+ break L;
+ case 7: switch (s.charAt(0)) {
+ case 'f': X="forEach";id=Id_forEach; break L;
+ case 'i': X="indexOf";id=Id_indexOf; break L;
+ case 'r': X="reverse";id=Id_reverse; break L;
+ case 'u': X="unshift";id=Id_unshift; break L;
+ } break L;
+ case 8: c=s.charAt(3);
+ if (c=='o') { X="toSource";id=Id_toSource; }
+ else if (c=='t') { X="toString";id=Id_toString; }
+ break L;
+ case 11: c=s.charAt(0);
+ if (c=='c') { X="constructor";id=Id_constructor; }
+ else if (c=='l') { X="lastIndexOf";id=Id_lastIndexOf; }
+ break L;
+ case 14: X="toLocaleString";id=Id_toLocaleString; break L;
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_constructor = 1,
+ Id_toString = 2,
+ Id_toLocaleString = 3,
+ Id_toSource = 4,
+ Id_join = 5,
+ Id_reverse = 6,
+ Id_sort = 7,
+ Id_push = 8,
+ Id_pop = 9,
+ Id_shift = 10,
+ Id_unshift = 11,
+ Id_splice = 12,
+ Id_concat = 13,
+ Id_slice = 14,
+ Id_indexOf = 15,
+ Id_lastIndexOf = 16,
+ Id_every = 17,
+ Id_filter = 18,
+ Id_forEach = 19,
+ Id_map = 20,
+ Id_some = 21,
+
+ MAX_PROTOTYPE_ID = 21;
+
+// #/string_id_map#
+
+ private static final int
+ ConstructorId_join = -Id_join,
+ ConstructorId_reverse = -Id_reverse,
+ ConstructorId_sort = -Id_sort,
+ ConstructorId_push = -Id_push,
+ ConstructorId_pop = -Id_pop,
+ ConstructorId_shift = -Id_shift,
+ ConstructorId_unshift = -Id_unshift,
+ ConstructorId_splice = -Id_splice,
+ ConstructorId_concat = -Id_concat,
+ ConstructorId_slice = -Id_slice,
+ ConstructorId_indexOf = -Id_indexOf,
+ ConstructorId_lastIndexOf = -Id_lastIndexOf,
+ ConstructorId_every = -Id_every,
+ ConstructorId_filter = -Id_filter,
+ ConstructorId_forEach = -Id_forEach,
+ ConstructorId_map = -Id_map,
+ ConstructorId_some = -Id_some;
+
+ /**
+ * Internal representation of the JavaScript array's length property.
+ */
+ private long length;
+
+ /**
+ * Fast storage for dense arrays. Sparse arrays will use the superclass's
+ * hashtable storage scheme.
+ */
+ private Object[] dense;
+
+ /**
+ * True if all numeric properties are stored in <code>dense</code>.
+ */
+ private boolean denseOnly;
+
+ /**
+ * The maximum size of <code>dense</code> that will be allocated initially.
+ */
+ private static int maximumInitialCapacity = 10000;
+
+ /**
+ * The default capacity for <code>dense</code>.
+ */
+ private static final int DEFAULT_INITIAL_CAPACITY = 10;
+
+ /**
+ * The factor to grow <code>dense</code> by.
+ */
+ private static final double GROW_FACTOR = 1.5;
+ private static final int MAX_PRE_GROW_SIZE = (int)(Integer.MAX_VALUE / GROW_FACTOR);
+}
diff --git a/src/org/mozilla/javascript/NativeBoolean.java b/src/org/mozilla/javascript/NativeBoolean.java
new file mode 100644
index 0000000..07d1482
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeBoolean.java
@@ -0,0 +1,175 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Mike McCabe
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * This class implements the Boolean native object.
+ * See ECMA 15.6.
+ * @author Norris Boyd
+ */
+final class NativeBoolean extends IdScriptableObject
+{
+ static final long serialVersionUID = -3716996899943880933L;
+
+ private static final Object BOOLEAN_TAG = "Boolean";
+
+ static void init(Scriptable scope, boolean sealed)
+ {
+ NativeBoolean obj = new NativeBoolean(false);
+ obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+ }
+
+ private NativeBoolean(boolean b)
+ {
+ booleanValue = b;
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return "Boolean";
+ }
+
+ @Override
+ public Object getDefaultValue(Class<?> typeHint) {
+ // This is actually non-ECMA, but will be proposed
+ // as a change in round 2.
+ if (typeHint == ScriptRuntime.BooleanClass)
+ return ScriptRuntime.wrapBoolean(booleanValue);
+ return super.getDefaultValue(typeHint);
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_constructor: arity=1; s="constructor"; break;
+ case Id_toString: arity=0; s="toString"; break;
+ case Id_toSource: arity=0; s="toSource"; break;
+ case Id_valueOf: arity=0; s="valueOf"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(BOOLEAN_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(BOOLEAN_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+
+ if (id == Id_constructor) {
+ boolean b;
+ if (args.length == 0) {
+ b = false;
+ } else {
+ b = args[0] instanceof ScriptableObject &&
+ ((ScriptableObject) args[0]).avoidObjectDetection()
+ ? true
+ : ScriptRuntime.toBoolean(args[0]);
+ }
+ if (thisObj == null) {
+ // new Boolean(val) creates a new boolean object.
+ return new NativeBoolean(b);
+ }
+ // Boolean(val) converts val to a boolean.
+ return ScriptRuntime.wrapBoolean(b);
+ }
+
+ // The rest of Boolean.prototype methods require thisObj to be Boolean
+
+ if (!(thisObj instanceof NativeBoolean))
+ throw incompatibleCallError(f);
+ boolean value = ((NativeBoolean)thisObj).booleanValue;
+
+ switch (id) {
+
+ case Id_toString:
+ return value ? "true" : "false";
+
+ case Id_toSource:
+ return value ? "(new Boolean(true))" : "(new Boolean(false))";
+
+ case Id_valueOf:
+ return ScriptRuntime.wrapBoolean(value);
+ }
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+
+// #string_id_map#
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #generated# Last update: 2007-05-09 08:15:31 EDT
+ L0: { id = 0; String X = null; int c;
+ int s_length = s.length();
+ if (s_length==7) { X="valueOf";id=Id_valueOf; }
+ else if (s_length==8) {
+ c=s.charAt(3);
+ if (c=='o') { X="toSource";id=Id_toSource; }
+ else if (c=='t') { X="toString";id=Id_toString; }
+ }
+ else if (s_length==11) { X="constructor";id=Id_constructor; }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_constructor = 1,
+ Id_toString = 2,
+ Id_toSource = 3,
+ Id_valueOf = 4,
+ MAX_PROTOTYPE_ID = 4;
+
+// #/string_id_map#
+
+ private boolean booleanValue;
+}
diff --git a/src/org/mozilla/javascript/NativeCall.java b/src/org/mozilla/javascript/NativeCall.java
new file mode 100644
index 0000000..f4e1949
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeCall.java
@@ -0,0 +1,158 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Bob Jervis
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * This class implements the activation object.
+ *
+ * See ECMA 10.1.6
+ *
+ * @see org.mozilla.javascript.Arguments
+ * @author Norris Boyd
+ */
+public final class NativeCall extends IdScriptableObject
+{
+ static final long serialVersionUID = -7471457301304454454L;
+
+ private static final Object CALL_TAG = "Call";
+
+ static void init(Scriptable scope, boolean sealed)
+ {
+ NativeCall obj = new NativeCall();
+ obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+ }
+
+ NativeCall() { }
+
+ NativeCall(NativeFunction function, Scriptable scope, Object[] args)
+ {
+ this.function = function;
+
+ setParentScope(scope);
+ // leave prototype null
+
+ this.originalArgs = (args == null) ? ScriptRuntime.emptyArgs : args;
+
+ // initialize values of arguments
+ int paramAndVarCount = function.getParamAndVarCount();
+ int paramCount = function.getParamCount();
+ if (paramAndVarCount != 0) {
+ for (int i = 0; i < paramCount; ++i) {
+ String name = function.getParamOrVarName(i);
+ Object val = i < args.length ? args[i]
+ : Undefined.instance;
+ defineProperty(name, val, PERMANENT);
+ }
+ }
+
+ // initialize "arguments" property but only if it was not overridden by
+ // the parameter with the same name
+ if (!super.has("arguments", this)) {
+ defineProperty("arguments", new Arguments(this), PERMANENT);
+ }
+
+ if (paramAndVarCount != 0) {
+ for (int i = paramCount; i < paramAndVarCount; ++i) {
+ String name = function.getParamOrVarName(i);
+ if (!super.has(name, this)) {
+ if (function.getParamOrVarConst(i))
+ defineProperty(name, Undefined.instance, CONST);
+ else
+ defineProperty(name, Undefined.instance, PERMANENT);
+ }
+ }
+ }
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return "Call";
+ }
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ return s.equals("constructor") ? Id_constructor : 0;
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ if (id == Id_constructor) {
+ arity=1; s="constructor";
+ } else {
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(CALL_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(CALL_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+ if (id == Id_constructor) {
+ if (thisObj != null) {
+ throw Context.reportRuntimeError1("msg.only.from.new", "Call");
+ }
+ ScriptRuntime.checkDeprecated(cx, "Call");
+ NativeCall result = new NativeCall();
+ result.setPrototype(getObjectPrototype(scope));
+ return result;
+ }
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+
+ private static final int
+ Id_constructor = 1,
+ MAX_PROTOTYPE_ID = 1;
+
+ NativeFunction function;
+ Object[] originalArgs;
+
+ transient NativeCall parentActivationCall;
+}
+
diff --git a/src/org/mozilla/javascript/NativeContinuation.java b/src/org/mozilla/javascript/NativeContinuation.java
new file mode 100644
index 0000000..5ed53d8
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeContinuation.java
@@ -0,0 +1,139 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+public final class NativeContinuation extends IdScriptableObject
+ implements Function
+{
+ static final long serialVersionUID = 1794167133757605367L;
+
+ private static final Object FTAG = "Continuation";
+
+ private Object implementation;
+
+ public static void init(Context cx, Scriptable scope, boolean sealed)
+ {
+ NativeContinuation obj = new NativeContinuation();
+ obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+ }
+
+ public Object getImplementation()
+ {
+ return implementation;
+ }
+
+ public void initImplementation(Object implementation)
+ {
+ this.implementation = implementation;
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return "Continuation";
+ }
+
+ public Scriptable construct(Context cx, Scriptable scope, Object[] args)
+ {
+ throw Context.reportRuntimeError("Direct call is not supported");
+ }
+
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ return Interpreter.restartContinuation(this, cx, scope, args);
+ }
+
+ public static boolean isContinuationConstructor(IdFunctionObject f)
+ {
+ if (f.hasTag(FTAG) && f.methodId() == Id_constructor) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_constructor: arity=0; s="constructor"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(FTAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(FTAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+ switch (id) {
+ case Id_constructor:
+ throw Context.reportRuntimeError("Direct call is not supported");
+ }
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+
+// #string_id_map#
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #generated# Last update: 2007-05-09 08:16:40 EDT
+ L0: { id = 0; String X = null;
+ if (s.length()==11) { X="constructor";id=Id_constructor; }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_constructor = 1,
+ MAX_PROTOTYPE_ID = 1;
+
+// #/string_id_map#
+}
diff --git a/src/org/mozilla/javascript/NativeDate.java b/src/org/mozilla/javascript/NativeDate.java
new file mode 100644
index 0000000..2eb8145
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeDate.java
@@ -0,0 +1,1610 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Peter Annema
+ * Norris Boyd
+ * Mike McCabe
+ * Ilya Frank
+ *
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.util.Date;
+import java.text.DateFormat;
+
+/**
+ * This class implements the Date native object.
+ * See ECMA 15.9.
+ * @author Mike McCabe
+ */
+final class NativeDate extends IdScriptableObject
+{
+ static final long serialVersionUID = -8307438915861678966L;
+
+ private static final Object DATE_TAG = "Date";
+
+ private static final String js_NaN_date_str = "Invalid Date";
+
+ static void init(Scriptable scope, boolean sealed)
+ {
+ NativeDate obj = new NativeDate();
+ // Set the value of the prototype Date to NaN ('invalid date');
+ obj.date = ScriptRuntime.NaN;
+ obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+ }
+
+ private NativeDate()
+ {
+ if (thisTimeZone == null) {
+ // j.u.TimeZone is synchronized, so setting class statics from it
+ // should be OK.
+ thisTimeZone = java.util.TimeZone.getDefault();
+ LocalTZA = thisTimeZone.getRawOffset();
+ }
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return "Date";
+ }
+
+ @Override
+ public Object getDefaultValue(Class<?> typeHint)
+ {
+ if (typeHint == null)
+ typeHint = ScriptRuntime.StringClass;
+ return super.getDefaultValue(typeHint);
+ }
+
+ double getJSTimeValue()
+ {
+ return date;
+ }
+
+ @Override
+ protected void fillConstructorProperties(IdFunctionObject ctor)
+ {
+ addIdFunctionProperty(ctor, DATE_TAG, ConstructorId_now,
+ "now", 0);
+ addIdFunctionProperty(ctor, DATE_TAG, ConstructorId_parse,
+ "parse", 1);
+ addIdFunctionProperty(ctor, DATE_TAG, ConstructorId_UTC,
+ "UTC", 1);
+ super.fillConstructorProperties(ctor);
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_constructor: arity=1; s="constructor"; break;
+ case Id_toString: arity=0; s="toString"; break;
+ case Id_toTimeString: arity=0; s="toTimeString"; break;
+ case Id_toDateString: arity=0; s="toDateString"; break;
+ case Id_toLocaleString: arity=0; s="toLocaleString"; break;
+ case Id_toLocaleTimeString: arity=0; s="toLocaleTimeString"; break;
+ case Id_toLocaleDateString: arity=0; s="toLocaleDateString"; break;
+ case Id_toUTCString: arity=0; s="toUTCString"; break;
+ case Id_toSource: arity=0; s="toSource"; break;
+ case Id_valueOf: arity=0; s="valueOf"; break;
+ case Id_getTime: arity=0; s="getTime"; break;
+ case Id_getYear: arity=0; s="getYear"; break;
+ case Id_getFullYear: arity=0; s="getFullYear"; break;
+ case Id_getUTCFullYear: arity=0; s="getUTCFullYear"; break;
+ case Id_getMonth: arity=0; s="getMonth"; break;
+ case Id_getUTCMonth: arity=0; s="getUTCMonth"; break;
+ case Id_getDate: arity=0; s="getDate"; break;
+ case Id_getUTCDate: arity=0; s="getUTCDate"; break;
+ case Id_getDay: arity=0; s="getDay"; break;
+ case Id_getUTCDay: arity=0; s="getUTCDay"; break;
+ case Id_getHours: arity=0; s="getHours"; break;
+ case Id_getUTCHours: arity=0; s="getUTCHours"; break;
+ case Id_getMinutes: arity=0; s="getMinutes"; break;
+ case Id_getUTCMinutes: arity=0; s="getUTCMinutes"; break;
+ case Id_getSeconds: arity=0; s="getSeconds"; break;
+ case Id_getUTCSeconds: arity=0; s="getUTCSeconds"; break;
+ case Id_getMilliseconds: arity=0; s="getMilliseconds"; break;
+ case Id_getUTCMilliseconds: arity=0; s="getUTCMilliseconds"; break;
+ case Id_getTimezoneOffset: arity=0; s="getTimezoneOffset"; break;
+ case Id_setTime: arity=1; s="setTime"; break;
+ case Id_setMilliseconds: arity=1; s="setMilliseconds"; break;
+ case Id_setUTCMilliseconds: arity=1; s="setUTCMilliseconds"; break;
+ case Id_setSeconds: arity=2; s="setSeconds"; break;
+ case Id_setUTCSeconds: arity=2; s="setUTCSeconds"; break;
+ case Id_setMinutes: arity=3; s="setMinutes"; break;
+ case Id_setUTCMinutes: arity=3; s="setUTCMinutes"; break;
+ case Id_setHours: arity=4; s="setHours"; break;
+ case Id_setUTCHours: arity=4; s="setUTCHours"; break;
+ case Id_setDate: arity=1; s="setDate"; break;
+ case Id_setUTCDate: arity=1; s="setUTCDate"; break;
+ case Id_setMonth: arity=2; s="setMonth"; break;
+ case Id_setUTCMonth: arity=2; s="setUTCMonth"; break;
+ case Id_setFullYear: arity=3; s="setFullYear"; break;
+ case Id_setUTCFullYear: arity=3; s="setUTCFullYear"; break;
+ case Id_setYear: arity=1; s="setYear"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(DATE_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(DATE_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+ switch (id) {
+ case ConstructorId_now:
+ return ScriptRuntime.wrapNumber(now());
+
+ case ConstructorId_parse:
+ {
+ String dataStr = ScriptRuntime.toString(args, 0);
+ return ScriptRuntime.wrapNumber(date_parseString(dataStr));
+ }
+
+ case ConstructorId_UTC:
+ return ScriptRuntime.wrapNumber(jsStaticFunction_UTC(args));
+
+ case Id_constructor:
+ {
+ // if called as a function, just return a string
+ // representing the current time.
+ if (thisObj != null)
+ return date_format(now(), Id_toString);
+ return jsConstructor(args);
+ }
+ }
+
+ // The rest of Date.prototype methods require thisObj to be Date
+
+ if (!(thisObj instanceof NativeDate))
+ throw incompatibleCallError(f);
+ NativeDate realThis = (NativeDate)thisObj;
+ double t = realThis.date;
+
+ switch (id) {
+
+ case Id_toString:
+ case Id_toTimeString:
+ case Id_toDateString:
+ if (t == t) {
+ return date_format(t, id);
+ }
+ return js_NaN_date_str;
+
+ case Id_toLocaleString:
+ case Id_toLocaleTimeString:
+ case Id_toLocaleDateString:
+ if (t == t) {
+ return toLocale_helper(t, id);
+ }
+ return js_NaN_date_str;
+
+ case Id_toUTCString:
+ if (t == t) {
+ return js_toUTCString(t);
+ }
+ return js_NaN_date_str;
+
+ case Id_toSource:
+ return "(new Date("+ScriptRuntime.toString(t)+"))";
+
+ case Id_valueOf:
+ case Id_getTime:
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_getYear:
+ case Id_getFullYear:
+ case Id_getUTCFullYear:
+ if (t == t) {
+ if (id != Id_getUTCFullYear) t = LocalTime(t);
+ t = YearFromTime(t);
+ if (id == Id_getYear) {
+ if (cx.hasFeature(Context.FEATURE_NON_ECMA_GET_YEAR)) {
+ if (1900 <= t && t < 2000) {
+ t -= 1900;
+ }
+ } else {
+ t -= 1900;
+ }
+ }
+ }
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_getMonth:
+ case Id_getUTCMonth:
+ if (t == t) {
+ if (id == Id_getMonth) t = LocalTime(t);
+ t = MonthFromTime(t);
+ }
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_getDate:
+ case Id_getUTCDate:
+ if (t == t) {
+ if (id == Id_getDate) t = LocalTime(t);
+ t = DateFromTime(t);
+ }
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_getDay:
+ case Id_getUTCDay:
+ if (t == t) {
+ if (id == Id_getDay) t = LocalTime(t);
+ t = WeekDay(t);
+ }
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_getHours:
+ case Id_getUTCHours:
+ if (t == t) {
+ if (id == Id_getHours) t = LocalTime(t);
+ t = HourFromTime(t);
+ }
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_getMinutes:
+ case Id_getUTCMinutes:
+ if (t == t) {
+ if (id == Id_getMinutes) t = LocalTime(t);
+ t = MinFromTime(t);
+ }
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_getSeconds:
+ case Id_getUTCSeconds:
+ if (t == t) {
+ if (id == Id_getSeconds) t = LocalTime(t);
+ t = SecFromTime(t);
+ }
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_getMilliseconds:
+ case Id_getUTCMilliseconds:
+ if (t == t) {
+ if (id == Id_getMilliseconds) t = LocalTime(t);
+ t = msFromTime(t);
+ }
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_getTimezoneOffset:
+ if (t == t) {
+ t = (t - LocalTime(t)) / msPerMinute;
+ }
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_setTime:
+ t = TimeClip(ScriptRuntime.toNumber(args, 0));
+ realThis.date = t;
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_setMilliseconds:
+ case Id_setUTCMilliseconds:
+ case Id_setSeconds:
+ case Id_setUTCSeconds:
+ case Id_setMinutes:
+ case Id_setUTCMinutes:
+ case Id_setHours:
+ case Id_setUTCHours:
+ t = makeTime(t, args, id);
+ realThis.date = t;
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_setDate:
+ case Id_setUTCDate:
+ case Id_setMonth:
+ case Id_setUTCMonth:
+ case Id_setFullYear:
+ case Id_setUTCFullYear:
+ t = makeDate(t, args, id);
+ realThis.date = t;
+ return ScriptRuntime.wrapNumber(t);
+
+ case Id_setYear:
+ {
+ double year = ScriptRuntime.toNumber(args, 0);
+
+ if (year != year || Double.isInfinite(year)) {
+ t = ScriptRuntime.NaN;
+ } else {
+ if (t != t) {
+ t = 0;
+ } else {
+ t = LocalTime(t);
+ }
+
+ if (year >= 0 && year <= 99)
+ year += 1900;
+
+ double day = MakeDay(year, MonthFromTime(t),
+ DateFromTime(t));
+ t = MakeDate(day, TimeWithinDay(t));
+ t = internalUTC(t);
+ t = TimeClip(t);
+ }
+ }
+ realThis.date = t;
+ return ScriptRuntime.wrapNumber(t);
+
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+
+ }
+
+ /* ECMA helper functions */
+
+ private static final double HalfTimeDomain = 8.64e15;
+ private static final double HoursPerDay = 24.0;
+ private static final double MinutesPerHour = 60.0;
+ private static final double SecondsPerMinute = 60.0;
+ private static final double msPerSecond = 1000.0;
+ private static final double MinutesPerDay = (HoursPerDay * MinutesPerHour);
+ private static final double SecondsPerDay = (MinutesPerDay * SecondsPerMinute);
+ private static final double SecondsPerHour = (MinutesPerHour * SecondsPerMinute);
+ private static final double msPerDay = (SecondsPerDay * msPerSecond);
+ private static final double msPerHour = (SecondsPerHour * msPerSecond);
+ private static final double msPerMinute = (SecondsPerMinute * msPerSecond);
+
+ private static double Day(double t)
+ {
+ return Math.floor(t / msPerDay);
+ }
+
+ private static double TimeWithinDay(double t)
+ {
+ double result;
+ result = t % msPerDay;
+ if (result < 0)
+ result += msPerDay;
+ return result;
+ }
+
+ private static boolean IsLeapYear(int year)
+ {
+ return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
+ }
+
+ /* math here has to be f.p, because we need
+ * floor((1968 - 1969) / 4) == -1
+ */
+ private static double DayFromYear(double y)
+ {
+ return ((365 * ((y)-1970) + Math.floor(((y)-1969)/4.0)
+ - Math.floor(((y)-1901)/100.0) + Math.floor(((y)-1601)/400.0)));
+ }
+
+ private static double TimeFromYear(double y)
+ {
+ return DayFromYear(y) * msPerDay;
+ }
+
+ private static int YearFromTime(double t)
+ {
+ int lo = (int) Math.floor((t / msPerDay) / 366) + 1970;
+ int hi = (int) Math.floor((t / msPerDay) / 365) + 1970;
+ int mid;
+
+ /* above doesn't work for negative dates... */
+ if (hi < lo) {
+ int temp = lo;
+ lo = hi;
+ hi = temp;
+ }
+
+ /* Use a simple binary search algorithm to find the right
+ year. This seems like brute force... but the computation
+ of hi and lo years above lands within one year of the
+ correct answer for years within a thousand years of
+ 1970; the loop below only requires six iterations
+ for year 270000. */
+ while (hi > lo) {
+ mid = (hi + lo) / 2;
+ if (TimeFromYear(mid) > t) {
+ hi = mid - 1;
+ } else {
+ lo = mid + 1;
+ if (TimeFromYear(lo) > t) {
+ return mid;
+ }
+ }
+ }
+ return lo;
+ }
+
+ private static double DayFromMonth(int m, int year)
+ {
+ int day = m * 30;
+
+ if (m >= 7) { day += m / 2 - 1; }
+ else if (m >= 2) { day += (m - 1) / 2 - 1; }
+ else { day += m; }
+
+ if (m >= 2 && IsLeapYear(year)) { ++day; }
+
+ return day;
+ }
+
+ private static int MonthFromTime(double t)
+ {
+ int year = YearFromTime(t);
+ int d = (int)(Day(t) - DayFromYear(year));
+
+ d -= 31 + 28;
+ if (d < 0) {
+ return (d < -28) ? 0 : 1;
+ }
+
+ if (IsLeapYear(year)) {
+ if (d == 0)
+ return 1; // 29 February
+ --d;
+ }
+
+ // d: date count from 1 March
+ int estimate = d / 30; // approx number of month since March
+ int mstart;
+ switch (estimate) {
+ case 0: return 2;
+ case 1: mstart = 31; break;
+ case 2: mstart = 31+30; break;
+ case 3: mstart = 31+30+31; break;
+ case 4: mstart = 31+30+31+30; break;
+ case 5: mstart = 31+30+31+30+31; break;
+ case 6: mstart = 31+30+31+30+31+31; break;
+ case 7: mstart = 31+30+31+30+31+31+30; break;
+ case 8: mstart = 31+30+31+30+31+31+30+31; break;
+ case 9: mstart = 31+30+31+30+31+31+30+31+30; break;
+ case 10: return 11; //Late december
+ default: throw Kit.codeBug();
+ }
+ // if d < mstart then real month since March == estimate - 1
+ return (d >= mstart) ? estimate + 2 : estimate + 1;
+ }
+
+ private static int DateFromTime(double t)
+ {
+ int year = YearFromTime(t);
+ int d = (int)(Day(t) - DayFromYear(year));
+
+ d -= 31 + 28;
+ if (d < 0) {
+ return (d < -28) ? d + 31 + 28 + 1 : d + 28 + 1;
+ }
+
+ if (IsLeapYear(year)) {
+ if (d == 0)
+ return 29; // 29 February
+ --d;
+ }
+
+ // d: date count from 1 March
+ int mdays, mstart;
+ switch (d / 30) { // approx number of month since March
+ case 0: return d + 1;
+ case 1: mdays = 31; mstart = 31; break;
+ case 2: mdays = 30; mstart = 31+30; break;
+ case 3: mdays = 31; mstart = 31+30+31; break;
+ case 4: mdays = 30; mstart = 31+30+31+30; break;
+ case 5: mdays = 31; mstart = 31+30+31+30+31; break;
+ case 6: mdays = 31; mstart = 31+30+31+30+31+31; break;
+ case 7: mdays = 30; mstart = 31+30+31+30+31+31+30; break;
+ case 8: mdays = 31; mstart = 31+30+31+30+31+31+30+31; break;
+ case 9: mdays = 30; mstart = 31+30+31+30+31+31+30+31+30; break;
+ case 10:
+ return d - (31+30+31+30+31+31+30+31+30) + 1; //Late december
+ default: throw Kit.codeBug();
+ }
+ d -= mstart;
+ if (d < 0) {
+ // wrong estimate: sfhift to previous month
+ d += mdays;
+ }
+ return d + 1;
+ }
+
+ private static int WeekDay(double t)
+ {
+ double result;
+ result = Day(t) + 4;
+ result = result % 7;
+ if (result < 0)
+ result += 7;
+ return (int) result;
+ }
+
+ private static double now()
+ {
+ return System.currentTimeMillis();
+ }
+
+ /* Should be possible to determine the need for this dynamically
+ * if we go with the workaround... I'm not using it now, because I
+ * can't think of any clean way to make toLocaleString() and the
+ * time zone (comment) in toString match the generated string
+ * values. Currently it's wrong-but-consistent in all but the
+ * most recent betas of the JRE - seems to work in 1.1.7.
+ */
+ private final static boolean TZO_WORKAROUND = false;
+ private static double DaylightSavingTA(double t)
+ {
+ // Another workaround! The JRE doesn't seem to know about DST
+ // before year 1 AD, so we map to equivalent dates for the
+ // purposes of finding dst. To be safe, we do this for years
+ // outside 1970-2038.
+ if (t < 0.0 || t > 2145916800000.0) {
+ int year = EquivalentYear(YearFromTime(t));
+ double day = MakeDay(year, MonthFromTime(t), DateFromTime(t));
+ t = MakeDate(day, TimeWithinDay(t));
+ }
+ if (!TZO_WORKAROUND) {
+ Date date = new Date((long) t);
+ if (thisTimeZone.inDaylightTime(date))
+ return msPerHour;
+ else
+ return 0;
+ } else {
+ /* Use getOffset if inDaylightTime() is broken, because it
+ * seems to work acceptably. We don't switch over to it
+ * entirely, because it requires (expensive) exploded date arguments,
+ * and the api makes it impossible to handle dst
+ * changeovers cleanly.
+ */
+
+ // Hardcode the assumption that the changeover always
+ // happens at 2:00 AM:
+ t += LocalTZA + (HourFromTime(t) <= 2 ? msPerHour : 0);
+
+ int year = YearFromTime(t);
+ double offset = thisTimeZone.getOffset(year > 0 ? 1 : 0,
+ year,
+ MonthFromTime(t),
+ DateFromTime(t),
+ WeekDay(t),
+ (int)TimeWithinDay(t));
+
+ if ((offset - LocalTZA) != 0)
+ return msPerHour;
+ else
+ return 0;
+ // return offset - LocalTZA;
+ }
+ }
+
+ /*
+ * Find a year for which any given date will fall on the same weekday.
+ *
+ * This function should be used with caution when used other than
+ * for determining DST; it hasn't been proven not to produce an
+ * incorrect year for times near year boundaries.
+ */
+ private static int EquivalentYear(int year)
+ {
+ int day = (int) DayFromYear(year) + 4;
+ day = day % 7;
+ if (day < 0)
+ day += 7;
+ // Years and leap years on which Jan 1 is a Sunday, Monday, etc.
+ if (IsLeapYear(year)) {
+ switch (day) {
+ case 0: return 1984;
+ case 1: return 1996;
+ case 2: return 1980;
+ case 3: return 1992;
+ case 4: return 1976;
+ case 5: return 1988;
+ case 6: return 1972;
+ }
+ } else {
+ switch (day) {
+ case 0: return 1978;
+ case 1: return 1973;
+ case 2: return 1974;
+ case 3: return 1975;
+ case 4: return 1981;
+ case 5: return 1971;
+ case 6: return 1977;
+ }
+ }
+ // Unreachable
+ throw Kit.codeBug();
+ }
+
+ private static double LocalTime(double t)
+ {
+ return t + LocalTZA + DaylightSavingTA(t);
+ }
+
+ private static double internalUTC(double t)
+ {
+ return t - LocalTZA - DaylightSavingTA(t - LocalTZA);
+ }
+
+ private static int HourFromTime(double t)
+ {
+ double result;
+ result = Math.floor(t / msPerHour) % HoursPerDay;
+ if (result < 0)
+ result += HoursPerDay;
+ return (int) result;
+ }
+
+ private static int MinFromTime(double t)
+ {
+ double result;
+ result = Math.floor(t / msPerMinute) % MinutesPerHour;
+ if (result < 0)
+ result += MinutesPerHour;
+ return (int) result;
+ }
+
+ private static int SecFromTime(double t)
+ {
+ double result;
+ result = Math.floor(t / msPerSecond) % SecondsPerMinute;
+ if (result < 0)
+ result += SecondsPerMinute;
+ return (int) result;
+ }
+
+ private static int msFromTime(double t)
+ {
+ double result;
+ result = t % msPerSecond;
+ if (result < 0)
+ result += msPerSecond;
+ return (int) result;
+ }
+
+ private static double MakeTime(double hour, double min,
+ double sec, double ms)
+ {
+ return ((hour * MinutesPerHour + min) * SecondsPerMinute + sec)
+ * msPerSecond + ms;
+ }
+
+ private static double MakeDay(double year, double month, double date)
+ {
+ year += Math.floor(month / 12);
+
+ month = month % 12;
+ if (month < 0)
+ month += 12;
+
+ double yearday = Math.floor(TimeFromYear(year) / msPerDay);
+ double monthday = DayFromMonth((int)month, (int)year);
+
+ return yearday + monthday + date - 1;
+ }
+
+ private static double MakeDate(double day, double time)
+ {
+ return day * msPerDay + time;
+ }
+
+ private static double TimeClip(double d)
+ {
+ if (d != d ||
+ d == Double.POSITIVE_INFINITY ||
+ d == Double.NEGATIVE_INFINITY ||
+ Math.abs(d) > HalfTimeDomain)
+ {
+ return ScriptRuntime.NaN;
+ }
+ if (d > 0.0)
+ return Math.floor(d + 0.);
+ else
+ return Math.ceil(d + 0.);
+ }
+
+ /* end of ECMA helper functions */
+
+ /* find UTC time from given date... no 1900 correction! */
+ private static double date_msecFromDate(double year, double mon,
+ double mday, double hour,
+ double min, double sec,
+ double msec)
+ {
+ double day;
+ double time;
+ double result;
+
+ day = MakeDay(year, mon, mday);
+ time = MakeTime(hour, min, sec, msec);
+ result = MakeDate(day, time);
+ return result;
+ }
+
+ /* compute the time in msec (unclipped) from the given args */
+ private static final int MAXARGS = 7;
+ private static double date_msecFromArgs(Object[] args)
+ {
+ double array[] = new double[MAXARGS];
+ int loop;
+ double d;
+
+ for (loop = 0; loop < MAXARGS; loop++) {
+ if (loop < args.length) {
+ d = ScriptRuntime.toNumber(args[loop]);
+ if (d != d || Double.isInfinite(d)) {
+ return ScriptRuntime.NaN;
+ }
+ array[loop] = ScriptRuntime.toInteger(args[loop]);
+ } else {
+ if (loop == 2) {
+ array[loop] = 1; /* Default the date argument to 1. */
+ } else {
+ array[loop] = 0;
+ }
+ }
+ }
+
+ /* adjust 2-digit years into the 20th century */
+ if (array[0] >= 0 && array[0] <= 99)
+ array[0] += 1900;
+
+ return date_msecFromDate(array[0], array[1], array[2],
+ array[3], array[4], array[5], array[6]);
+ }
+
+ private static double jsStaticFunction_UTC(Object[] args)
+ {
+ return TimeClip(date_msecFromArgs(args));
+ }
+
+ private static double date_parseString(String s)
+ {
+ int year = -1;
+ int mon = -1;
+ int mday = -1;
+ int hour = -1;
+ int min = -1;
+ int sec = -1;
+ char c = 0;
+ char si = 0;
+ int i = 0;
+ int n = -1;
+ double tzoffset = -1;
+ char prevc = 0;
+ int limit = 0;
+ boolean seenplusminus = false;
+
+ limit = s.length();
+ while (i < limit) {
+ c = s.charAt(i);
+ i++;
+ if (c <= ' ' || c == ',' || c == '-') {
+ if (i < limit) {
+ si = s.charAt(i);
+ if (c == '-' && '0' <= si && si <= '9') {
+ prevc = c;
+ }
+ }
+ continue;
+ }
+ if (c == '(') { /* comments) */
+ int depth = 1;
+ while (i < limit) {
+ c = s.charAt(i);
+ i++;
+ if (c == '(')
+ depth++;
+ else if (c == ')')
+ if (--depth <= 0)
+ break;
+ }
+ continue;
+ }
+ if ('0' <= c && c <= '9') {
+ n = c - '0';
+ while (i < limit && '0' <= (c = s.charAt(i)) && c <= '9') {
+ n = n * 10 + c - '0';
+ i++;
+ }
+
+ /* allow TZA before the year, so
+ * 'Wed Nov 05 21:49:11 GMT-0800 1997'
+ * works */
+
+ /* uses of seenplusminus allow : in TZA, so Java
+ * no-timezone style of GMT+4:30 works
+ */
+ if ((prevc == '+' || prevc == '-')/* && year>=0 */) {
+ /* make ':' case below change tzoffset */
+ seenplusminus = true;
+
+ /* offset */
+ if (n < 24)
+ n = n * 60; /* EG. "GMT-3" */
+ else
+ n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
+ if (prevc == '+') /* plus means east of GMT */
+ n = -n;
+ if (tzoffset != 0 && tzoffset != -1)
+ return ScriptRuntime.NaN;
+ tzoffset = n;
+ } else if (n >= 70 ||
+ (prevc == '/' && mon >= 0 && mday >= 0
+ && year < 0))
+ {
+ if (year >= 0)
+ return ScriptRuntime.NaN;
+ else if (c <= ' ' || c == ',' || c == '/' || i >= limit)
+ year = n < 100 ? n + 1900 : n;
+ else
+ return ScriptRuntime.NaN;
+ } else if (c == ':') {
+ if (hour < 0)
+ hour = /*byte*/ n;
+ else if (min < 0)
+ min = /*byte*/ n;
+ else
+ return ScriptRuntime.NaN;
+ } else if (c == '/') {
+ if (mon < 0)
+ mon = /*byte*/ n-1;
+ else if (mday < 0)
+ mday = /*byte*/ n;
+ else
+ return ScriptRuntime.NaN;
+ } else if (i < limit && c != ',' && c > ' ' && c != '-') {
+ return ScriptRuntime.NaN;
+ } else if (seenplusminus && n < 60) { /* handle GMT-3:30 */
+ if (tzoffset < 0)
+ tzoffset -= n;
+ else
+ tzoffset += n;
+ } else if (hour >= 0 && min < 0) {
+ min = /*byte*/ n;
+ } else if (min >= 0 && sec < 0) {
+ sec = /*byte*/ n;
+ } else if (mday < 0) {
+ mday = /*byte*/ n;
+ } else {
+ return ScriptRuntime.NaN;
+ }
+ prevc = 0;
+ } else if (c == '/' || c == ':' || c == '+' || c == '-') {
+ prevc = c;
+ } else {
+ int st = i - 1;
+ while (i < limit) {
+ c = s.charAt(i);
+ if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')))
+ break;
+ i++;
+ }
+ int letterCount = i - st;
+ if (letterCount < 2)
+ return ScriptRuntime.NaN;
+ /*
+ * Use ported code from jsdate.c rather than the locale-specific
+ * date-parsing code from Java, to keep js and rhino consistent.
+ * Is this the right strategy?
+ */
+ String wtb = "am;pm;"
+ +"monday;tuesday;wednesday;thursday;friday;"
+ +"saturday;sunday;"
+ +"january;february;march;april;may;june;"
+ +"july;august;september;october;november;december;"
+ +"gmt;ut;utc;est;edt;cst;cdt;mst;mdt;pst;pdt;";
+ int index = 0;
+ for (int wtbOffset = 0; ;) {
+ int wtbNext = wtb.indexOf(';', wtbOffset);
+ if (wtbNext < 0)
+ return ScriptRuntime.NaN;
+ if (wtb.regionMatches(true, wtbOffset, s, st, letterCount))
+ break;
+ wtbOffset = wtbNext + 1;
+ ++index;
+ }
+ if (index < 2) {
+ /*
+ * AM/PM. Count 12:30 AM as 00:30, 12:30 PM as
+ * 12:30, instead of blindly adding 12 if PM.
+ */
+ if (hour > 12 || hour < 0) {
+ return ScriptRuntime.NaN;
+ } else if (index == 0) {
+ // AM
+ if (hour == 12)
+ hour = 0;
+ } else {
+ // PM
+ if (hour != 12)
+ hour += 12;
+ }
+ } else if ((index -= 2) < 7) {
+ // ignore week days
+ } else if ((index -= 7) < 12) {
+ // month
+ if (mon < 0) {
+ mon = index;
+ } else {
+ return ScriptRuntime.NaN;
+ }
+ } else {
+ index -= 12;
+ // timezones
+ switch (index) {
+ case 0 /* gmt */: tzoffset = 0; break;
+ case 1 /* ut */: tzoffset = 0; break;
+ case 2 /* utc */: tzoffset = 0; break;
+ case 3 /* est */: tzoffset = 5 * 60; break;
+ case 4 /* edt */: tzoffset = 4 * 60; break;
+ case 5 /* cst */: tzoffset = 6 * 60; break;
+ case 6 /* cdt */: tzoffset = 5 * 60; break;
+ case 7 /* mst */: tzoffset = 7 * 60; break;
+ case 8 /* mdt */: tzoffset = 6 * 60; break;
+ case 9 /* pst */: tzoffset = 8 * 60; break;
+ case 10 /* pdt */:tzoffset = 7 * 60; break;
+ default: Kit.codeBug();
+ }
+ }
+ }
+ }
+ if (year < 0 || mon < 0 || mday < 0)
+ return ScriptRuntime.NaN;
+ if (sec < 0)
+ sec = 0;
+ if (min < 0)
+ min = 0;
+ if (hour < 0)
+ hour = 0;
+
+ double msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
+ if (tzoffset == -1) { /* no time zone specified, have to use local */
+ return internalUTC(msec);
+ } else {
+ return msec + tzoffset * msPerMinute;
+ }
+ }
+
+ private static String date_format(double t, int methodId)
+ {
+ StringBuffer result = new StringBuffer(60);
+ double local = LocalTime(t);
+
+ /* Tue Oct 31 09:41:40 GMT-0800 (PST) 2000 */
+ /* Tue Oct 31 2000 */
+ /* 09:41:40 GMT-0800 (PST) */
+
+ if (methodId != Id_toTimeString) {
+ appendWeekDayName(result, WeekDay(local));
+ result.append(' ');
+ appendMonthName(result, MonthFromTime(local));
+ result.append(' ');
+ append0PaddedUint(result, DateFromTime(local), 2);
+ result.append(' ');
+ int year = YearFromTime(local);
+ if (year < 0) {
+ result.append('-');
+ year = -year;
+ }
+ append0PaddedUint(result, year, 4);
+ if (methodId != Id_toDateString)
+ result.append(' ');
+ }
+
+ if (methodId != Id_toDateString) {
+ append0PaddedUint(result, HourFromTime(local), 2);
+ result.append(':');
+ append0PaddedUint(result, MinFromTime(local), 2);
+ result.append(':');
+ append0PaddedUint(result, SecFromTime(local), 2);
+
+ // offset from GMT in minutes. The offset includes daylight
+ // savings, if it applies.
+ int minutes = (int) Math.floor((LocalTZA + DaylightSavingTA(t))
+ / msPerMinute);
+ // map 510 minutes to 0830 hours
+ int offset = (minutes / 60) * 100 + minutes % 60;
+ if (offset > 0) {
+ result.append(" GMT+");
+ } else {
+ result.append(" GMT-");
+ offset = -offset;
+ }
+ append0PaddedUint(result, offset, 4);
+
+ if (timeZoneFormatter == null)
+ timeZoneFormatter = new java.text.SimpleDateFormat("zzz");
+
+ // Find an equivalent year before getting the timezone
+ // comment. See DaylightSavingTA.
+ if (t < 0.0 || t > 2145916800000.0) {
+ int equiv = EquivalentYear(YearFromTime(local));
+ double day = MakeDay(equiv, MonthFromTime(t), DateFromTime(t));
+ t = MakeDate(day, TimeWithinDay(t));
+ }
+ result.append(" (");
+ java.util.Date date = new Date((long) t);
+ synchronized (timeZoneFormatter) {
+ result.append(timeZoneFormatter.format(date));
+ }
+ result.append(')');
+ }
+ return result.toString();
+ }
+
+ /* the javascript constructor */
+ private static Object jsConstructor(Object[] args)
+ {
+ NativeDate obj = new NativeDate();
+
+ // if called as a constructor with no args,
+ // return a new Date with the current time.
+ if (args.length == 0) {
+ obj.date = now();
+ return obj;
+ }
+
+ // if called with just one arg -
+ if (args.length == 1) {
+ Object arg0 = args[0];
+ if (arg0 instanceof Scriptable)
+ arg0 = ((Scriptable) arg0).getDefaultValue(null);
+ double date;
+ if (arg0 instanceof String) {
+ // it's a string; parse it.
+ date = date_parseString((String)arg0);
+ } else {
+ // if it's not a string, use it as a millisecond date
+ date = ScriptRuntime.toNumber(arg0);
+ }
+ obj.date = TimeClip(date);
+ return obj;
+ }
+
+ double time = date_msecFromArgs(args);
+
+ if (!Double.isNaN(time) && !Double.isInfinite(time))
+ time = TimeClip(internalUTC(time));
+
+ obj.date = time;
+
+ return obj;
+ }
+
+ private static String toLocale_helper(double t, int methodId)
+ {
+ java.text.DateFormat formatter;
+ switch (methodId) {
+ case Id_toLocaleString:
+ if (localeDateTimeFormatter == null) {
+ localeDateTimeFormatter
+ = DateFormat.getDateTimeInstance(DateFormat.LONG,
+ DateFormat.LONG);
+ }
+ formatter = localeDateTimeFormatter;
+ break;
+ case Id_toLocaleTimeString:
+ if (localeTimeFormatter == null) {
+ localeTimeFormatter
+ = DateFormat.getTimeInstance(DateFormat.LONG);
+ }
+ formatter = localeTimeFormatter;
+ break;
+ case Id_toLocaleDateString:
+ if (localeDateFormatter == null) {
+ localeDateFormatter
+ = DateFormat.getDateInstance(DateFormat.LONG);
+ }
+ formatter = localeDateFormatter;
+ break;
+ default: formatter = null; // unreachable
+ }
+
+ synchronized (formatter) {
+ return formatter.format(new Date((long) t));
+ }
+ }
+
+ private static String js_toUTCString(double date)
+ {
+ StringBuffer result = new StringBuffer(60);
+
+ appendWeekDayName(result, WeekDay(date));
+ result.append(", ");
+ append0PaddedUint(result, DateFromTime(date), 2);
+ result.append(' ');
+ appendMonthName(result, MonthFromTime(date));
+ result.append(' ');
+ int year = YearFromTime(date);
+ if (year < 0) {
+ result.append('-'); year = -year;
+ }
+ append0PaddedUint(result, year, 4);
+ result.append(' ');
+ append0PaddedUint(result, HourFromTime(date), 2);
+ result.append(':');
+ append0PaddedUint(result, MinFromTime(date), 2);
+ result.append(':');
+ append0PaddedUint(result, SecFromTime(date), 2);
+ result.append(" GMT");
+ return result.toString();
+ }
+
+ private static void append0PaddedUint(StringBuffer sb, int i, int minWidth)
+ {
+ if (i < 0) Kit.codeBug();
+ int scale = 1;
+ --minWidth;
+ if (i >= 10) {
+ if (i < 1000 * 1000 * 1000) {
+ for (;;) {
+ int newScale = scale * 10;
+ if (i < newScale) { break; }
+ --minWidth;
+ scale = newScale;
+ }
+ } else {
+ // Separated case not to check against 10 * 10^9 overflow
+ minWidth -= 9;
+ scale = 1000 * 1000 * 1000;
+ }
+ }
+ while (minWidth > 0) {
+ sb.append('0');
+ --minWidth;
+ }
+ while (scale != 1) {
+ sb.append((char)('0' + (i / scale)));
+ i %= scale;
+ scale /= 10;
+ }
+ sb.append((char)('0' + i));
+ }
+
+ private static void appendMonthName(StringBuffer sb, int index)
+ {
+ // Take advantage of the fact that all month abbreviations
+ // have the same length to minimize amount of strings runtime has
+ // to keep in memory
+ String months = "Jan"+"Feb"+"Mar"+"Apr"+"May"+"Jun"
+ +"Jul"+"Aug"+"Sep"+"Oct"+"Nov"+"Dec";
+ index *= 3;
+ for (int i = 0; i != 3; ++i) {
+ sb.append(months.charAt(index + i));
+ }
+ }
+
+ private static void appendWeekDayName(StringBuffer sb, int index)
+ {
+ String days = "Sun"+"Mon"+"Tue"+"Wed"+"Thu"+"Fri"+"Sat";
+ index *= 3;
+ for (int i = 0; i != 3; ++i) {
+ sb.append(days.charAt(index + i));
+ }
+ }
+
+ private static double makeTime(double date, Object[] args, int methodId)
+ {
+ int maxargs;
+ boolean local = true;
+ switch (methodId) {
+ case Id_setUTCMilliseconds:
+ local = false;
+ // fallthrough
+ case Id_setMilliseconds:
+ maxargs = 1;
+ break;
+
+ case Id_setUTCSeconds:
+ local = false;
+ // fallthrough
+ case Id_setSeconds:
+ maxargs = 2;
+ break;
+
+ case Id_setUTCMinutes:
+ local = false;
+ // fallthrough
+ case Id_setMinutes:
+ maxargs = 3;
+ break;
+
+ case Id_setUTCHours:
+ local = false;
+ // fallthrough
+ case Id_setHours:
+ maxargs = 4;
+ break;
+
+ default:
+ Kit.codeBug();
+ maxargs = 0;
+ }
+
+ int i;
+ double conv[] = new double[4];
+ double hour, min, sec, msec;
+ double lorutime; /* Local or UTC version of date */
+
+ double time;
+ double result;
+
+ /* just return NaN if the date is already NaN */
+ if (date != date)
+ return date;
+
+ /* Satisfy the ECMA rule that if a function is called with
+ * fewer arguments than the specified formal arguments, the
+ * remaining arguments are set to undefined. Seems like all
+ * the Date.setWhatever functions in ECMA are only varargs
+ * beyond the first argument; this should be set to undefined
+ * if it's not given. This means that "d = new Date();
+ * d.setMilliseconds()" returns NaN. Blech.
+ */
+ if (args.length == 0)
+ args = ScriptRuntime.padArguments(args, 1);
+
+ for (i = 0; i < args.length && i < maxargs; i++) {
+ conv[i] = ScriptRuntime.toNumber(args[i]);
+
+ // limit checks that happen in MakeTime in ECMA.
+ if (conv[i] != conv[i] || Double.isInfinite(conv[i])) {
+ return ScriptRuntime.NaN;
+ }
+ conv[i] = ScriptRuntime.toInteger(conv[i]);
+ }
+
+ if (local)
+ lorutime = LocalTime(date);
+ else
+ lorutime = date;
+
+ i = 0;
+ int stop = args.length;
+
+ if (maxargs >= 4 && i < stop)
+ hour = conv[i++];
+ else
+ hour = HourFromTime(lorutime);
+
+ if (maxargs >= 3 && i < stop)
+ min = conv[i++];
+ else
+ min = MinFromTime(lorutime);
+
+ if (maxargs >= 2 && i < stop)
+ sec = conv[i++];
+ else
+ sec = SecFromTime(lorutime);
+
+ if (maxargs >= 1 && i < stop)
+ msec = conv[i++];
+ else
+ msec = msFromTime(lorutime);
+
+ time = MakeTime(hour, min, sec, msec);
+ result = MakeDate(Day(lorutime), time);
+
+ if (local)
+ result = internalUTC(result);
+ date = TimeClip(result);
+
+ return date;
+ }
+
+ private static double makeDate(double date, Object[] args, int methodId)
+ {
+ int maxargs;
+ boolean local = true;
+ switch (methodId) {
+ case Id_setUTCDate:
+ local = false;
+ // fallthrough
+ case Id_setDate:
+ maxargs = 1;
+ break;
+
+ case Id_setUTCMonth:
+ local = false;
+ // fallthrough
+ case Id_setMonth:
+ maxargs = 2;
+ break;
+
+ case Id_setUTCFullYear:
+ local = false;
+ // fallthrough
+ case Id_setFullYear:
+ maxargs = 3;
+ break;
+
+ default:
+ Kit.codeBug();
+ maxargs = 0;
+ }
+
+ int i;
+ double conv[] = new double[3];
+ double year, month, day;
+ double lorutime; /* local or UTC version of date */
+ double result;
+
+ /* See arg padding comment in makeTime.*/
+ if (args.length == 0)
+ args = ScriptRuntime.padArguments(args, 1);
+
+ for (i = 0; i < args.length && i < maxargs; i++) {
+ conv[i] = ScriptRuntime.toNumber(args[i]);
+
+ // limit checks that happen in MakeDate in ECMA.
+ if (conv[i] != conv[i] || Double.isInfinite(conv[i])) {
+ return ScriptRuntime.NaN;
+ }
+ conv[i] = ScriptRuntime.toInteger(conv[i]);
+ }
+
+ /* return NaN if date is NaN and we're not setting the year,
+ * If we are, use 0 as the time. */
+ if (date != date) {
+ if (args.length < 3) {
+ return ScriptRuntime.NaN;
+ } else {
+ lorutime = 0;
+ }
+ } else {
+ if (local)
+ lorutime = LocalTime(date);
+ else
+ lorutime = date;
+ }
+
+ i = 0;
+ int stop = args.length;
+
+ if (maxargs >= 3 && i < stop)
+ year = conv[i++];
+ else
+ year = YearFromTime(lorutime);
+
+ if (maxargs >= 2 && i < stop)
+ month = conv[i++];
+ else
+ month = MonthFromTime(lorutime);
+
+ if (maxargs >= 1 && i < stop)
+ day = conv[i++];
+ else
+ day = DateFromTime(lorutime);
+
+ day = MakeDay(year, month, day); /* day within year */
+ result = MakeDate(day, TimeWithinDay(lorutime));
+
+ if (local)
+ result = internalUTC(result);
+
+ date = TimeClip(result);
+
+ return date;
+ }
+
+// #string_id_map#
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #generated# Last update: 2007-05-09 08:15:38 EDT
+ L0: { id = 0; String X = null; int c;
+ L: switch (s.length()) {
+ case 6: X="getDay";id=Id_getDay; break L;
+ case 7: switch (s.charAt(3)) {
+ case 'D': c=s.charAt(0);
+ if (c=='g') { X="getDate";id=Id_getDate; }
+ else if (c=='s') { X="setDate";id=Id_setDate; }
+ break L;
+ case 'T': c=s.charAt(0);
+ if (c=='g') { X="getTime";id=Id_getTime; }
+ else if (c=='s') { X="setTime";id=Id_setTime; }
+ break L;
+ case 'Y': c=s.charAt(0);
+ if (c=='g') { X="getYear";id=Id_getYear; }
+ else if (c=='s') { X="setYear";id=Id_setYear; }
+ break L;
+ case 'u': X="valueOf";id=Id_valueOf; break L;
+ } break L;
+ case 8: switch (s.charAt(3)) {
+ case 'H': c=s.charAt(0);
+ if (c=='g') { X="getHours";id=Id_getHours; }
+ else if (c=='s') { X="setHours";id=Id_setHours; }
+ break L;
+ case 'M': c=s.charAt(0);
+ if (c=='g') { X="getMonth";id=Id_getMonth; }
+ else if (c=='s') { X="setMonth";id=Id_setMonth; }
+ break L;
+ case 'o': X="toSource";id=Id_toSource; break L;
+ case 't': X="toString";id=Id_toString; break L;
+ } break L;
+ case 9: X="getUTCDay";id=Id_getUTCDay; break L;
+ case 10: c=s.charAt(3);
+ if (c=='M') {
+ c=s.charAt(0);
+ if (c=='g') { X="getMinutes";id=Id_getMinutes; }
+ else if (c=='s') { X="setMinutes";id=Id_setMinutes; }
+ }
+ else if (c=='S') {
+ c=s.charAt(0);
+ if (c=='g') { X="getSeconds";id=Id_getSeconds; }
+ else if (c=='s') { X="setSeconds";id=Id_setSeconds; }
+ }
+ else if (c=='U') {
+ c=s.charAt(0);
+ if (c=='g') { X="getUTCDate";id=Id_getUTCDate; }
+ else if (c=='s') { X="setUTCDate";id=Id_setUTCDate; }
+ }
+ break L;
+ case 11: switch (s.charAt(3)) {
+ case 'F': c=s.charAt(0);
+ if (c=='g') { X="getFullYear";id=Id_getFullYear; }
+ else if (c=='s') { X="setFullYear";id=Id_setFullYear; }
+ break L;
+ case 'M': X="toGMTString";id=Id_toGMTString; break L;
+ case 'T': X="toUTCString";id=Id_toUTCString; break L;
+ case 'U': c=s.charAt(0);
+ if (c=='g') {
+ c=s.charAt(9);
+ if (c=='r') { X="getUTCHours";id=Id_getUTCHours; }
+ else if (c=='t') { X="getUTCMonth";id=Id_getUTCMonth; }
+ }
+ else if (c=='s') {
+ c=s.charAt(9);
+ if (c=='r') { X="setUTCHours";id=Id_setUTCHours; }
+ else if (c=='t') { X="setUTCMonth";id=Id_setUTCMonth; }
+ }
+ break L;
+ case 's': X="constructor";id=Id_constructor; break L;
+ } break L;
+ case 12: c=s.charAt(2);
+ if (c=='D') { X="toDateString";id=Id_toDateString; }
+ else if (c=='T') { X="toTimeString";id=Id_toTimeString; }
+ break L;
+ case 13: c=s.charAt(0);
+ if (c=='g') {
+ c=s.charAt(6);
+ if (c=='M') { X="getUTCMinutes";id=Id_getUTCMinutes; }
+ else if (c=='S') { X="getUTCSeconds";id=Id_getUTCSeconds; }
+ }
+ else if (c=='s') {
+ c=s.charAt(6);
+ if (c=='M') { X="setUTCMinutes";id=Id_setUTCMinutes; }
+ else if (c=='S') { X="setUTCSeconds";id=Id_setUTCSeconds; }
+ }
+ break L;
+ case 14: c=s.charAt(0);
+ if (c=='g') { X="getUTCFullYear";id=Id_getUTCFullYear; }
+ else if (c=='s') { X="setUTCFullYear";id=Id_setUTCFullYear; }
+ else if (c=='t') { X="toLocaleString";id=Id_toLocaleString; }
+ break L;
+ case 15: c=s.charAt(0);
+ if (c=='g') { X="getMilliseconds";id=Id_getMilliseconds; }
+ else if (c=='s') { X="setMilliseconds";id=Id_setMilliseconds; }
+ break L;
+ case 17: X="getTimezoneOffset";id=Id_getTimezoneOffset; break L;
+ case 18: c=s.charAt(0);
+ if (c=='g') { X="getUTCMilliseconds";id=Id_getUTCMilliseconds; }
+ else if (c=='s') { X="setUTCMilliseconds";id=Id_setUTCMilliseconds; }
+ else if (c=='t') {
+ c=s.charAt(8);
+ if (c=='D') { X="toLocaleDateString";id=Id_toLocaleDateString; }
+ else if (c=='T') { X="toLocaleTimeString";id=Id_toLocaleTimeString; }
+ }
+ break L;
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ ConstructorId_now = -3,
+ ConstructorId_parse = -2,
+ ConstructorId_UTC = -1,
+
+ Id_constructor = 1,
+ Id_toString = 2,
+ Id_toTimeString = 3,
+ Id_toDateString = 4,
+ Id_toLocaleString = 5,
+ Id_toLocaleTimeString = 6,
+ Id_toLocaleDateString = 7,
+ Id_toUTCString = 8,
+ Id_toSource = 9,
+ Id_valueOf = 10,
+ Id_getTime = 11,
+ Id_getYear = 12,
+ Id_getFullYear = 13,
+ Id_getUTCFullYear = 14,
+ Id_getMonth = 15,
+ Id_getUTCMonth = 16,
+ Id_getDate = 17,
+ Id_getUTCDate = 18,
+ Id_getDay = 19,
+ Id_getUTCDay = 20,
+ Id_getHours = 21,
+ Id_getUTCHours = 22,
+ Id_getMinutes = 23,
+ Id_getUTCMinutes = 24,
+ Id_getSeconds = 25,
+ Id_getUTCSeconds = 26,
+ Id_getMilliseconds = 27,
+ Id_getUTCMilliseconds = 28,
+ Id_getTimezoneOffset = 29,
+ Id_setTime = 30,
+ Id_setMilliseconds = 31,
+ Id_setUTCMilliseconds = 32,
+ Id_setSeconds = 33,
+ Id_setUTCSeconds = 34,
+ Id_setMinutes = 35,
+ Id_setUTCMinutes = 36,
+ Id_setHours = 37,
+ Id_setUTCHours = 38,
+ Id_setDate = 39,
+ Id_setUTCDate = 40,
+ Id_setMonth = 41,
+ Id_setUTCMonth = 42,
+ Id_setFullYear = 43,
+ Id_setUTCFullYear = 44,
+ Id_setYear = 45,
+
+ MAX_PROTOTYPE_ID = 45;
+
+ private static final int
+ Id_toGMTString = Id_toUTCString; // Alias, see Ecma B.2.6
+// #/string_id_map#
+
+ /* cached values */
+ private static java.util.TimeZone thisTimeZone;
+ private static double LocalTZA;
+ private static java.text.DateFormat timeZoneFormatter;
+ private static java.text.DateFormat localeDateTimeFormatter;
+ private static java.text.DateFormat localeDateFormatter;
+ private static java.text.DateFormat localeTimeFormatter;
+
+ private double date;
+}
+
+
diff --git a/src/org/mozilla/javascript/NativeError.java b/src/org/mozilla/javascript/NativeError.java
new file mode 100644
index 0000000..4d8f364
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeError.java
@@ -0,0 +1,232 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+package org.mozilla.javascript;
+
+/**
+ *
+ * The class of error objects
+ *
+ * ECMA 15.11
+ */
+final class NativeError extends IdScriptableObject
+{
+ static final long serialVersionUID = -5338413581437645187L;
+
+ private static final Object ERROR_TAG = "Error";
+
+ static void init(Scriptable scope, boolean sealed)
+ {
+ NativeError obj = new NativeError();
+ ScriptableObject.putProperty(obj, "name", "Error");
+ ScriptableObject.putProperty(obj, "message", "");
+ ScriptableObject.putProperty(obj, "fileName", "");
+ ScriptableObject.putProperty(obj, "lineNumber", new Integer(0));
+ obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+ }
+
+ static NativeError make(Context cx, Scriptable scope,
+ IdFunctionObject ctorObj, Object[] args)
+ {
+ Scriptable proto = (Scriptable)(ctorObj.get("prototype", ctorObj));
+
+ NativeError obj = new NativeError();
+ obj.setPrototype(proto);
+ obj.setParentScope(scope);
+
+ int arglen = args.length;
+ if (arglen >= 1) {
+ ScriptableObject.putProperty(obj, "message",
+ ScriptRuntime.toString(args[0]));
+ if (arglen >= 2) {
+ ScriptableObject.putProperty(obj, "fileName", args[1]);
+ if (arglen >= 3) {
+ int line = ScriptRuntime.toInt32(args[2]);
+ ScriptableObject.putProperty(obj, "lineNumber",
+ new Integer(line));
+ }
+ }
+ }
+ if(arglen < 3 && cx.hasFeature(Context.FEATURE_LOCATION_INFORMATION_IN_ERROR)) {
+ // Fill in fileName and lineNumber automatically when not specified
+ // explicitly, see Bugzilla issue #342807
+ int[] linep = new int[1];
+ String fileName = Context.getSourcePositionFromStack(linep);
+ ScriptableObject.putProperty(obj, "lineNumber",
+ new Integer(linep[0]));
+ if(arglen < 2) {
+ ScriptableObject.putProperty(obj, "fileName", fileName);
+ }
+ }
+ return obj;
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return "Error";
+ }
+
+ @Override
+ public String toString()
+ {
+ return js_toString(this);
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_constructor: arity=1; s="constructor"; break;
+ case Id_toString: arity=0; s="toString"; break;
+ case Id_toSource: arity=0; s="toSource"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(ERROR_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(ERROR_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+ switch (id) {
+ case Id_constructor:
+ return make(cx, scope, f, args);
+
+ case Id_toString:
+ return js_toString(thisObj);
+
+ case Id_toSource:
+ return js_toSource(cx, scope, thisObj);
+ }
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+
+ private static String js_toString(Scriptable thisObj)
+ {
+ return getString(thisObj, "name")+": "+getString(thisObj, "message");
+ }
+
+ private static String js_toSource(Context cx, Scriptable scope,
+ Scriptable thisObj)
+ {
+ // Emulation of SpiderMonkey behavior
+ Object name = ScriptableObject.getProperty(thisObj, "name");
+ Object message = ScriptableObject.getProperty(thisObj, "message");
+ Object fileName = ScriptableObject.getProperty(thisObj, "fileName");
+ Object lineNumber = ScriptableObject.getProperty(thisObj, "lineNumber");
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("(new ");
+ if (name == NOT_FOUND) {
+ name = Undefined.instance;
+ }
+ sb.append(ScriptRuntime.toString(name));
+ sb.append("(");
+ if (message != NOT_FOUND
+ || fileName != NOT_FOUND
+ || lineNumber != NOT_FOUND)
+ {
+ if (message == NOT_FOUND) {
+ message = "";
+ }
+ sb.append(ScriptRuntime.uneval(cx, scope, message));
+ if (fileName != NOT_FOUND || lineNumber != NOT_FOUND) {
+ sb.append(", ");
+ if (fileName == NOT_FOUND) {
+ fileName = "";
+ }
+ sb.append(ScriptRuntime.uneval(cx, scope, fileName));
+ if (lineNumber != NOT_FOUND) {
+ int line = ScriptRuntime.toInt32(lineNumber);
+ if (line != 0) {
+ sb.append(", ");
+ sb.append(ScriptRuntime.toString(line));
+ }
+ }
+ }
+ }
+ sb.append("))");
+ return sb.toString();
+ }
+
+ private static String getString(Scriptable obj, String id)
+ {
+ Object value = ScriptableObject.getProperty(obj, id);
+ if (value == NOT_FOUND) return "";
+ return ScriptRuntime.toString(value);
+ }
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #string_id_map#
+// #generated# Last update: 2007-05-09 08:15:45 EDT
+ L0: { id = 0; String X = null; int c;
+ int s_length = s.length();
+ if (s_length==8) {
+ c=s.charAt(3);
+ if (c=='o') { X="toSource";id=Id_toSource; }
+ else if (c=='t') { X="toString";id=Id_toString; }
+ }
+ else if (s_length==11) { X="constructor";id=Id_constructor; }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_constructor = 1,
+ Id_toString = 2,
+ Id_toSource = 3,
+
+ MAX_PROTOTYPE_ID = 3;
+
+// #/string_id_map#
+}
diff --git a/src/org/mozilla/javascript/NativeFunction.java b/src/org/mozilla/javascript/NativeFunction.java
new file mode 100644
index 0000000..ffa6a86
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeFunction.java
@@ -0,0 +1,172 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Bob Jervis
+ * Roger Lawrence
+ * Mike McCabe
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import org.mozilla.javascript.debug.DebuggableScript;
+
+/**
+ * This class implements the Function native object.
+ * See ECMA 15.3.
+ * @author Norris Boyd
+ */
+public abstract class NativeFunction extends BaseFunction
+{
+
+ public final void initScriptFunction(Context cx, Scriptable scope)
+ {
+ ScriptRuntime.setFunctionProtoAndParent(this, scope);
+ }
+
+ /**
+ * @param indent How much to indent the decompiled result
+ *
+ * @param flags Flags specifying format of decompilation output
+ */
+ @Override
+ final String decompile(int indent, int flags)
+ {
+ String encodedSource = getEncodedSource();
+ if (encodedSource == null) {
+ return super.decompile(indent, flags);
+ } else {
+ UintMap properties = new UintMap(1);
+ properties.put(Decompiler.INITIAL_INDENT_PROP, indent);
+ return Decompiler.decompile(encodedSource, flags, properties);
+ }
+ }
+
+ @Override
+ public int getLength()
+ {
+ int paramCount = getParamCount();
+ if (getLanguageVersion() != Context.VERSION_1_2) {
+ return paramCount;
+ }
+ Context cx = Context.getContext();
+ NativeCall activation = ScriptRuntime.findFunctionActivation(cx, this);
+ if (activation == null) {
+ return paramCount;
+ }
+ return activation.originalArgs.length;
+ }
+
+ @Override
+ public int getArity()
+ {
+ return getParamCount();
+ }
+
+ /**
+ * @deprecated Use {@link BaseFunction#getFunctionName()} instead.
+ * For backwards compatibility keep an old method name used by
+ * Batik and possibly others.
+ */
+ public String jsGet_name()
+ {
+ return getFunctionName();
+ }
+
+ /**
+ * Get encoded source string.
+ */
+ public String getEncodedSource()
+ {
+ return null;
+ }
+
+ public DebuggableScript getDebuggableView()
+ {
+ return null;
+ }
+
+ /**
+ * Resume execution of a suspended generator.
+ * @param cx The current context
+ * @param scope Scope for the parent generator function
+ * @param operation The resumption operation (next, send, etc.. )
+ * @param state The generator state (has locals, stack, etc.)
+ * @param value The return value of yield (if required).
+ * @return The next yielded value (if any)
+ */
+ public Object resumeGenerator(Context cx, Scriptable scope,
+ int operation, Object state, Object value)
+ {
+ throw new EvaluatorException("resumeGenerator() not implemented");
+ }
+
+
+ protected abstract int getLanguageVersion();
+
+ /**
+ * Get number of declared parameters. It should be 0 for scripts.
+ */
+ protected abstract int getParamCount();
+
+ /**
+ * Get number of declared parameters and variables defined through var
+ * statements.
+ */
+ protected abstract int getParamAndVarCount();
+
+ /**
+ * Get parameter or variable name.
+ * If <tt>index < {@link #getParamCount()}</tt>, then return the name of the
+ * corresponding parameter. Otherwise return the name of variable.
+ */
+ protected abstract String getParamOrVarName(int index);
+
+ /**
+ * Get parameter or variable const-ness.
+ * If <tt>index < {@link #getParamCount()}</tt>, then return the const-ness
+ * of the corresponding parameter. Otherwise return whether the variable is
+ * const.
+ */
+ protected boolean getParamOrVarConst(int index)
+ {
+ // By default return false to preserve compatibility with existing
+ // classes subclassing this class, which are mostly generated by jsc
+ // from earlier Rhino versions. See Bugzilla #396117.
+ return false;
+ }
+}
+
diff --git a/src/org/mozilla/javascript/NativeGenerator.java b/src/org/mozilla/javascript/NativeGenerator.java
new file mode 100644
index 0000000..46c993b
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeGenerator.java
@@ -0,0 +1,288 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * This class implements generator objects. See
+ * http://developer.mozilla.org/en/docs/New_in_JavaScript_1.7#Generators
+ *
+ * @author Norris Boyd
+ */
+public final class NativeGenerator extends IdScriptableObject {
+ private static final long serialVersionUID = 1645892441041347273L;
+
+ private static final Object GENERATOR_TAG = "Generator";
+
+ static NativeGenerator init(ScriptableObject scope, boolean sealed) {
+ // Generator
+ // Can't use "NativeGenerator().exportAsJSClass" since we don't want
+ // to define "Generator" as a constructor in the top-level scope.
+
+ NativeGenerator prototype = new NativeGenerator();
+ if (scope != null) {
+ prototype.setParentScope(scope);
+ prototype.setPrototype(getObjectPrototype(scope));
+ }
+ prototype.activatePrototypeMap(MAX_PROTOTYPE_ID);
+ if (sealed) {
+ prototype.sealObject();
+ }
+
+ // Need to access Generator prototype when constructing
+ // Generator instances, but don't have a generator constructor
+ // to use to find the prototype. Use the "associateValue"
+ // approach instead.
+ if (scope != null) {
+ scope.associateValue(GENERATOR_TAG, prototype);
+ }
+
+ return prototype;
+ }
+
+ /**
+ * Only for constructing the prototype object.
+ */
+ private NativeGenerator() { }
+
+ public NativeGenerator(Scriptable scope, NativeFunction function,
+ Object savedState)
+ {
+ this.function = function;
+ this.savedState = savedState;
+ // Set parent and prototype properties. Since we don't have a
+ // "Generator" constructor in the top scope, we stash the
+ // prototype in the top scope's associated value.
+ Scriptable top = ScriptableObject.getTopLevelScope(scope);
+ this.setParentScope(top);
+ NativeGenerator prototype = (NativeGenerator)
+ ScriptableObject.getTopScopeValue(top, GENERATOR_TAG);
+ this.setPrototype(prototype);
+ }
+
+ public static final int GENERATOR_SEND = 0,
+ GENERATOR_THROW = 1,
+ GENERATOR_CLOSE = 2;
+
+ @Override
+ public String getClassName() {
+ return "Generator";
+ }
+
+ /**
+ * Close the generator if it is still open.
+ */
+ @Override
+ public void finalize() throws Throwable {
+ if (savedState != null) {
+ // This is a little tricky since we are most likely running in
+ // a different thread. We need to get a Context to run this, and
+ // we must call "doTopCall" since this will likely be the outermost
+ // JavaScript frame on this thread.
+ Context cx = Context.getCurrentContext();
+ ContextFactory factory = cx != null ? cx.getFactory()
+ : ContextFactory.getGlobal();
+ factory.call(new CloseGeneratorAction(this));
+ }
+ }
+
+ private static class CloseGeneratorAction implements ContextAction {
+ private NativeGenerator generator;
+
+ CloseGeneratorAction(NativeGenerator generator) {
+ this.generator = generator;
+ }
+
+ public Object run(Context cx) {
+ Scriptable scope = ScriptableObject.getTopLevelScope(generator);
+ Callable closeGenerator = new Callable() {
+ public Object call(Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args) {
+ return ((NativeGenerator)thisObj).resume(cx, scope,
+ GENERATOR_CLOSE, new GeneratorClosedException());
+ }
+ };
+ return ScriptRuntime.doTopCall(closeGenerator, cx, scope,
+ generator, null);
+ }
+ }
+
+ @Override
+ protected void initPrototypeId(int id) {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_close: arity=1; s="close"; break;
+ case Id_next: arity=1; s="next"; break;
+ case Id_send: arity=0; s="send"; break;
+ case Id_throw: arity=0; s="throw"; break;
+ case Id___iterator__: arity=1; s="__iterator__"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(GENERATOR_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(GENERATOR_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+
+ if (!(thisObj instanceof NativeGenerator))
+ throw incompatibleCallError(f);
+
+ NativeGenerator generator = (NativeGenerator) thisObj;
+
+ switch (id) {
+
+ case Id_close:
+ // need to run any pending finally clauses
+ return generator.resume(cx, scope, GENERATOR_CLOSE,
+ new GeneratorClosedException());
+
+ case Id_next:
+ // arguments to next() are ignored
+ generator.firstTime = false;
+ return generator.resume(cx, scope, GENERATOR_SEND,
+ Undefined.instance);
+
+ case Id_send: {
+ Object arg = args.length > 0 ? args[0] : Undefined.instance;
+ if (generator.firstTime && !arg.equals(Undefined.instance)) {
+ throw ScriptRuntime.typeError0("msg.send.newborn");
+ }
+ return generator.resume(cx, scope, GENERATOR_SEND, arg);
+ }
+
+ case Id_throw:
+ return generator.resume(cx, scope, GENERATOR_THROW,
+ args.length > 0 ? args[0] : Undefined.instance);
+
+ case Id___iterator__:
+ return thisObj;
+
+ default:
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+ }
+
+ private Object resume(Context cx, Scriptable scope, int operation,
+ Object value)
+ {
+ if (savedState == null) {
+ if (operation == GENERATOR_CLOSE)
+ return Undefined.instance;
+ Object thrown;
+ if (operation == GENERATOR_THROW) {
+ thrown = value;
+ } else {
+ thrown = NativeIterator.getStopIterationObject(scope);
+ }
+ throw new JavaScriptException(thrown, lineSource, lineNumber);
+ }
+ try {
+ synchronized (this) {
+ // generator execution is necessarily single-threaded and
+ // non-reentrant.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=349263
+ if (locked)
+ throw ScriptRuntime.typeError0("msg.already.exec.gen");
+ locked = true;
+ }
+ return function.resumeGenerator(cx, scope, operation, savedState,
+ value);
+ } catch (GeneratorClosedException e) {
+ // On closing a generator in the compile path, the generator
+ // throws a special exception. This ensures execution of all pending
+ // finalizers and will not get caught by user code.
+ return Undefined.instance;
+ } catch (RhinoException e) {
+ lineNumber = e.lineNumber();
+ lineSource = e.lineSource();
+ savedState = null;
+ throw e;
+ } finally {
+ synchronized (this) {
+ locked = false;
+ }
+ if (operation == GENERATOR_CLOSE)
+ savedState = null;
+ }
+ }
+
+// #string_id_map#
+
+ @Override
+ protected int findPrototypeId(String s) {
+ int id;
+// #generated# Last update: 2007-06-14 13:13:03 EDT
+ L0: { id = 0; String X = null; int c;
+ int s_length = s.length();
+ if (s_length==4) {
+ c=s.charAt(0);
+ if (c=='n') { X="next";id=Id_next; }
+ else if (c=='s') { X="send";id=Id_send; }
+ }
+ else if (s_length==5) {
+ c=s.charAt(0);
+ if (c=='c') { X="close";id=Id_close; }
+ else if (c=='t') { X="throw";id=Id_throw; }
+ }
+ else if (s_length==12) { X="__iterator__";id=Id___iterator__; }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_close = 1,
+ Id_next = 2,
+ Id_send = 3,
+ Id_throw = 4,
+ Id___iterator__ = 5,
+ MAX_PROTOTYPE_ID = 5;
+
+// #/string_id_map#
+ private NativeFunction function;
+ private Object savedState;
+ private String lineSource;
+ private int lineNumber;
+ private boolean firstTime = true;
+ private boolean locked;
+
+ public static class GeneratorClosedException extends RuntimeException {
+ private static final long serialVersionUID = 2561315658662379681L;
+ }
+}
diff --git a/src/org/mozilla/javascript/NativeGlobal.java b/src/org/mozilla/javascript/NativeGlobal.java
new file mode 100644
index 0000000..a65fc08
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeGlobal.java
@@ -0,0 +1,795 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Mike McCabe
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.Serializable;
+
+import org.mozilla.javascript.xml.XMLLib;
+
+/**
+ * This class implements the global native object (function and value
+ * properties only).
+ *
+ * See ECMA 15.1.[12].
+ *
+ * @author Mike Shaver
+ */
+
+public class NativeGlobal implements Serializable, IdFunctionCall
+{
+ static final long serialVersionUID = 6080442165748707530L;
+
+ public static void init(Context cx, Scriptable scope, boolean sealed) {
+ NativeGlobal obj = new NativeGlobal();
+
+ for (int id = 1; id <= LAST_SCOPE_FUNCTION_ID; ++id) {
+ String name;
+ int arity = 1;
+ switch (id) {
+ case Id_decodeURI:
+ name = "decodeURI";
+ break;
+ case Id_decodeURIComponent:
+ name = "decodeURIComponent";
+ break;
+ case Id_encodeURI:
+ name = "encodeURI";
+ break;
+ case Id_encodeURIComponent:
+ name = "encodeURIComponent";
+ break;
+ case Id_escape:
+ name = "escape";
+ break;
+ case Id_eval:
+ name = "eval";
+ break;
+ case Id_isFinite:
+ name = "isFinite";
+ break;
+ case Id_isNaN:
+ name = "isNaN";
+ break;
+ case Id_isXMLName:
+ name = "isXMLName";
+ break;
+ case Id_parseFloat:
+ name = "parseFloat";
+ break;
+ case Id_parseInt:
+ name = "parseInt";
+ arity = 2;
+ break;
+ case Id_unescape:
+ name = "unescape";
+ break;
+ case Id_uneval:
+ name = "uneval";
+ break;
+ default:
+ throw Kit.codeBug();
+ }
+ IdFunctionObject f = new IdFunctionObject(obj, FTAG, id, name,
+ arity, scope);
+ if (sealed) {
+ f.sealObject();
+ }
+ f.exportAsScopeProperty();
+ }
+
+ ScriptableObject.defineProperty(
+ scope, "NaN", ScriptRuntime.NaNobj,
+ ScriptableObject.DONTENUM);
+ ScriptableObject.defineProperty(
+ scope, "Infinity",
+ ScriptRuntime.wrapNumber(Double.POSITIVE_INFINITY),
+ ScriptableObject.DONTENUM);
+ ScriptableObject.defineProperty(
+ scope, "undefined", Undefined.instance,
+ ScriptableObject.DONTENUM);
+
+ String[] errorMethods = {
+ "ConversionError",
+ "EvalError",
+ "RangeError",
+ "ReferenceError",
+ "SyntaxError",
+ "TypeError",
+ "URIError",
+ "InternalError",
+ "JavaException"
+ };
+
+ /*
+ Each error constructor gets its own Error object as a prototype,
+ with the 'name' property set to the name of the error.
+ */
+ for (int i = 0; i < errorMethods.length; i++) {
+ String name = errorMethods[i];
+ Scriptable errorProto = ScriptRuntime.
+ newObject(cx, scope, "Error",
+ ScriptRuntime.emptyArgs);
+ errorProto.put("name", errorProto, name);
+ if (sealed) {
+ if (errorProto instanceof ScriptableObject) {
+ ((ScriptableObject)errorProto).sealObject();
+ }
+ }
+ IdFunctionObject ctor = new IdFunctionObject(obj, FTAG,
+ Id_new_CommonError,
+ name, 1, scope);
+ ctor.markAsConstructor(errorProto);
+ if (sealed) {
+ ctor.sealObject();
+ }
+ ctor.exportAsScopeProperty();
+ }
+ }
+
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (f.hasTag(FTAG)) {
+ int methodId = f.methodId();
+ switch (methodId) {
+ case Id_decodeURI:
+ case Id_decodeURIComponent: {
+ String str = ScriptRuntime.toString(args, 0);
+ return decode(str, methodId == Id_decodeURI);
+ }
+
+ case Id_encodeURI:
+ case Id_encodeURIComponent: {
+ String str = ScriptRuntime.toString(args, 0);
+ return encode(str, methodId == Id_encodeURI);
+ }
+
+ case Id_escape:
+ return js_escape(args);
+
+ case Id_eval:
+ return js_eval(cx, scope, thisObj, args);
+
+ case Id_isFinite: {
+ boolean result;
+ if (args.length < 1) {
+ result = false;
+ } else {
+ double d = ScriptRuntime.toNumber(args[0]);
+ result = (d == d
+ && d != Double.POSITIVE_INFINITY
+ && d != Double.NEGATIVE_INFINITY);
+ }
+ return ScriptRuntime.wrapBoolean(result);
+ }
+
+ case Id_isNaN: {
+ // The global method isNaN, as per ECMA-262 15.1.2.6.
+ boolean result;
+ if (args.length < 1) {
+ result = true;
+ } else {
+ double d = ScriptRuntime.toNumber(args[0]);
+ result = (d != d);
+ }
+ return ScriptRuntime.wrapBoolean(result);
+ }
+
+ case Id_isXMLName: {
+ Object name = (args.length == 0)
+ ? Undefined.instance : args[0];
+ XMLLib xmlLib = XMLLib.extractFromScope(scope);
+ return ScriptRuntime.wrapBoolean(
+ xmlLib.isXMLName(cx, name));
+ }
+
+ case Id_parseFloat:
+ return js_parseFloat(args);
+
+ case Id_parseInt:
+ return js_parseInt(args);
+
+ case Id_unescape:
+ return js_unescape(args);
+
+ case Id_uneval: {
+ Object value = (args.length != 0)
+ ? args[0] : Undefined.instance;
+ return ScriptRuntime.uneval(cx, scope, value);
+ }
+
+ case Id_new_CommonError:
+ // The implementation of all the ECMA error constructors
+ // (SyntaxError, TypeError, etc.)
+ return NativeError.make(cx, scope, f, args);
+ }
+ }
+ throw f.unknown();
+ }
+
+ /**
+ * The global method parseInt, as per ECMA-262 15.1.2.2.
+ */
+ private Object js_parseInt(Object[] args) {
+ String s = ScriptRuntime.toString(args, 0);
+ int radix = ScriptRuntime.toInt32(args, 1);
+
+ int len = s.length();
+ if (len == 0)
+ return ScriptRuntime.NaNobj;
+
+ boolean negative = false;
+ int start = 0;
+ char c;
+ do {
+ c = s.charAt(start);
+ if (!Character.isWhitespace(c))
+ break;
+ start++;
+ } while (start < len);
+
+ if (c == '+' || (negative = (c == '-')))
+ start++;
+
+ final int NO_RADIX = -1;
+ if (radix == 0) {
+ radix = NO_RADIX;
+ } else if (radix < 2 || radix > 36) {
+ return ScriptRuntime.NaNobj;
+ } else if (radix == 16 && len - start > 1 && s.charAt(start) == '0') {
+ c = s.charAt(start+1);
+ if (c == 'x' || c == 'X')
+ start += 2;
+ }
+
+ if (radix == NO_RADIX) {
+ radix = 10;
+ if (len - start > 1 && s.charAt(start) == '0') {
+ c = s.charAt(start+1);
+ if (c == 'x' || c == 'X') {
+ radix = 16;
+ start += 2;
+ } else if ('0' <= c && c <= '9') {
+ radix = 8;
+ start++;
+ }
+ }
+ }
+
+ double d = ScriptRuntime.stringToNumber(s, start, radix);
+ return ScriptRuntime.wrapNumber(negative ? -d : d);
+ }
+
+ /**
+ * The global method parseFloat, as per ECMA-262 15.1.2.3.
+ *
+ * @param args the arguments to parseFloat, ignoring args[>=1]
+ */
+ private Object js_parseFloat(Object[] args)
+ {
+ if (args.length < 1)
+ return ScriptRuntime.NaNobj;
+
+ String s = ScriptRuntime.toString(args[0]);
+ int len = s.length();
+ int start = 0;
+ // Scan forward to skip whitespace
+ char c;
+ for (;;) {
+ if (start == len) {
+ return ScriptRuntime.NaNobj;
+ }
+ c = s.charAt(start);
+ if (!TokenStream.isJSSpace(c)) {
+ break;
+ }
+ ++start;
+ }
+
+ int i = start;
+ if (c == '+' || c == '-') {
+ ++i;
+ if (i == len) {
+ return ScriptRuntime.NaNobj;
+ }
+ c = s.charAt(i);
+ }
+
+ if (c == 'I') {
+ // check for "Infinity"
+ if (i+8 <= len && s.regionMatches(i, "Infinity", 0, 8)) {
+ double d;
+ if (s.charAt(start) == '-') {
+ d = Double.NEGATIVE_INFINITY;
+ } else {
+ d = Double.POSITIVE_INFINITY;
+ }
+ return ScriptRuntime.wrapNumber(d);
+ }
+ return ScriptRuntime.NaNobj;
+ }
+
+ // Find the end of the legal bit
+ int decimal = -1;
+ int exponent = -1;
+ for (; i < len; i++) {
+ switch (s.charAt(i)) {
+ case '.':
+ if (decimal != -1) // Only allow a single decimal point.
+ break;
+ decimal = i;
+ continue;
+
+ case 'e':
+ case 'E':
+ if (exponent != -1)
+ break;
+ exponent = i;
+ continue;
+
+ case '+':
+ case '-':
+ // Only allow '+' or '-' after 'e' or 'E'
+ if (exponent != i-1)
+ break;
+ continue;
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ continue;
+
+ default:
+ break;
+ }
+ break;
+ }
+ s = s.substring(start, i);
+ try {
+ return Double.valueOf(s);
+ }
+ catch (NumberFormatException ex) {
+ return ScriptRuntime.NaNobj;
+ }
+ }
+
+ /**
+ * The global method escape, as per ECMA-262 15.1.2.4.
+
+ * Includes code for the 'mask' argument supported by the C escape
+ * method, which used to be part of the browser imbedding. Blame
+ * for the strange constant names should be directed there.
+ */
+
+ private Object js_escape(Object[] args) {
+ final int
+ URL_XALPHAS = 1,
+ URL_XPALPHAS = 2,
+ URL_PATH = 4;
+
+ String s = ScriptRuntime.toString(args, 0);
+
+ int mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH;
+ if (args.length > 1) { // the 'mask' argument. Non-ECMA.
+ double d = ScriptRuntime.toNumber(args[1]);
+ if (d != d || ((mask = (int) d) != d) ||
+ 0 != (mask & ~(URL_XALPHAS | URL_XPALPHAS | URL_PATH)))
+ {
+ throw Context.reportRuntimeError0("msg.bad.esc.mask");
+ }
+ }
+
+ StringBuffer sb = null;
+ for (int k = 0, L = s.length(); k != L; ++k) {
+ int c = s.charAt(k);
+ if (mask != 0
+ && ((c >= '0' && c <= '9')
+ || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
+ || c == '@' || c == '*' || c == '_' || c == '-' || c == '.'
+ || (0 != (mask & URL_PATH) && (c == '/' || c == '+'))))
+ {
+ if (sb != null) {
+ sb.append((char)c);
+ }
+ } else {
+ if (sb == null) {
+ sb = new StringBuffer(L + 3);
+ sb.append(s);
+ sb.setLength(k);
+ }
+
+ int hexSize;
+ if (c < 256) {
+ if (c == ' ' && mask == URL_XPALPHAS) {
+ sb.append('+');
+ continue;
+ }
+ sb.append('%');
+ hexSize = 2;
+ } else {
+ sb.append('%');
+ sb.append('u');
+ hexSize = 4;
+ }
+
+ // append hexadecimal form of c left-padded with 0
+ for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4) {
+ int digit = 0xf & (c >> shift);
+ int hc = (digit < 10) ? '0' + digit : 'A' - 10 + digit;
+ sb.append((char)hc);
+ }
+ }
+ }
+
+ return (sb == null) ? s : sb.toString();
+ }
+
+ /**
+ * The global unescape method, as per ECMA-262 15.1.2.5.
+ */
+
+ private Object js_unescape(Object[] args)
+ {
+ String s = ScriptRuntime.toString(args, 0);
+ int firstEscapePos = s.indexOf('%');
+ if (firstEscapePos >= 0) {
+ int L = s.length();
+ char[] buf = s.toCharArray();
+ int destination = firstEscapePos;
+ for (int k = firstEscapePos; k != L;) {
+ char c = buf[k];
+ ++k;
+ if (c == '%' && k != L) {
+ int end, start;
+ if (buf[k] == 'u') {
+ start = k + 1;
+ end = k + 5;
+ } else {
+ start = k;
+ end = k + 2;
+ }
+ if (end <= L) {
+ int x = 0;
+ for (int i = start; i != end; ++i) {
+ x = Kit.xDigitToInt(buf[i], x);
+ }
+ if (x >= 0) {
+ c = (char)x;
+ k = end;
+ }
+ }
+ }
+ buf[destination] = c;
+ ++destination;
+ }
+ s = new String(buf, 0, destination);
+ }
+ return s;
+ }
+
+ private Object js_eval(Context cx, Scriptable scope, Scriptable thisObj, Object[] args)
+ {
+ if (thisObj.getParentScope() == null) {
+ // We allow indirect calls to eval as long as the script will execute in
+ // the global scope.
+ return ScriptRuntime.evalSpecial(cx, scope, thisObj, args, "eval code", 1);
+ }
+ String m = ScriptRuntime.getMessage1("msg.cant.call.indirect", "eval");
+ throw NativeGlobal.constructError(cx, "EvalError", m, scope);
+ }
+
+ static boolean isEvalFunction(Object functionObj)
+ {
+ if (functionObj instanceof IdFunctionObject) {
+ IdFunctionObject function = (IdFunctionObject)functionObj;
+ if (function.hasTag(FTAG) && function.methodId() == Id_eval) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @deprecated Use {@link ScriptRuntime#constructError(String,String)}
+ * instead.
+ */
+ public static EcmaError constructError(Context cx,
+ String error,
+ String message,
+ Scriptable scope)
+ {
+ return ScriptRuntime.constructError(error, message);
+ }
+
+ /**
+ * @deprecated Use
+ * {@link ScriptRuntime#constructError(String,String,String,int,String,int)}
+ * instead.
+ */
+ public static EcmaError constructError(Context cx,
+ String error,
+ String message,
+ Scriptable scope,
+ String sourceName,
+ int lineNumber,
+ int columnNumber,
+ String lineSource)
+ {
+ return ScriptRuntime.constructError(error, message,
+ sourceName, lineNumber,
+ lineSource, columnNumber);
+ }
+
+ /*
+ * ECMA 3, 15.1.3 URI Handling Function Properties
+ *
+ * The following are implementations of the algorithms
+ * given in the ECMA specification for the hidden functions
+ * 'Encode' and 'Decode'.
+ */
+ private static String encode(String str, boolean fullUri) {
+ byte[] utf8buf = null;
+ StringBuffer sb = null;
+
+ for (int k = 0, length = str.length(); k != length; ++k) {
+ char C = str.charAt(k);
+ if (encodeUnescaped(C, fullUri)) {
+ if (sb != null) {
+ sb.append(C);
+ }
+ } else {
+ if (sb == null) {
+ sb = new StringBuffer(length + 3);
+ sb.append(str);
+ sb.setLength(k);
+ utf8buf = new byte[6];
+ }
+ if (0xDC00 <= C && C <= 0xDFFF) {
+ throw Context.reportRuntimeError0("msg.bad.uri");
+ }
+ int V;
+ if (C < 0xD800 || 0xDBFF < C) {
+ V = C;
+ } else {
+ k++;
+ if (k == length) {
+ throw Context.reportRuntimeError0("msg.bad.uri");
+ }
+ char C2 = str.charAt(k);
+ if (!(0xDC00 <= C2 && C2 <= 0xDFFF)) {
+ throw Context.reportRuntimeError0("msg.bad.uri");
+ }
+ V = ((C - 0xD800) << 10) + (C2 - 0xDC00) + 0x10000;
+ }
+ int L = oneUcs4ToUtf8Char(utf8buf, V);
+ for (int j = 0; j < L; j++) {
+ int d = 0xff & utf8buf[j];
+ sb.append('%');
+ sb.append(toHexChar(d >>> 4));
+ sb.append(toHexChar(d & 0xf));
+ }
+ }
+ }
+ return (sb == null) ? str : sb.toString();
+ }
+
+ private static char toHexChar(int i) {
+ if (i >> 4 != 0) Kit.codeBug();
+ return (char)((i < 10) ? i + '0' : i - 10 + 'A');
+ }
+
+ private static int unHex(char c) {
+ if ('A' <= c && c <= 'F') {
+ return c - 'A' + 10;
+ } else if ('a' <= c && c <= 'f') {
+ return c - 'a' + 10;
+ } else if ('0' <= c && c <= '9') {
+ return c - '0';
+ } else {
+ return -1;
+ }
+ }
+
+ private static int unHex(char c1, char c2) {
+ int i1 = unHex(c1);
+ int i2 = unHex(c2);
+ if (i1 >= 0 && i2 >= 0) {
+ return (i1 << 4) | i2;
+ }
+ return -1;
+ }
+
+ private static String decode(String str, boolean fullUri) {
+ char[] buf = null;
+ int bufTop = 0;
+
+ for (int k = 0, length = str.length(); k != length;) {
+ char C = str.charAt(k);
+ if (C != '%') {
+ if (buf != null) {
+ buf[bufTop++] = C;
+ }
+ ++k;
+ } else {
+ if (buf == null) {
+ // decode always compress so result can not be bigger then
+ // str.length()
+ buf = new char[length];
+ str.getChars(0, k, buf, 0);
+ bufTop = k;
+ }
+ int start = k;
+ if (k + 3 > length)
+ throw Context.reportRuntimeError0("msg.bad.uri");
+ int B = unHex(str.charAt(k + 1), str.charAt(k + 2));
+ if (B < 0) throw Context.reportRuntimeError0("msg.bad.uri");
+ k += 3;
+ if ((B & 0x80) == 0) {
+ C = (char)B;
+ } else {
+ // Decode UTF-8 sequence into ucs4Char and encode it into
+ // UTF-16
+ int utf8Tail, ucs4Char, minUcs4Char;
+ if ((B & 0xC0) == 0x80) {
+ // First UTF-8 should be ouside 0x80..0xBF
+ throw Context.reportRuntimeError0("msg.bad.uri");
+ } else if ((B & 0x20) == 0) {
+ utf8Tail = 1; ucs4Char = B & 0x1F;
+ minUcs4Char = 0x80;
+ } else if ((B & 0x10) == 0) {
+ utf8Tail = 2; ucs4Char = B & 0x0F;
+ minUcs4Char = 0x800;
+ } else if ((B & 0x08) == 0) {
+ utf8Tail = 3; ucs4Char = B & 0x07;
+ minUcs4Char = 0x10000;
+ } else if ((B & 0x04) == 0) {
+ utf8Tail = 4; ucs4Char = B & 0x03;
+ minUcs4Char = 0x200000;
+ } else if ((B & 0x02) == 0) {
+ utf8Tail = 5; ucs4Char = B & 0x01;
+ minUcs4Char = 0x4000000;
+ } else {
+ // First UTF-8 can not be 0xFF or 0xFE
+ throw Context.reportRuntimeError0("msg.bad.uri");
+ }
+ if (k + 3 * utf8Tail > length)
+ throw Context.reportRuntimeError0("msg.bad.uri");
+ for (int j = 0; j != utf8Tail; j++) {
+ if (str.charAt(k) != '%')
+ throw Context.reportRuntimeError0("msg.bad.uri");
+ B = unHex(str.charAt(k + 1), str.charAt(k + 2));
+ if (B < 0 || (B & 0xC0) != 0x80)
+ throw Context.reportRuntimeError0("msg.bad.uri");
+ ucs4Char = (ucs4Char << 6) | (B & 0x3F);
+ k += 3;
+ }
+ // Check for overlongs and other should-not-present codes
+ if (ucs4Char < minUcs4Char
+ || ucs4Char == 0xFFFE || ucs4Char == 0xFFFF)
+ {
+ ucs4Char = 0xFFFD;
+ }
+ if (ucs4Char >= 0x10000) {
+ ucs4Char -= 0x10000;
+ if (ucs4Char > 0xFFFFF)
+ throw Context.reportRuntimeError0("msg.bad.uri");
+ char H = (char)((ucs4Char >>> 10) + 0xD800);
+ C = (char)((ucs4Char & 0x3FF) + 0xDC00);
+ buf[bufTop++] = H;
+ } else {
+ C = (char)ucs4Char;
+ }
+ }
+ if (fullUri && URI_DECODE_RESERVED.indexOf(C) >= 0) {
+ for (int x = start; x != k; x++) {
+ buf[bufTop++] = str.charAt(x);
+ }
+ } else {
+ buf[bufTop++] = C;
+ }
+ }
+ }
+ return (buf == null) ? str : new String(buf, 0, bufTop);
+ }
+
+ private static boolean encodeUnescaped(char c, boolean fullUri) {
+ if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
+ || ('0' <= c && c <= '9'))
+ {
+ return true;
+ }
+ if ("-_.!~*'()".indexOf(c) >= 0)
+ return true;
+ if (fullUri) {
+ return URI_DECODE_RESERVED.indexOf(c) >= 0;
+ }
+ return false;
+ }
+
+ private static final String URI_DECODE_RESERVED = ";/?:@&=+$,#";
+
+ /* Convert one UCS-4 char and write it into a UTF-8 buffer, which must be
+ * at least 6 bytes long. Return the number of UTF-8 bytes of data written.
+ */
+ private static int oneUcs4ToUtf8Char(byte[] utf8Buffer, int ucs4Char) {
+ int utf8Length = 1;
+
+ //JS_ASSERT(ucs4Char <= 0x7FFFFFFF);
+ if ((ucs4Char & ~0x7F) == 0)
+ utf8Buffer[0] = (byte)ucs4Char;
+ else {
+ int i;
+ int a = ucs4Char >>> 11;
+ utf8Length = 2;
+ while (a != 0) {
+ a >>>= 5;
+ utf8Length++;
+ }
+ i = utf8Length;
+ while (--i > 0) {
+ utf8Buffer[i] = (byte)((ucs4Char & 0x3F) | 0x80);
+ ucs4Char >>>= 6;
+ }
+ utf8Buffer[0] = (byte)(0x100 - (1 << (8-utf8Length)) + ucs4Char);
+ }
+ return utf8Length;
+ }
+
+ private static final Object FTAG = "Global";
+
+ private static final int
+ Id_decodeURI = 1,
+ Id_decodeURIComponent = 2,
+ Id_encodeURI = 3,
+ Id_encodeURIComponent = 4,
+ Id_escape = 5,
+ Id_eval = 6,
+ Id_isFinite = 7,
+ Id_isNaN = 8,
+ Id_isXMLName = 9,
+ Id_parseFloat = 10,
+ Id_parseInt = 11,
+ Id_unescape = 12,
+ Id_uneval = 13,
+
+ LAST_SCOPE_FUNCTION_ID = 13,
+
+ Id_new_CommonError = 14;
+}
diff --git a/src/org/mozilla/javascript/NativeIterator.java b/src/org/mozilla/javascript/NativeIterator.java
new file mode 100644
index 0000000..f383707
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeIterator.java
@@ -0,0 +1,269 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.util.Iterator;
+
+/**
+ * This class implements iterator objects. See
+ * http://developer.mozilla.org/en/docs/New_in_JavaScript_1.7#Iterators
+ *
+ * @author Norris Boyd
+ */
+public final class NativeIterator extends IdScriptableObject {
+ private static final long serialVersionUID = -4136968203581667681L;
+ private static final Object ITERATOR_TAG = "Iterator";
+
+ static void init(ScriptableObject scope, boolean sealed) {
+ // Iterator
+ NativeIterator iterator = new NativeIterator();
+ iterator.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+
+ // Generator
+ NativeGenerator.init(scope, sealed);
+
+ // StopIteration
+ NativeObject obj = new StopIteration();
+ obj.setPrototype(getObjectPrototype(scope));
+ obj.setParentScope(scope);
+ if (sealed) { obj.sealObject(); }
+ ScriptableObject.defineProperty(scope, STOP_ITERATION, obj,
+ ScriptableObject.DONTENUM);
+ // Use "associateValue" so that generators can continue to
+ // throw StopIteration even if the property of the global
+ // scope is replaced or deleted.
+ scope.associateValue(ITERATOR_TAG, obj);
+ }
+
+ /**
+ * Only for constructing the prototype object.
+ */
+ private NativeIterator() {
+ }
+
+ private NativeIterator(Object objectIterator) {
+ this.objectIterator = objectIterator;
+ }
+
+ /**
+ * Get the value of the "StopIteration" object. Note that this value
+ * is stored in the top-level scope using "associateValue" so the
+ * value can still be found even if a script overwrites or deletes
+ * the global "StopIteration" property.
+ * @param scope a scope whose parent chain reaches a top-level scope
+ * @return the StopIteration object
+ */
+ public static Object getStopIterationObject(Scriptable scope) {
+ Scriptable top = ScriptableObject.getTopLevelScope(scope);
+ return ScriptableObject.getTopScopeValue(top, ITERATOR_TAG);
+ }
+
+ private static final String STOP_ITERATION = "StopIteration";
+ public static final String ITERATOR_PROPERTY_NAME = "__iterator__";
+
+ static class StopIteration extends NativeObject {
+ private static final long serialVersionUID = 2485151085722377663L;
+
+ @Override
+ public String getClassName() {
+ return STOP_ITERATION;
+ }
+
+ /* StopIteration has custom instanceof behavior since it
+ * doesn't have a constructor.
+ */
+ @Override
+ public boolean hasInstance(Scriptable instance) {
+ return instance instanceof StopIteration;
+ }
+ }
+
+ @Override
+ public String getClassName() {
+ return "Iterator";
+ }
+
+ @Override
+ protected void initPrototypeId(int id) {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_constructor: arity=2; s="constructor"; break;
+ case Id_next: arity=0; s="next"; break;
+ case Id___iterator__: arity=1; s=ITERATOR_PROPERTY_NAME; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(ITERATOR_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(ITERATOR_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+
+ if (id == Id_constructor) {
+ return jsConstructor(cx, scope, thisObj, args);
+ }
+
+ if (!(thisObj instanceof NativeIterator))
+ throw incompatibleCallError(f);
+
+ NativeIterator iterator = (NativeIterator) thisObj;
+
+ switch (id) {
+
+ case Id_next:
+ return iterator.next(cx, scope);
+
+ case Id___iterator__:
+ /// XXX: what about argument? SpiderMonkey apparently ignores it
+ return thisObj;
+
+ default:
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+ }
+
+ /* The JavaScript constructor */
+ private static Object jsConstructor(Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (args.length == 0 || args[0] == null ||
+ args[0] == Undefined.instance)
+ {
+ throw ScriptRuntime.typeError1("msg.no.properties",
+ ScriptRuntime.toString(args[0]));
+ }
+ Scriptable obj = ScriptRuntime.toObject(scope, args[0]);
+ boolean keyOnly = args.length > 1 && ScriptRuntime.toBoolean(args[1]);
+ if (thisObj != null) {
+ // Called as a function. Convert to iterator if possible.
+
+ // For objects that implement java.lang.Iterable or
+ // java.util.Iterator, have JavaScript Iterator call the underlying
+ // iteration methods
+ Iterator<?> iterator =
+ VMBridge.instance.getJavaIterator(cx, scope, obj);
+ if (iterator != null) {
+ scope = ScriptableObject.getTopLevelScope(scope);
+ return cx.getWrapFactory().wrap(cx, scope,
+ new WrappedJavaIterator(iterator, scope),
+ WrappedJavaIterator.class);
+ }
+
+ // Otherwise, just call the runtime routine
+ Scriptable jsIterator = ScriptRuntime.toIterator(cx, scope, obj,
+ keyOnly);
+ if (jsIterator != null) {
+ return jsIterator;
+ }
+ }
+
+ // Otherwise, just set up to iterate over the properties of the object.
+ // Do not call __iterator__ method.
+ Object objectIterator = ScriptRuntime.enumInit(obj, cx,
+ keyOnly ? ScriptRuntime.ENUMERATE_KEYS_NO_ITERATOR
+ : ScriptRuntime.ENUMERATE_ARRAY_NO_ITERATOR);
+ ScriptRuntime.setEnumNumbers(objectIterator, true);
+ NativeIterator result = new NativeIterator(objectIterator);
+ result.setPrototype(NativeIterator.getClassPrototype(scope,
+ result.getClassName()));
+ result.setParentScope(scope);
+ return result;
+ }
+
+ private Object next(Context cx, Scriptable scope) {
+ Boolean b = ScriptRuntime.enumNext(this.objectIterator);
+ if (!b.booleanValue()) {
+ // Out of values. Throw StopIteration.
+ throw new JavaScriptException(
+ NativeIterator.getStopIterationObject(scope), null, 0);
+ }
+ return ScriptRuntime.enumId(this.objectIterator, cx);
+ }
+
+ static public class WrappedJavaIterator
+ {
+ WrappedJavaIterator(Iterator<?> iterator, Scriptable scope) {
+ this.iterator = iterator;
+ this.scope = scope;
+ }
+
+ public Object next() {
+ if (!iterator.hasNext()) {
+ // Out of values. Throw StopIteration.
+ throw new JavaScriptException(
+ NativeIterator.getStopIterationObject(scope), null, 0);
+ }
+ return iterator.next();
+ }
+
+ public Object __iterator__(boolean b) {
+ return this;
+ }
+
+ private Iterator<?> iterator;
+ private Scriptable scope;
+ }
+
+// #string_id_map#
+
+ @Override
+ protected int findPrototypeId(String s) {
+ int id;
+// #generated# Last update: 2007-06-11 09:43:19 EDT
+ L0: { id = 0; String X = null;
+ int s_length = s.length();
+ if (s_length==4) { X="next";id=Id_next; }
+ else if (s_length==11) { X="constructor";id=Id_constructor; }
+ else if (s_length==12) { X="__iterator__";id=Id___iterator__; }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_constructor = 1,
+ Id_next = 2,
+ Id___iterator__ = 3,
+ MAX_PROTOTYPE_ID = 3;
+
+// #/string_id_map#
+
+ private Object objectIterator;
+}
+
diff --git a/src/org/mozilla/javascript/NativeJavaArray.java b/src/org/mozilla/javascript/NativeJavaArray.java
new file mode 100644
index 0000000..b2766df
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeJavaArray.java
@@ -0,0 +1,180 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Frank Mitchell
+ * Mike Shaver
+ * Kemal Bayram
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.Array;
+
+/**
+ * This class reflects Java arrays into the JavaScript environment.
+ *
+ * @author Mike Shaver
+ * @see NativeJavaClass
+ * @see NativeJavaObject
+ * @see NativeJavaPackage
+ */
+
+public class NativeJavaArray extends NativeJavaObject
+{
+ static final long serialVersionUID = -924022554283675333L;
+
+ @Override
+ public String getClassName() {
+ return "JavaArray";
+ }
+
+ public static NativeJavaArray wrap(Scriptable scope, Object array) {
+ return new NativeJavaArray(scope, array);
+ }
+
+ @Override
+ public Object unwrap() {
+ return array;
+ }
+
+ public NativeJavaArray(Scriptable scope, Object array) {
+ super(scope, null, ScriptRuntime.ObjectClass);
+ Class<?> cl = array.getClass();
+ if (!cl.isArray()) {
+ throw new RuntimeException("Array expected");
+ }
+ this.array = array;
+ this.length = Array.getLength(array);
+ this.cls = cl.getComponentType();
+ }
+
+ @Override
+ public boolean has(String id, Scriptable start) {
+ return id.equals("length") || super.has(id, start);
+ }
+
+ @Override
+ public boolean has(int index, Scriptable start) {
+ return 0 <= index && index < length;
+ }
+
+ @Override
+ public Object get(String id, Scriptable start) {
+ if (id.equals("length"))
+ return new Integer(length);
+ Object result = super.get(id, start);
+ if (result == NOT_FOUND &&
+ !ScriptableObject.hasProperty(getPrototype(), id))
+ {
+ throw Context.reportRuntimeError2(
+ "msg.java.member.not.found", array.getClass().getName(), id);
+ }
+ return result;
+ }
+
+ @Override
+ public Object get(int index, Scriptable start) {
+ if (0 <= index && index < length) {
+ Context cx = Context.getContext();
+ Object obj = Array.get(array, index);
+ return cx.getWrapFactory().wrap(cx, this, obj, cls);
+ }
+ return Undefined.instance;
+ }
+
+ @Override
+ public void put(String id, Scriptable start, Object value) {
+ // Ignore assignments to "length"--it's readonly.
+ if (!id.equals("length"))
+ throw Context.reportRuntimeError1(
+ "msg.java.array.member.not.found", id);
+ }
+
+ @Override
+ public void put(int index, Scriptable start, Object value) {
+ if (0 <= index && index < length) {
+ Array.set(array, index, Context.jsToJava(value, cls));
+ }
+ else {
+ throw Context.reportRuntimeError2(
+ "msg.java.array.index.out.of.bounds", String.valueOf(index),
+ String.valueOf(length - 1));
+ }
+ }
+
+ @Override
+ public Object getDefaultValue(Class<?> hint) {
+ if (hint == null || hint == ScriptRuntime.StringClass)
+ return array.toString();
+ if (hint == ScriptRuntime.BooleanClass)
+ return Boolean.TRUE;
+ if (hint == ScriptRuntime.NumberClass)
+ return ScriptRuntime.NaNobj;
+ return this;
+ }
+
+ @Override
+ public Object[] getIds() {
+ Object[] result = new Object[length];
+ int i = length;
+ while (--i >= 0)
+ result[i] = new Integer(i);
+ return result;
+ }
+
+ @Override
+ public boolean hasInstance(Scriptable value) {
+ if (!(value instanceof Wrapper))
+ return false;
+ Object instance = ((Wrapper)value).unwrap();
+ return cls.isInstance(instance);
+ }
+
+ @Override
+ public Scriptable getPrototype() {
+ if (prototype == null) {
+ prototype =
+ ScriptableObject.getClassPrototype(this.getParentScope(),
+ "Array");
+ }
+ return prototype;
+ }
+
+ Object array;
+ int length;
+ Class<?> cls;
+}
diff --git a/src/org/mozilla/javascript/NativeJavaClass.java b/src/org/mozilla/javascript/NativeJavaClass.java
new file mode 100644
index 0000000..f72f9e7
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeJavaClass.java
@@ -0,0 +1,329 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Frank Mitchell
+ * Mike Shaver
+ * Kurt Westerfeld
+ * Kemal Bayram
+ * Ulrike Mueller <umueller at demandware.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.*;
+import java.util.Map;
+
+/**
+ * This class reflects Java classes into the JavaScript environment, mainly
+ * for constructors and static members. We lazily reflect properties,
+ * and currently do not guarantee that a single j.l.Class is only
+ * reflected once into the JS environment, although we should.
+ * The only known case where multiple reflections
+ * are possible occurs when a j.l.Class is wrapped as part of a
+ * method return or property access, rather than by walking the
+ * Packages/java tree.
+ *
+ * @author Mike Shaver
+ * @see NativeJavaArray
+ * @see NativeJavaObject
+ * @see NativeJavaPackage
+ */
+
+public class NativeJavaClass extends NativeJavaObject implements Function
+{
+ static final long serialVersionUID = -6460763940409461664L;
+
+ // Special property for getting the underlying Java class object.
+ static final String javaClassPropertyName = "__javaObject__";
+
+ public NativeJavaClass() {
+ }
+
+ public NativeJavaClass(Scriptable scope, Class<?> cl) {
+ this.parent = scope;
+ this.javaObject = cl;
+ initMembers();
+ }
+
+ @Override
+ protected void initMembers() {
+ Class<?> cl = (Class<?>)javaObject;
+ members = JavaMembers.lookupClass(parent, cl, cl, false);
+ staticFieldAndMethods
+ = members.getFieldAndMethodsObjects(this, cl, true);
+ }
+
+ @Override
+ public String getClassName() {
+ return "JavaClass";
+ }
+
+ @Override
+ public boolean has(String name, Scriptable start) {
+ return members.has(name, true) || javaClassPropertyName.equals(name);
+ }
+
+ @Override
+ public Object get(String name, Scriptable start) {
+ // When used as a constructor, ScriptRuntime.newObject() asks
+ // for our prototype to create an object of the correct type.
+ // We don't really care what the object is, since we're returning
+ // one constructed out of whole cloth, so we return null.
+ if (name.equals("prototype"))
+ return null;
+
+ if (staticFieldAndMethods != null) {
+ Object result = staticFieldAndMethods.get(name);
+ if (result != null)
+ return result;
+ }
+
+ if (members.has(name, true)) {
+ return members.get(this, name, javaObject, true);
+ }
+
+ if (javaClassPropertyName.equals(name)) {
+ Context cx = Context.getContext();
+ Scriptable scope = ScriptableObject.getTopLevelScope(start);
+ return cx.getWrapFactory().wrap(cx, scope, javaObject,
+ ScriptRuntime.ClassClass);
+ }
+
+ // experimental: look for nested classes by appending $name to
+ // current class' name.
+ Class<?> nestedClass = findNestedClass(getClassObject(), name);
+ if (nestedClass != null) {
+ NativeJavaClass nestedValue = new NativeJavaClass
+ (ScriptableObject.getTopLevelScope(this), nestedClass);
+ nestedValue.setParentScope(this);
+ return nestedValue;
+ }
+
+ throw members.reportMemberNotFound(name);
+ }
+
+ @Override
+ public void put(String name, Scriptable start, Object value) {
+ members.put(this, name, javaObject, value, true);
+ }
+
+ @Override
+ public Object[] getIds() {
+ return members.getIds(true);
+ }
+
+ public Class<?> getClassObject() {
+ return (Class<?>) super.unwrap();
+ }
+
+ @Override
+ public Object getDefaultValue(Class<?> hint) {
+ if (hint == null || hint == ScriptRuntime.StringClass)
+ return this.toString();
+ if (hint == ScriptRuntime.BooleanClass)
+ return Boolean.TRUE;
+ if (hint == ScriptRuntime.NumberClass)
+ return ScriptRuntime.NaNobj;
+ return this;
+ }
+
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ // If it looks like a "cast" of an object to this class type,
+ // walk the prototype chain to see if there's a wrapper of a
+ // object that's an instanceof this class.
+ if (args.length == 1 && args[0] instanceof Scriptable) {
+ Class<?> c = getClassObject();
+ Scriptable p = (Scriptable) args[0];
+ do {
+ if (p instanceof Wrapper) {
+ Object o = ((Wrapper) p).unwrap();
+ if (c.isInstance(o))
+ return p;
+ }
+ p = p.getPrototype();
+ } while (p != null);
+ }
+ return construct(cx, scope, args);
+ }
+
+ public Scriptable construct(Context cx, Scriptable scope, Object[] args)
+ {
+ Class<?> classObject = getClassObject();
+ int modifiers = classObject.getModifiers();
+ if (! (Modifier.isInterface(modifiers) ||
+ Modifier.isAbstract(modifiers)))
+ {
+ MemberBox[] ctors = members.ctors;
+ int index = NativeJavaMethod.findFunction(cx, ctors, args);
+ if (index < 0) {
+ String sig = NativeJavaMethod.scriptSignature(args);
+ throw Context.reportRuntimeError2(
+ "msg.no.java.ctor", classObject.getName(), sig);
+ }
+
+ // Found the constructor, so try invoking it.
+ return constructSpecific(cx, scope, args, ctors[index]);
+ } else {
+ Scriptable topLevel = ScriptableObject.getTopLevelScope(this);
+ String msg = "";
+ try {
+ // trying to construct an interface; use JavaAdapter to
+ // construct a new class on the fly that implements this
+ // interface.
+ Object v = topLevel.get("JavaAdapter", topLevel);
+ if (v != NOT_FOUND) {
+ Function f = (Function) v;
+ Object[] adapterArgs = { this, args[0] };
+ return f.construct(cx, topLevel,adapterArgs);
+ }
+ } catch (Exception ex) {
+ // fall through to error
+ String m = ex.getMessage();
+ if (m != null)
+ msg = m;
+ }
+ throw Context.reportRuntimeError2(
+ "msg.cant.instantiate", msg, classObject.getName());
+ }
+ }
+
+ static Scriptable constructSpecific(Context cx, Scriptable scope,
+ Object[] args, MemberBox ctor)
+ {
+ Scriptable topLevel = ScriptableObject.getTopLevelScope(scope);
+ Class<?>[] argTypes = ctor.argTypes;
+
+ if (ctor.vararg) {
+ // marshall the explicit parameter
+ Object[] newArgs = new Object[argTypes.length];
+ for (int i = 0; i < argTypes.length-1; i++) {
+ newArgs[i] = Context.jsToJava(args[i], argTypes[i]);
+ }
+
+ Object varArgs;
+
+ // Handle special situation where a single variable parameter
+ // is given and it is a Java or ECMA array.
+ if (args.length == argTypes.length &&
+ (args[args.length-1] == null ||
+ args[args.length-1] instanceof NativeArray ||
+ args[args.length-1] instanceof NativeJavaArray))
+ {
+ // convert the ECMA array into a native array
+ varArgs = Context.jsToJava(args[args.length-1],
+ argTypes[argTypes.length - 1]);
+ } else {
+ // marshall the variable parameter
+ Class<?> componentType = argTypes[argTypes.length - 1].
+ getComponentType();
+ varArgs = Array.newInstance(componentType,
+ args.length - argTypes.length + 1);
+ for (int i=0; i < Array.getLength(varArgs); i++) {
+ Object value = Context.jsToJava(args[argTypes.length-1 + i],
+ componentType);
+ Array.set(varArgs, i, value);
+ }
+ }
+
+ // add varargs
+ newArgs[argTypes.length-1] = varArgs;
+ // replace the original args with the new one
+ args = newArgs;
+ } else {
+ Object[] origArgs = args;
+ for (int i = 0; i < args.length; i++) {
+ Object arg = args[i];
+ Object x = Context.jsToJava(arg, argTypes[i]);
+ if (x != arg) {
+ if (args == origArgs) {
+ args = origArgs.clone();
+ }
+ args[i] = x;
+ }
+ }
+ }
+
+ Object instance = ctor.newInstance(args);
+ // we need to force this to be wrapped, because construct _has_
+ // to return a scriptable
+ return cx.getWrapFactory().wrapNewObject(cx, topLevel, instance);
+ }
+
+ @Override
+ public String toString() {
+ return "[JavaClass " + getClassObject().getName() + "]";
+ }
+
+ /**
+ * Determines if prototype is a wrapped Java object and performs
+ * a Java "instanceof".
+ * Exception: if value is an instance of NativeJavaClass, it isn't
+ * considered an instance of the Java class; this forestalls any
+ * name conflicts between java.lang.Class's methods and the
+ * static methods exposed by a JavaNativeClass.
+ */
+ @Override
+ public boolean hasInstance(Scriptable value) {
+
+ if (value instanceof Wrapper &&
+ !(value instanceof NativeJavaClass)) {
+ Object instance = ((Wrapper)value).unwrap();
+
+ return getClassObject().isInstance(instance);
+ }
+
+ // value wasn't something we understand
+ return false;
+ }
+
+ private static Class<?> findNestedClass(Class<?> parentClass, String name) {
+ String nestedClassName = parentClass.getName() + '$' + name;
+ ClassLoader loader = parentClass.getClassLoader();
+ if (loader == null) {
+ // ALERT: if loader is null, nested class should be loaded
+ // via system class loader which can be different from the
+ // loader that brought Rhino classes that Class.forName() would
+ // use, but ClassLoader.getSystemClassLoader() is Java 2 only
+ return Kit.classOrNull(nestedClassName);
+ } else {
+ return Kit.classOrNull(loader, nestedClassName);
+ }
+ }
+
+ private Map<String,FieldAndMethods> staticFieldAndMethods;
+}
diff --git a/src/org/mozilla/javascript/NativeJavaConstructor.java b/src/org/mozilla/javascript/NativeJavaConstructor.java
new file mode 100644
index 0000000..557aeb5
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeJavaConstructor.java
@@ -0,0 +1,88 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Frank Mitchell
+ * Mike Shaver
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * This class reflects a single Java constructor into the JavaScript
+ * environment. It satisfies a request for an overloaded constructor,
+ * as introduced in LiveConnect 3.
+ * All NativeJavaConstructors behave as JSRef `bound' methods, in that they
+ * always construct the same NativeJavaClass regardless of any reparenting
+ * that may occur.
+ *
+ * @author Frank Mitchell
+ * @see NativeJavaMethod
+ * @see NativeJavaPackage
+ * @see NativeJavaClass
+ */
+
+public class NativeJavaConstructor extends BaseFunction
+{
+ static final long serialVersionUID = -8149253217482668463L;
+
+ MemberBox ctor;
+
+ public NativeJavaConstructor(MemberBox ctor)
+ {
+ this.ctor = ctor;
+ }
+
+ @Override
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ return NativeJavaClass.constructSpecific(cx, scope, args, ctor);
+ }
+
+ @Override
+ public String getFunctionName()
+ {
+ String sig = JavaMembers.liveConnectSignature(ctor.argTypes);
+ return "<init>".concat(sig);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "[JavaConstructor " + ctor.getName() + "]";
+ }
+}
+
diff --git a/src/org/mozilla/javascript/NativeJavaMethod.java b/src/org/mozilla/javascript/NativeJavaMethod.java
new file mode 100644
index 0000000..b6eaf8b
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeJavaMethod.java
@@ -0,0 +1,580 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Frank Mitchell
+ * Mike Shaver
+ * Ulrike Mueller <umueller at demandware.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.*;
+
+/**
+ * This class reflects Java methods into the JavaScript environment and
+ * handles overloading of methods.
+ *
+ * @author Mike Shaver
+ * @see NativeJavaArray
+ * @see NativeJavaPackage
+ * @see NativeJavaClass
+ */
+
+public class NativeJavaMethod extends BaseFunction
+{
+ static final long serialVersionUID = -3440381785576412928L;
+
+ NativeJavaMethod(MemberBox[] methods)
+ {
+ this.functionName = methods[0].getName();
+ this.methods = methods;
+ }
+
+ NativeJavaMethod(MemberBox method, String name)
+ {
+ this.functionName = name;
+ this.methods = new MemberBox[] { method };
+ }
+
+ public NativeJavaMethod(Method method, String name)
+ {
+ this(new MemberBox(method), name);
+ }
+
+ @Override
+ public String getFunctionName()
+ {
+ return functionName;
+ }
+
+ static String scriptSignature(Object[] values)
+ {
+ StringBuffer sig = new StringBuffer();
+ for (int i = 0; i != values.length; ++i) {
+ Object value = values[i];
+
+ String s;
+ if (value == null) {
+ s = "null";
+ } else if (value instanceof Boolean) {
+ s = "boolean";
+ } else if (value instanceof String) {
+ s = "string";
+ } else if (value instanceof Number) {
+ s = "number";
+ } else if (value instanceof Scriptable) {
+ if (value instanceof Undefined) {
+ s = "undefined";
+ } else if (value instanceof Wrapper) {
+ Object wrapped = ((Wrapper)value).unwrap();
+ s = wrapped.getClass().getName();
+ } else if (value instanceof Function) {
+ s = "function";
+ } else {
+ s = "object";
+ }
+ } else {
+ s = JavaMembers.javaSignature(value.getClass());
+ }
+
+ if (i != 0) {
+ sig.append(',');
+ }
+ sig.append(s);
+ }
+ return sig.toString();
+ }
+
+ @Override
+ String decompile(int indent, int flags)
+ {
+ StringBuffer sb = new StringBuffer();
+ boolean justbody = (0 != (flags & Decompiler.ONLY_BODY_FLAG));
+ if (!justbody) {
+ sb.append("function ");
+ sb.append(getFunctionName());
+ sb.append("() {");
+ }
+ sb.append("/*\n");
+ sb.append(toString());
+ sb.append(justbody ? "*/\n" : "*/}\n");
+ return sb.toString();
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0, N = methods.length; i != N; ++i) {
+ Method method = methods[i].method();
+ sb.append(JavaMembers.javaSignature(method.getReturnType()));
+ sb.append(' ');
+ sb.append(method.getName());
+ sb.append(JavaMembers.liveConnectSignature(methods[i].argTypes));
+ sb.append('\n');
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ // Find a method that matches the types given.
+ if (methods.length == 0) {
+ throw new RuntimeException("No methods defined for call");
+ }
+
+ int index = findFunction(cx, methods, args);
+ if (index < 0) {
+ Class<?> c = methods[0].method().getDeclaringClass();
+ String sig = c.getName() + '.' + getFunctionName() + '(' +
+ scriptSignature(args) + ')';
+ throw Context.reportRuntimeError1("msg.java.no_such_method", sig);
+ }
+
+ MemberBox meth = methods[index];
+ Class<?>[] argTypes = meth.argTypes;
+
+ if (meth.vararg) {
+ // marshall the explicit parameters
+ Object[] newArgs = new Object[argTypes.length];
+ for (int i = 0; i < argTypes.length-1; i++) {
+ newArgs[i] = Context.jsToJava(args[i], argTypes[i]);
+ }
+
+ Object varArgs;
+
+ // Handle special situation where a single variable parameter
+ // is given and it is a Java or ECMA array or is null.
+ if (args.length == argTypes.length &&
+ (args[args.length-1] == null ||
+ args[args.length-1] instanceof NativeArray ||
+ args[args.length-1] instanceof NativeJavaArray))
+ {
+ // convert the ECMA array into a native array
+ varArgs = Context.jsToJava(args[args.length-1],
+ argTypes[argTypes.length - 1]);
+ } else {
+ // marshall the variable parameters
+ Class<?> componentType = argTypes[argTypes.length - 1].
+ getComponentType();
+ varArgs = Array.newInstance(componentType,
+ args.length - argTypes.length + 1);
+ for (int i = 0; i < Array.getLength(varArgs); i++) {
+ Object value = Context.jsToJava(args[argTypes.length-1 + i],
+ componentType);
+ Array.set(varArgs, i, value);
+ }
+ }
+
+ // add varargs
+ newArgs[argTypes.length-1] = varArgs;
+ // replace the original args with the new one
+ args = newArgs;
+ } else {
+ // First, we marshall the args.
+ Object[] origArgs = args;
+ for (int i = 0; i < args.length; i++) {
+ Object arg = args[i];
+ Object coerced = Context.jsToJava(arg, argTypes[i]);
+ if (coerced != arg) {
+ if (origArgs == args) {
+ args = args.clone();
+ }
+ args[i] = coerced;
+ }
+ }
+ }
+ Object javaObject;
+ if (meth.isStatic()) {
+ javaObject = null; // don't need an object
+ } else {
+ Scriptable o = thisObj;
+ Class<?> c = meth.getDeclaringClass();
+ for (;;) {
+ if (o == null) {
+ throw Context.reportRuntimeError3(
+ "msg.nonjava.method", getFunctionName(),
+ ScriptRuntime.toString(thisObj), c.getName());
+ }
+ if (o instanceof Wrapper) {
+ javaObject = ((Wrapper)o).unwrap();
+ if (c.isInstance(javaObject)) {
+ break;
+ }
+ }
+ o = o.getPrototype();
+ }
+ }
+ if (debug) {
+ printDebug("Calling ", meth, args);
+ }
+
+ Object retval = meth.invoke(javaObject, args);
+ Class<?> staticType = meth.method().getReturnType();
+
+ if (debug) {
+ Class<?> actualType = (retval == null) ? null
+ : retval.getClass();
+ System.err.println(" ----- Returned " + retval +
+ " actual = " + actualType +
+ " expect = " + staticType);
+ }
+
+ Object wrapped = cx.getWrapFactory().wrap(cx, scope,
+ retval, staticType);
+ if (debug) {
+ Class<?> actualType = (wrapped == null) ? null
+ : wrapped.getClass();
+ System.err.println(" ----- Wrapped as " + wrapped +
+ " class = " + actualType);
+ }
+
+ if (wrapped == null && staticType == Void.TYPE) {
+ wrapped = Undefined.instance;
+ }
+ return wrapped;
+ }
+
+ /**
+ * Find the index of the correct function to call given the set of methods
+ * or constructors and the arguments.
+ * If no function can be found to call, return -1.
+ */
+ static int findFunction(Context cx,
+ MemberBox[] methodsOrCtors, Object[] args)
+ {
+ if (methodsOrCtors.length == 0) {
+ return -1;
+ } else if (methodsOrCtors.length == 1) {
+ MemberBox member = methodsOrCtors[0];
+ Class<?>[] argTypes = member.argTypes;
+ int alength = argTypes.length;
+
+ if (member.vararg) {
+ alength--;
+ if ( alength > args.length) {
+ return -1;
+ }
+ } else {
+ if (alength != args.length) {
+ return -1;
+ }
+ }
+ for (int j = 0; j != alength; ++j) {
+ if (!NativeJavaObject.canConvert(args[j], argTypes[j])) {
+ if (debug) printDebug("Rejecting (args can't convert) ",
+ member, args);
+ return -1;
+ }
+ }
+ if (debug) printDebug("Found ", member, args);
+ return 0;
+ }
+
+ int firstBestFit = -1;
+ int[] extraBestFits = null;
+ int extraBestFitsCount = 0;
+
+ search:
+ for (int i = 0; i < methodsOrCtors.length; i++) {
+ MemberBox member = methodsOrCtors[i];
+ Class<?>[] argTypes = member.argTypes;
+ int alength = argTypes.length;
+ if (member.vararg) {
+ alength--;
+ if ( alength > args.length) {
+ continue search;
+ }
+ } else {
+ if (alength != args.length) {
+ continue search;
+ }
+ }
+ for (int j = 0; j < alength; j++) {
+ if (!NativeJavaObject.canConvert(args[j], argTypes[j])) {
+ if (debug) printDebug("Rejecting (args can't convert) ",
+ member, args);
+ continue search;
+ }
+ }
+ if (firstBestFit < 0) {
+ if (debug) printDebug("Found first applicable ", member, args);
+ firstBestFit = i;
+ } else {
+ // Compare with all currently fit methods.
+ // The loop starts from -1 denoting firstBestFit and proceed
+ // until extraBestFitsCount to avoid extraBestFits allocation
+ // in the most common case of no ambiguity
+ int betterCount = 0; // number of times member was prefered over
+ // best fits
+ int worseCount = 0; // number of times best fits were prefered
+ // over member
+ for (int j = -1; j != extraBestFitsCount; ++j) {
+ int bestFitIndex;
+ if (j == -1) {
+ bestFitIndex = firstBestFit;
+ } else {
+ bestFitIndex = extraBestFits[j];
+ }
+ MemberBox bestFit = methodsOrCtors[bestFitIndex];
+ if (cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS) &&
+ (bestFit.member().getModifiers() & Modifier.PUBLIC) !=
+ (member.member().getModifiers() & Modifier.PUBLIC))
+ {
+ // When FEATURE_ENHANCED_JAVA_ACCESS gives us access
+ // to non-public members, continue to prefer public
+ // methods in overloading
+ if ((bestFit.member().getModifiers() & Modifier.PUBLIC) == 0)
+ ++betterCount;
+ else
+ ++worseCount;
+ } else {
+ int preference = preferSignature(args, argTypes,
+ member.vararg,
+ bestFit.argTypes,
+ bestFit.vararg );
+ if (preference == PREFERENCE_AMBIGUOUS) {
+ break;
+ } else if (preference == PREFERENCE_FIRST_ARG) {
+ ++betterCount;
+ } else if (preference == PREFERENCE_SECOND_ARG) {
+ ++worseCount;
+ } else {
+ if (preference != PREFERENCE_EQUAL) Kit.codeBug();
+ // This should not happen in theory
+ // but on some JVMs, Class.getMethods will return all
+ // static methods of the class hierarchy, even if
+ // a derived class's parameters match exactly.
+ // We want to call the derived class's method.
+ if (bestFit.isStatic() &&
+ bestFit.getDeclaringClass().isAssignableFrom(
+ member.getDeclaringClass()))
+ {
+ // On some JVMs, Class.getMethods will return all
+ // static methods of the class hierarchy, even if
+ // a derived class's parameters match exactly.
+ // We want to call the derived class's method.
+ if (debug) printDebug(
+ "Substituting (overridden static)",
+ member, args);
+ if (j == -1) {
+ firstBestFit = i;
+ } else {
+ extraBestFits[j] = i;
+ }
+ } else {
+ if (debug) printDebug(
+ "Ignoring same signature member ",
+ member, args);
+ }
+ continue search;
+ }
+ }
+ }
+ if (betterCount == 1 + extraBestFitsCount) {
+ // member was prefered over all best fits
+ if (debug) printDebug(
+ "New first applicable ", member, args);
+ firstBestFit = i;
+ extraBestFitsCount = 0;
+ } else if (worseCount == 1 + extraBestFitsCount) {
+ // all best fits were prefered over member, ignore it
+ if (debug) printDebug(
+ "Rejecting (all current bests better) ", member, args);
+ } else {
+ // some ambiguity was present, add member to best fit set
+ if (debug) printDebug(
+ "Added to best fit set ", member, args);
+ if (extraBestFits == null) {
+ // Allocate maximum possible array
+ extraBestFits = new int[methodsOrCtors.length - 1];
+ }
+ extraBestFits[extraBestFitsCount] = i;
+ ++extraBestFitsCount;
+ }
+ }
+ }
+
+ if (firstBestFit < 0) {
+ // Nothing was found
+ return -1;
+ } else if (extraBestFitsCount == 0) {
+ // single best fit
+ return firstBestFit;
+ }
+
+ // report remaining ambiguity
+ StringBuffer buf = new StringBuffer();
+ for (int j = -1; j != extraBestFitsCount; ++j) {
+ int bestFitIndex;
+ if (j == -1) {
+ bestFitIndex = firstBestFit;
+ } else {
+ bestFitIndex = extraBestFits[j];
+ }
+ buf.append("\n ");
+ buf.append(methodsOrCtors[bestFitIndex].toJavaDeclaration());
+ }
+
+ MemberBox firstFitMember = methodsOrCtors[firstBestFit];
+ String memberName = firstFitMember.getName();
+ String memberClass = firstFitMember.getDeclaringClass().getName();
+
+ if (methodsOrCtors[0].isMethod()) {
+ throw Context.reportRuntimeError3(
+ "msg.constructor.ambiguous",
+ memberName, scriptSignature(args), buf.toString());
+ } else {
+ throw Context.reportRuntimeError4(
+ "msg.method.ambiguous", memberClass,
+ memberName, scriptSignature(args), buf.toString());
+ }
+ }
+
+ /** Types are equal */
+ private static final int PREFERENCE_EQUAL = 0;
+ private static final int PREFERENCE_FIRST_ARG = 1;
+ private static final int PREFERENCE_SECOND_ARG = 2;
+ /** No clear "easy" conversion */
+ private static final int PREFERENCE_AMBIGUOUS = 3;
+
+ /**
+ * Determine which of two signatures is the closer fit.
+ * Returns one of PREFERENCE_EQUAL, PREFERENCE_FIRST_ARG,
+ * PREFERENCE_SECOND_ARG, or PREFERENCE_AMBIGUOUS.
+ */
+ private static int preferSignature(Object[] args,
+ Class<?>[] sig1,
+ boolean vararg1,
+ Class<?>[] sig2,
+ boolean vararg2 )
+ {
+ // TODO: This test is pretty primitive. It basically prefers
+ // a matching no vararg method over a vararg method independent
+ // of the type conversion cost. This can lead to unexpected results.
+ int alength = args.length;
+ if (!vararg1 && vararg2) {
+ // prefer the no vararg signature
+ return PREFERENCE_FIRST_ARG;
+ } else if (vararg1 && !vararg2) {
+ // prefer the no vararg signature
+ return PREFERENCE_SECOND_ARG;
+ } else if (vararg1 && vararg2) {
+ if (sig1.length < sig2.length) {
+ // prefer the signature with more explicit types
+ return PREFERENCE_SECOND_ARG;
+ } else if (sig1.length > sig2.length) {
+ // prefer the signature with more explicit types
+ return PREFERENCE_FIRST_ARG;
+ } else {
+ // Both are varargs and have the same length, so make the
+ // decision with the explicit args.
+ alength = Math.min(args.length, sig1.length-1);
+ }
+ }
+
+ int totalPreference = 0;
+ for (int j = 0; j < alength; j++) {
+ Class<?> type1 = sig1[j];
+ Class<?> type2 = sig2[j];
+ if (type1 == type2) {
+ continue;
+ }
+ Object arg = args[j];
+
+ // Determine which of type1, type2 is easier to convert from arg.
+
+ int rank1 = NativeJavaObject.getConversionWeight(arg, type1);
+ int rank2 = NativeJavaObject.getConversionWeight(arg, type2);
+
+ int preference;
+ if (rank1 < rank2) {
+ preference = PREFERENCE_FIRST_ARG;
+ } else if (rank1 > rank2) {
+ preference = PREFERENCE_SECOND_ARG;
+ } else {
+ // Equal ranks
+ if (rank1 == NativeJavaObject.CONVERSION_NONTRIVIAL) {
+ if (type1.isAssignableFrom(type2)) {
+ preference = PREFERENCE_SECOND_ARG;
+ } else if (type2.isAssignableFrom(type1)) {
+ preference = PREFERENCE_FIRST_ARG;
+ } else {
+ preference = PREFERENCE_AMBIGUOUS;
+ }
+ } else {
+ preference = PREFERENCE_AMBIGUOUS;
+ }
+ }
+
+ totalPreference |= preference;
+
+ if (totalPreference == PREFERENCE_AMBIGUOUS) {
+ break;
+ }
+ }
+ return totalPreference;
+ }
+
+
+ private static final boolean debug = false;
+
+ private static void printDebug(String msg, MemberBox member,
+ Object[] args)
+ {
+ if (debug) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(" ----- ");
+ sb.append(msg);
+ sb.append(member.getDeclaringClass().getName());
+ sb.append('.');
+ if (member.isMethod()) {
+ sb.append(member.getName());
+ }
+ sb.append(JavaMembers.liveConnectSignature(member.argTypes));
+ sb.append(" for arguments (");
+ sb.append(scriptSignature(args));
+ sb.append(')');
+ System.out.println(sb);
+ }
+ }
+
+ MemberBox[] methods;
+ private String functionName;
+}
+
diff --git a/src/org/mozilla/javascript/NativeJavaObject.java b/src/org/mozilla/javascript/NativeJavaObject.java
new file mode 100644
index 0000000..6bb106a
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeJavaObject.java
@@ -0,0 +1,1002 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Frank Mitchell
+ * Mike Shaver
+ * Kemal Bayram
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.Map;
+import java.util.Date;
+
+/**
+ * This class reflects non-Array Java objects into the JavaScript environment. It
+ * reflect fields directly, and uses NativeJavaMethod objects to reflect (possibly
+ * overloaded) methods.<p>
+ *
+ * @author Mike Shaver
+ * @see NativeJavaArray
+ * @see NativeJavaPackage
+ * @see NativeJavaClass
+ */
+
+public class NativeJavaObject implements Scriptable, Wrapper, Serializable
+{
+ static final long serialVersionUID = -6948590651130498591L;
+
+ public NativeJavaObject() { }
+
+ public NativeJavaObject(Scriptable scope, Object javaObject,
+ Class<?> staticType)
+ {
+ this(scope, javaObject, staticType, false);
+ }
+
+ public NativeJavaObject(Scriptable scope, Object javaObject,
+ Class<?> staticType, boolean isAdapter)
+ {
+ this.parent = scope;
+ this.javaObject = javaObject;
+ this.staticType = staticType;
+ this.isAdapter = isAdapter;
+ initMembers();
+ }
+
+ protected void initMembers() {
+ Class<?> dynamicType;
+ if (javaObject != null) {
+ dynamicType = javaObject.getClass();
+ } else {
+ dynamicType = staticType;
+ }
+ members = JavaMembers.lookupClass(parent, dynamicType, staticType,
+ isAdapter);
+ fieldAndMethods
+ = members.getFieldAndMethodsObjects(this, javaObject, false);
+ }
+
+ public boolean has(String name, Scriptable start) {
+ return members.has(name, false);
+ }
+
+ public boolean has(int index, Scriptable start) {
+ return false;
+ }
+
+ public Object get(String name, Scriptable start) {
+ if (fieldAndMethods != null) {
+ Object result = fieldAndMethods.get(name);
+ if (result != null) {
+ return result;
+ }
+ }
+ // TODO: passing 'this' as the scope is bogus since it has
+ // no parent scope
+ return members.get(this, name, javaObject, false);
+ }
+
+ public Object get(int index, Scriptable start) {
+ throw members.reportMemberNotFound(Integer.toString(index));
+ }
+
+ public void put(String name, Scriptable start, Object value) {
+ // We could be asked to modify the value of a property in the
+ // prototype. Since we can't add a property to a Java object,
+ // we modify it in the prototype rather than copy it down.
+ if (prototype == null || members.has(name, false))
+ members.put(this, name, javaObject, value, false);
+ else
+ prototype.put(name, prototype, value);
+ }
+
+ public void put(int index, Scriptable start, Object value) {
+ throw members.reportMemberNotFound(Integer.toString(index));
+ }
+
+ public boolean hasInstance(Scriptable value) {
+ // This is an instance of a Java class, so always return false
+ return false;
+ }
+
+ public void delete(String name) {
+ }
+
+ public void delete(int index) {
+ }
+
+ public Scriptable getPrototype() {
+ if (prototype == null && javaObject instanceof String) {
+ return ScriptableObject.getClassPrototype(parent, "String");
+ }
+ return prototype;
+ }
+
+ /**
+ * Sets the prototype of the object.
+ */
+ public void setPrototype(Scriptable m) {
+ prototype = m;
+ }
+
+ /**
+ * Returns the parent (enclosing) scope of the object.
+ */
+ public Scriptable getParentScope() {
+ return parent;
+ }
+
+ /**
+ * Sets the parent (enclosing) scope of the object.
+ */
+ public void setParentScope(Scriptable m) {
+ parent = m;
+ }
+
+ public Object[] getIds() {
+ return members.getIds(false);
+ }
+
+/**
+ at deprecated Use {@link Context#getWrapFactory()} together with calling {@link
+WrapFactory#wrap(Context, Scriptable, Object, Class)}
+*/
+ public static Object wrap(Scriptable scope, Object obj, Class<?> staticType) {
+
+ Context cx = Context.getContext();
+ return cx.getWrapFactory().wrap(cx, scope, obj, staticType);
+ }
+
+ public Object unwrap() {
+ return javaObject;
+ }
+
+ public String getClassName() {
+ return "JavaObject";
+ }
+
+ public Object getDefaultValue(Class<?> hint)
+ {
+ Object value;
+ if (hint == null) {
+ if (javaObject instanceof Boolean) {
+ hint = ScriptRuntime.BooleanClass;
+ }
+ }
+ if (hint == null || hint == ScriptRuntime.StringClass) {
+ value = javaObject.toString();
+ } else {
+ String converterName;
+ if (hint == ScriptRuntime.BooleanClass) {
+ converterName = "booleanValue";
+ } else if (hint == ScriptRuntime.NumberClass) {
+ converterName = "doubleValue";
+ } else {
+ throw Context.reportRuntimeError0("msg.default.value");
+ }
+ Object converterObject = get(converterName, this);
+ if (converterObject instanceof Function) {
+ Function f = (Function)converterObject;
+ value = f.call(Context.getContext(), f.getParentScope(),
+ this, ScriptRuntime.emptyArgs);
+ } else {
+ if (hint == ScriptRuntime.NumberClass
+ && javaObject instanceof Boolean)
+ {
+ boolean b = ((Boolean)javaObject).booleanValue();
+ value = ScriptRuntime.wrapNumber(b ? 1.0 : 0.0);
+ } else {
+ value = javaObject.toString();
+ }
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Determine whether we can/should convert between the given type and the
+ * desired one. This should be superceded by a conversion-cost calculation
+ * function, but for now I'll hide behind precedent.
+ */
+ public static boolean canConvert(Object fromObj, Class<?> to) {
+ int weight = getConversionWeight(fromObj, to);
+
+ return (weight < CONVERSION_NONE);
+ }
+
+ private static final int JSTYPE_UNDEFINED = 0; // undefined type
+ private static final int JSTYPE_NULL = 1; // null
+ private static final int JSTYPE_BOOLEAN = 2; // boolean
+ private static final int JSTYPE_NUMBER = 3; // number
+ private static final int JSTYPE_STRING = 4; // string
+ private static final int JSTYPE_JAVA_CLASS = 5; // JavaClass
+ private static final int JSTYPE_JAVA_OBJECT = 6; // JavaObject
+ private static final int JSTYPE_JAVA_ARRAY = 7; // JavaArray
+ private static final int JSTYPE_OBJECT = 8; // Scriptable
+
+ static final byte CONVERSION_TRIVIAL = 1;
+ static final byte CONVERSION_NONTRIVIAL = 0;
+ static final byte CONVERSION_NONE = 99;
+
+ /**
+ * Derive a ranking based on how "natural" the conversion is.
+ * The special value CONVERSION_NONE means no conversion is possible,
+ * and CONVERSION_NONTRIVIAL signals that more type conformance testing
+ * is required.
+ * Based on
+ * <a href="http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html">
+ * "preferred method conversions" from Live Connect 3</a>
+ */
+ static int getConversionWeight(Object fromObj, Class<?> to) {
+ int fromCode = getJSTypeCode(fromObj);
+
+ switch (fromCode) {
+
+ case JSTYPE_UNDEFINED:
+ if (to == ScriptRuntime.StringClass ||
+ to == ScriptRuntime.ObjectClass) {
+ return 1;
+ }
+ break;
+
+ case JSTYPE_NULL:
+ if (!to.isPrimitive()) {
+ return 1;
+ }
+ break;
+
+ case JSTYPE_BOOLEAN:
+ // "boolean" is #1
+ if (to == Boolean.TYPE) {
+ return 1;
+ }
+ else if (to == ScriptRuntime.BooleanClass) {
+ return 2;
+ }
+ else if (to == ScriptRuntime.ObjectClass) {
+ return 3;
+ }
+ else if (to == ScriptRuntime.StringClass) {
+ return 4;
+ }
+ break;
+
+ case JSTYPE_NUMBER:
+ if (to.isPrimitive()) {
+ if (to == Double.TYPE) {
+ return 1;
+ }
+ else if (to != Boolean.TYPE) {
+ return 1 + getSizeRank(to);
+ }
+ }
+ else {
+ if (to == ScriptRuntime.StringClass) {
+ // native numbers are #1-8
+ return 9;
+ }
+ else if (to == ScriptRuntime.ObjectClass) {
+ return 10;
+ }
+ else if (ScriptRuntime.NumberClass.isAssignableFrom(to)) {
+ // "double" is #1
+ return 2;
+ }
+ }
+ break;
+
+ case JSTYPE_STRING:
+ if (to == ScriptRuntime.StringClass) {
+ return 1;
+ }
+ else if (to.isInstance(fromObj)) {
+ return 2;
+ }
+ else if (to.isPrimitive()) {
+ if (to == Character.TYPE) {
+ return 3;
+ } else if (to != Boolean.TYPE) {
+ return 4;
+ }
+ }
+ break;
+
+ case JSTYPE_JAVA_CLASS:
+ if (to == ScriptRuntime.ClassClass) {
+ return 1;
+ }
+ else if (to == ScriptRuntime.ObjectClass) {
+ return 3;
+ }
+ else if (to == ScriptRuntime.StringClass) {
+ return 4;
+ }
+ break;
+
+ case JSTYPE_JAVA_OBJECT:
+ case JSTYPE_JAVA_ARRAY:
+ Object javaObj = fromObj;
+ if (javaObj instanceof Wrapper) {
+ javaObj = ((Wrapper)javaObj).unwrap();
+ }
+ if (to.isInstance(javaObj)) {
+ return CONVERSION_NONTRIVIAL;
+ }
+ if (to == ScriptRuntime.StringClass) {
+ return 2;
+ }
+ else if (to.isPrimitive() && to != Boolean.TYPE) {
+ return (fromCode == JSTYPE_JAVA_ARRAY)
+ ? CONVERSION_NONE : 2 + getSizeRank(to);
+ }
+ break;
+
+ case JSTYPE_OBJECT:
+ // Other objects takes #1-#3 spots
+ if (Scriptable.class.isAssignableFrom(to) && to.isInstance(fromObj)) {
+ // No conversion required
+ return 1;
+ }
+ if (to.isArray()) {
+ if (fromObj instanceof NativeArray) {
+ // This is a native array conversion to a java array
+ // Array conversions are all equal, and preferable to object
+ // and string conversion, per LC3.
+ return 1;
+ }
+ }
+ else if (to == ScriptRuntime.ObjectClass) {
+ return 2;
+ }
+ else if (to == ScriptRuntime.StringClass) {
+ return 3;
+ }
+ else if (to == ScriptRuntime.DateClass) {
+ if (fromObj instanceof NativeDate) {
+ // This is a native date to java date conversion
+ return 1;
+ }
+ }
+ else if (to.isInterface()) {
+ if (fromObj instanceof Function) {
+ // See comments in coerceType
+ if (to.getMethods().length == 1) {
+ return 1;
+ }
+ }
+ return 11;
+ }
+ else if (to.isPrimitive() && to != Boolean.TYPE) {
+ return 3 + getSizeRank(to);
+ }
+ break;
+ }
+
+ return CONVERSION_NONE;
+ }
+
+ static int getSizeRank(Class<?> aType) {
+ if (aType == Double.TYPE) {
+ return 1;
+ }
+ else if (aType == Float.TYPE) {
+ return 2;
+ }
+ else if (aType == Long.TYPE) {
+ return 3;
+ }
+ else if (aType == Integer.TYPE) {
+ return 4;
+ }
+ else if (aType == Short.TYPE) {
+ return 5;
+ }
+ else if (aType == Character.TYPE) {
+ return 6;
+ }
+ else if (aType == Byte.TYPE) {
+ return 7;
+ }
+ else if (aType == Boolean.TYPE) {
+ return CONVERSION_NONE;
+ }
+ else {
+ return 8;
+ }
+ }
+
+ private static int getJSTypeCode(Object value) {
+ if (value == null) {
+ return JSTYPE_NULL;
+ }
+ else if (value == Undefined.instance) {
+ return JSTYPE_UNDEFINED;
+ }
+ else if (value instanceof String) {
+ return JSTYPE_STRING;
+ }
+ else if (value instanceof Number) {
+ return JSTYPE_NUMBER;
+ }
+ else if (value instanceof Boolean) {
+ return JSTYPE_BOOLEAN;
+ }
+ else if (value instanceof Scriptable) {
+ if (value instanceof NativeJavaClass) {
+ return JSTYPE_JAVA_CLASS;
+ }
+ else if (value instanceof NativeJavaArray) {
+ return JSTYPE_JAVA_ARRAY;
+ }
+ else if (value instanceof Wrapper) {
+ return JSTYPE_JAVA_OBJECT;
+ }
+ else {
+ return JSTYPE_OBJECT;
+ }
+ }
+ else if (value instanceof Class) {
+ return JSTYPE_JAVA_CLASS;
+ }
+ else {
+ Class<?> valueClass = value.getClass();
+ if (valueClass.isArray()) {
+ return JSTYPE_JAVA_ARRAY;
+ }
+ else {
+ return JSTYPE_JAVA_OBJECT;
+ }
+ }
+ }
+
+ /**
+ * Not intended for public use. Callers should use the
+ * public API Context.toType.
+ * @deprecated as of 1.5 Release 4
+ * @see org.mozilla.javascript.Context#jsToJava(Object, Class)
+ */
+ public static Object coerceType(Class<?> type, Object value)
+ {
+ return coerceTypeImpl(type, value);
+ }
+
+ /**
+ * Type-munging for field setting and method invocation.
+ * Conforms to LC3 specification
+ */
+ static Object coerceTypeImpl(Class<?> type, Object value)
+ {
+ if (value != null && value.getClass() == type) {
+ return value;
+ }
+
+ switch (getJSTypeCode(value)) {
+
+ case JSTYPE_NULL:
+ // raise error if type.isPrimitive()
+ if (type.isPrimitive()) {
+ reportConversionError(value, type);
+ }
+ return null;
+
+ case JSTYPE_UNDEFINED:
+ if (type == ScriptRuntime.StringClass ||
+ type == ScriptRuntime.ObjectClass) {
+ return "undefined";
+ }
+ else {
+ reportConversionError("undefined", type);
+ }
+ break;
+
+ case JSTYPE_BOOLEAN:
+ // Under LC3, only JS Booleans can be coerced into a Boolean value
+ if (type == Boolean.TYPE ||
+ type == ScriptRuntime.BooleanClass ||
+ type == ScriptRuntime.ObjectClass) {
+ return value;
+ }
+ else if (type == ScriptRuntime.StringClass) {
+ return value.toString();
+ }
+ else {
+ reportConversionError(value, type);
+ }
+ break;
+
+ case JSTYPE_NUMBER:
+ if (type == ScriptRuntime.StringClass) {
+ return ScriptRuntime.toString(value);
+ }
+ else if (type == ScriptRuntime.ObjectClass) {
+ return coerceToNumber(Double.TYPE, value);
+ }
+ else if ((type.isPrimitive() && type != Boolean.TYPE) ||
+ ScriptRuntime.NumberClass.isAssignableFrom(type)) {
+ return coerceToNumber(type, value);
+ }
+ else {
+ reportConversionError(value, type);
+ }
+ break;
+
+ case JSTYPE_STRING:
+ if (type == ScriptRuntime.StringClass || type.isInstance(value)) {
+ return value;
+ }
+ else if (type == Character.TYPE
+ || type == ScriptRuntime.CharacterClass)
+ {
+ // Special case for converting a single char string to a
+ // character
+ // Placed here because it applies *only* to JS strings,
+ // not other JS objects converted to strings
+ if (((String)value).length() == 1) {
+ return new Character(((String)value).charAt(0));
+ }
+ else {
+ return coerceToNumber(type, value);
+ }
+ }
+ else if ((type.isPrimitive() && type != Boolean.TYPE)
+ || ScriptRuntime.NumberClass.isAssignableFrom(type))
+ {
+ return coerceToNumber(type, value);
+ }
+ else {
+ reportConversionError(value, type);
+ }
+ break;
+
+ case JSTYPE_JAVA_CLASS:
+ if (value instanceof Wrapper) {
+ value = ((Wrapper)value).unwrap();
+ }
+
+ if (type == ScriptRuntime.ClassClass ||
+ type == ScriptRuntime.ObjectClass) {
+ return value;
+ }
+ else if (type == ScriptRuntime.StringClass) {
+ return value.toString();
+ }
+ else {
+ reportConversionError(value, type);
+ }
+ break;
+
+ case JSTYPE_JAVA_OBJECT:
+ case JSTYPE_JAVA_ARRAY:
+ if (value instanceof Wrapper) {
+ value = ((Wrapper)value).unwrap();
+ }
+ if (type.isPrimitive()) {
+ if (type == Boolean.TYPE) {
+ reportConversionError(value, type);
+ }
+ return coerceToNumber(type, value);
+ }
+ else {
+ if (type == ScriptRuntime.StringClass) {
+ return value.toString();
+ }
+ else {
+ if (type.isInstance(value)) {
+ return value;
+ }
+ else {
+ reportConversionError(value, type);
+ }
+ }
+ }
+ break;
+
+ case JSTYPE_OBJECT:
+ if (type == ScriptRuntime.StringClass) {
+ return ScriptRuntime.toString(value);
+ }
+ else if (type.isPrimitive()) {
+ if (type == Boolean.TYPE) {
+ reportConversionError(value, type);
+ }
+ return coerceToNumber(type, value);
+ }
+ else if (type.isInstance(value)) {
+ return value;
+ }
+ else if (type == ScriptRuntime.DateClass
+ && value instanceof NativeDate)
+ {
+ double time = ((NativeDate)value).getJSTimeValue();
+ // XXX: This will replace NaN by 0
+ return new Date((long)time);
+ }
+ else if (type.isArray() && value instanceof NativeArray) {
+ // Make a new java array, and coerce the JS array components
+ // to the target (component) type.
+ NativeArray array = (NativeArray) value;
+ long length = array.getLength();
+ Class<?> arrayType = type.getComponentType();
+ Object Result = Array.newInstance(arrayType, (int)length);
+ for (int i = 0 ; i < length ; ++i) {
+ try {
+ Array.set(Result, i, coerceType(arrayType,
+ array.get(i, array)));
+ }
+ catch (EvaluatorException ee) {
+ reportConversionError(value, type);
+ }
+ }
+
+ return Result;
+ }
+ else if (value instanceof Wrapper) {
+ value = ((Wrapper)value).unwrap();
+ if (type.isInstance(value))
+ return value;
+ reportConversionError(value, type);
+ }
+ else if (type.isInterface() && value instanceof Callable) {
+ // Try to use function as implementation of Java interface.
+ //
+ // XXX: Currently only instances of ScriptableObject are
+ // supported since the resulting interface proxies should
+ // be reused next time conversion is made and generic
+ // Callable has no storage for it. Weak references can
+ // address it but for now use this restriction.
+ if (value instanceof ScriptableObject) {
+ ScriptableObject so = (ScriptableObject)value;
+ Object key = Kit.makeHashKeyFromPair(
+ COERCED_INTERFACE_KEY, type);
+ Object old = so.getAssociatedValue(key);
+ if (old != null) {
+ // Function was already wrapped
+ return old;
+ }
+ Context cx = Context.getContext();
+ Object glue
+ = InterfaceAdapter.create(cx, type, (Callable)value);
+ // Store for later retrival
+ glue = so.associateValue(key, glue);
+ return glue;
+ }
+ reportConversionError(value, type);
+ } else {
+ reportConversionError(value, type);
+ }
+ break;
+ }
+
+ return value;
+ }
+
+ private static Object coerceToNumber(Class<?> type, Object value)
+ {
+ Class<?> valueClass = value.getClass();
+
+ // Character
+ if (type == Character.TYPE || type == ScriptRuntime.CharacterClass) {
+ if (valueClass == ScriptRuntime.CharacterClass) {
+ return value;
+ }
+ return new Character((char)toInteger(value,
+ ScriptRuntime.CharacterClass,
+ Character.MIN_VALUE,
+ Character.MAX_VALUE));
+ }
+
+ // Double, Float
+ if (type == ScriptRuntime.ObjectClass ||
+ type == ScriptRuntime.DoubleClass || type == Double.TYPE) {
+ return valueClass == ScriptRuntime.DoubleClass
+ ? value
+ : new Double(toDouble(value));
+ }
+
+ if (type == ScriptRuntime.FloatClass || type == Float.TYPE) {
+ if (valueClass == ScriptRuntime.FloatClass) {
+ return value;
+ }
+ else {
+ double number = toDouble(value);
+ if (Double.isInfinite(number) || Double.isNaN(number)
+ || number == 0.0) {
+ return new Float((float)number);
+ }
+ else {
+ double absNumber = Math.abs(number);
+ if (absNumber < Float.MIN_VALUE) {
+ return new Float((number > 0.0) ? +0.0 : -0.0);
+ }
+ else if (absNumber > Float.MAX_VALUE) {
+ return new Float((number > 0.0) ?
+ Float.POSITIVE_INFINITY :
+ Float.NEGATIVE_INFINITY);
+ }
+ else {
+ return new Float((float)number);
+ }
+ }
+ }
+ }
+
+ // Integer, Long, Short, Byte
+ if (type == ScriptRuntime.IntegerClass || type == Integer.TYPE) {
+ if (valueClass == ScriptRuntime.IntegerClass) {
+ return value;
+ }
+ else {
+ return new Integer((int)toInteger(value,
+ ScriptRuntime.IntegerClass,
+ Integer.MIN_VALUE,
+ Integer.MAX_VALUE));
+ }
+ }
+
+ if (type == ScriptRuntime.LongClass || type == Long.TYPE) {
+ if (valueClass == ScriptRuntime.LongClass) {
+ return value;
+ } else {
+ /* Long values cannot be expressed exactly in doubles.
+ * We thus use the largest and smallest double value that
+ * has a value expressible as a long value. We build these
+ * numerical values from their hexidecimal representations
+ * to avoid any problems caused by attempting to parse a
+ * decimal representation.
+ */
+ final double max = Double.longBitsToDouble(0x43dfffffffffffffL);
+ final double min = Double.longBitsToDouble(0xc3e0000000000000L);
+ return new Long(toInteger(value,
+ ScriptRuntime.LongClass,
+ min,
+ max));
+ }
+ }
+
+ if (type == ScriptRuntime.ShortClass || type == Short.TYPE) {
+ if (valueClass == ScriptRuntime.ShortClass) {
+ return value;
+ }
+ else {
+ return new Short((short)toInteger(value,
+ ScriptRuntime.ShortClass,
+ Short.MIN_VALUE,
+ Short.MAX_VALUE));
+ }
+ }
+
+ if (type == ScriptRuntime.ByteClass || type == Byte.TYPE) {
+ if (valueClass == ScriptRuntime.ByteClass) {
+ return value;
+ }
+ else {
+ return new Byte((byte)toInteger(value,
+ ScriptRuntime.ByteClass,
+ Byte.MIN_VALUE,
+ Byte.MAX_VALUE));
+ }
+ }
+
+ return new Double(toDouble(value));
+ }
+
+
+ private static double toDouble(Object value)
+ {
+ if (value instanceof Number) {
+ return ((Number)value).doubleValue();
+ }
+ else if (value instanceof String) {
+ return ScriptRuntime.toNumber((String)value);
+ }
+ else if (value instanceof Scriptable) {
+ if (value instanceof Wrapper) {
+ // XXX: optimize tail-recursion?
+ return toDouble(((Wrapper)value).unwrap());
+ }
+ else {
+ return ScriptRuntime.toNumber(value);
+ }
+ }
+ else {
+ Method meth;
+ try {
+ meth = value.getClass().getMethod("doubleValue",
+ (Class [])null);
+ }
+ catch (NoSuchMethodException e) {
+ meth = null;
+ }
+ catch (SecurityException e) {
+ meth = null;
+ }
+ if (meth != null) {
+ try {
+ return ((Number)meth.invoke(value,
+ (Object [])null)).doubleValue();
+ }
+ catch (IllegalAccessException e) {
+ // XXX: ignore, or error message?
+ reportConversionError(value, Double.TYPE);
+ }
+ catch (InvocationTargetException e) {
+ // XXX: ignore, or error message?
+ reportConversionError(value, Double.TYPE);
+ }
+ }
+ return ScriptRuntime.toNumber(value.toString());
+ }
+ }
+
+ private static long toInteger(Object value, Class<?> type,
+ double min, double max)
+ {
+ double d = toDouble(value);
+
+ if (Double.isInfinite(d) || Double.isNaN(d)) {
+ // Convert to string first, for more readable message
+ reportConversionError(ScriptRuntime.toString(value), type);
+ }
+
+ if (d > 0.0) {
+ d = Math.floor(d);
+ }
+ else {
+ d = Math.ceil(d);
+ }
+
+ if (d < min || d > max) {
+ // Convert to string first, for more readable message
+ reportConversionError(ScriptRuntime.toString(value), type);
+ }
+ return (long)d;
+ }
+
+ static void reportConversionError(Object value, Class<?> type)
+ {
+ // It uses String.valueOf(value), not value.toString() since
+ // value can be null, bug 282447.
+ throw Context.reportRuntimeError2(
+ "msg.conversion.not.allowed",
+ String.valueOf(value),
+ JavaMembers.javaSignature(type));
+ }
+
+ private void writeObject(ObjectOutputStream out)
+ throws IOException
+ {
+ out.defaultWriteObject();
+
+ out.writeBoolean(isAdapter);
+ if (isAdapter) {
+ if (adapter_writeAdapterObject == null) {
+ throw new IOException();
+ }
+ Object[] args = { javaObject, out };
+ try {
+ adapter_writeAdapterObject.invoke(null, args);
+ } catch (Exception ex) {
+ throw new IOException();
+ }
+ } else {
+ out.writeObject(javaObject);
+ }
+
+ if (staticType != null) {
+ out.writeObject(staticType.getClass().getName());
+ } else {
+ out.writeObject(null);
+ }
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+
+ isAdapter = in.readBoolean();
+ if (isAdapter) {
+ if (adapter_readAdapterObject == null)
+ throw new ClassNotFoundException();
+ Object[] args = { this, in };
+ try {
+ javaObject = adapter_readAdapterObject.invoke(null, args);
+ } catch (Exception ex) {
+ throw new IOException();
+ }
+ } else {
+ javaObject = in.readObject();
+ }
+
+ String className = (String)in.readObject();
+ if (className != null) {
+ staticType = Class.forName(className);
+ } else {
+ staticType = null;
+ }
+
+ initMembers();
+ }
+
+ /**
+ * The prototype of this object.
+ */
+ protected Scriptable prototype;
+
+ /**
+ * The parent scope of this object.
+ */
+ protected Scriptable parent;
+
+ protected transient Object javaObject;
+
+ protected transient Class<?> staticType;
+ protected transient JavaMembers members;
+ private transient Map<String,FieldAndMethods> fieldAndMethods;
+ private transient boolean isAdapter;
+
+ private static final Object COERCED_INTERFACE_KEY = "Coerced Interface";
+ private static Method adapter_writeAdapterObject;
+ private static Method adapter_readAdapterObject;
+
+ static {
+ // Reflection in java is verbose
+ Class<?>[] sig2 = new Class[2];
+ Class<?> cl = Kit.classOrNull("org.mozilla.javascript.JavaAdapter");
+ if (cl != null) {
+ try {
+ sig2[0] = ScriptRuntime.ObjectClass;
+ sig2[1] = Kit.classOrNull("java.io.ObjectOutputStream");
+ adapter_writeAdapterObject = cl.getMethod("writeAdapterObject",
+ sig2);
+
+ sig2[0] = ScriptRuntime.ScriptableClass;
+ sig2[1] = Kit.classOrNull("java.io.ObjectInputStream");
+ adapter_readAdapterObject = cl.getMethod("readAdapterObject",
+ sig2);
+
+ } catch (Exception ex) {
+ adapter_writeAdapterObject = null;
+ adapter_readAdapterObject = null;
+ }
+ }
+ }
+
+}
diff --git a/src/org/mozilla/javascript/NativeJavaPackage.java b/src/org/mozilla/javascript/NativeJavaPackage.java
new file mode 100644
index 0000000..464762a
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeJavaPackage.java
@@ -0,0 +1,218 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Frank Mitchell
+ * Mike Shaver
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class reflects Java packages into the JavaScript environment. We
+ * lazily reflect classes and subpackages, and use a caching/sharing
+ * system to ensure that members reflected into one JavaPackage appear
+ * in all other references to the same package (as with Packages.java.lang
+ * and java.lang).
+ *
+ * @author Mike Shaver
+ * @see NativeJavaArray
+ * @see NativeJavaObject
+ * @see NativeJavaClass
+ */
+
+public class NativeJavaPackage extends ScriptableObject
+{
+ static final long serialVersionUID = 7445054382212031523L;
+
+ NativeJavaPackage(boolean internalUsage, String packageName,
+ ClassLoader classLoader)
+ {
+ this.packageName = packageName;
+ this.classLoader = classLoader;
+ }
+
+ /**
+ * @deprecated NativeJavaPackage is an internal class, do not use
+ * it directly.
+ */
+ public NativeJavaPackage(String packageName, ClassLoader classLoader) {
+ this(false, packageName, classLoader);
+ }
+
+ /**
+ * @deprecated NativeJavaPackage is an internal class, do not use
+ * it directly.
+ */
+ public NativeJavaPackage(String packageName) {
+ this(false, packageName,
+ Context.getCurrentContext().getApplicationClassLoader());
+ }
+
+ @Override
+ public String getClassName() {
+ return "JavaPackage";
+ }
+
+ @Override
+ public boolean has(String id, Scriptable start) {
+ return true;
+ }
+
+ @Override
+ public boolean has(int index, Scriptable start) {
+ return false;
+ }
+
+ @Override
+ public void put(String id, Scriptable start, Object value) {
+ // Can't add properties to Java packages. Sorry.
+ }
+
+ @Override
+ public void put(int index, Scriptable start, Object value) {
+ throw Context.reportRuntimeError0("msg.pkg.int");
+ }
+
+ @Override
+ public Object get(String id, Scriptable start) {
+ return getPkgProperty(id, start, true);
+ }
+
+ @Override
+ public Object get(int index, Scriptable start) {
+ return NOT_FOUND;
+ }
+
+ // set up a name which is known to be a package so we don't
+ // need to look for a class by that name
+ NativeJavaPackage forcePackage(String name, Scriptable scope)
+ {
+ Object cached = super.get(name, this);
+ if (cached != null && cached instanceof NativeJavaPackage) {
+ return (NativeJavaPackage) cached;
+ } else {
+ String newPackage = packageName.length() == 0
+ ? name
+ : packageName + "." + name;
+ NativeJavaPackage pkg = new NativeJavaPackage(true, newPackage, classLoader);
+ ScriptRuntime.setObjectProtoAndParent(pkg, scope);
+ super.put(name, this, pkg);
+ return pkg;
+ }
+ }
+
+ synchronized Object getPkgProperty(String name, Scriptable start,
+ boolean createPkg)
+ {
+ Object cached = super.get(name, start);
+ if (cached != NOT_FOUND)
+ return cached;
+ if (negativeCache != null && negativeCache.contains(name)) {
+ // Performance optimization: see bug 421071
+ return null;
+ }
+
+ String className = (packageName.length() == 0)
+ ? name : packageName + '.' + name;
+ Context cx = Context.getContext();
+ ClassShutter shutter = cx.getClassShutter();
+ Scriptable newValue = null;
+ if (shutter == null || shutter.visibleToScripts(className)) {
+ Class<?> cl = null;
+ if (classLoader != null) {
+ cl = Kit.classOrNull(classLoader, className);
+ } else {
+ cl = Kit.classOrNull(className);
+ }
+ if (cl != null) {
+ newValue = new NativeJavaClass(getTopLevelScope(this), cl);
+ newValue.setPrototype(getPrototype());
+ }
+ }
+ if (newValue == null) {
+ if (createPkg) {
+ NativeJavaPackage pkg;
+ pkg = new NativeJavaPackage(true, className, classLoader);
+ ScriptRuntime.setObjectProtoAndParent(pkg, getParentScope());
+ newValue = pkg;
+ } else {
+ // add to negative cache
+ if (negativeCache == null)
+ negativeCache = new HashSet<String>();
+ negativeCache.add(name);
+ }
+ }
+ if (newValue != null) {
+ // Make it available for fast lookup and sharing of
+ // lazily-reflected constructors and static members.
+ super.put(name, start, newValue);
+ }
+ return newValue;
+ }
+
+ @Override
+ public Object getDefaultValue(Class<?> ignored) {
+ return toString();
+ }
+
+ @Override
+ public String toString() {
+ return "[JavaPackage " + packageName + "]";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if(obj instanceof NativeJavaPackage) {
+ NativeJavaPackage njp = (NativeJavaPackage)obj;
+ return packageName.equals(njp.packageName) &&
+ classLoader == njp.classLoader;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return packageName.hashCode() ^
+ (classLoader == null ? 0 : classLoader.hashCode());
+ }
+
+ private String packageName;
+ private ClassLoader classLoader;
+ private Set<String> negativeCache = null;
+}
diff --git a/src/org/mozilla/javascript/NativeJavaTopPackage.java b/src/org/mozilla/javascript/NativeJavaTopPackage.java
new file mode 100644
index 0000000..3095363
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeJavaTopPackage.java
@@ -0,0 +1,186 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Frank Mitchell
+ * Mike Shaver
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * This class reflects Java packages into the JavaScript environment. We
+ * lazily reflect classes and subpackages, and use a caching/sharing
+ * system to ensure that members reflected into one JavaPackage appear
+ * in all other references to the same package (as with Packages.java.lang
+ * and java.lang).
+ *
+ * @author Mike Shaver
+ * @see NativeJavaArray
+ * @see NativeJavaObject
+ * @see NativeJavaClass
+ */
+
+public class NativeJavaTopPackage
+ extends NativeJavaPackage implements Function, IdFunctionCall
+{
+ static final long serialVersionUID = -1455787259477709999L;
+
+ // we know these are packages so we can skip the class check
+ // note that this is ok even if the package isn't present.
+ private static final String[][] commonPackages = {
+ {"java", "lang", "reflect"},
+ {"java", "io"},
+ {"java", "math"},
+ {"java", "net"},
+ {"java", "util", "zip"},
+ {"java", "text", "resources"},
+ {"java", "applet"},
+ {"javax", "swing"}
+ };
+
+ NativeJavaTopPackage(ClassLoader loader)
+ {
+ super(true, "", loader);
+ }
+
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ return construct(cx, scope, args);
+ }
+
+ public Scriptable construct(Context cx, Scriptable scope, Object[] args)
+ {
+ ClassLoader loader = null;
+ if (args.length != 0) {
+ Object arg = args[0];
+ if (arg instanceof Wrapper) {
+ arg = ((Wrapper)arg).unwrap();
+ }
+ if (arg instanceof ClassLoader) {
+ loader = (ClassLoader)arg;
+ }
+ }
+ if (loader == null) {
+ Context.reportRuntimeError0("msg.not.classloader");
+ return null;
+ }
+ return new NativeJavaPackage(true, "", loader);
+ }
+
+ public static void init(Context cx, Scriptable scope, boolean sealed)
+ {
+ ClassLoader loader = cx.getApplicationClassLoader();
+ final NativeJavaTopPackage top = new NativeJavaTopPackage(loader);
+ top.setPrototype(getObjectPrototype(scope));
+ top.setParentScope(scope);
+
+ for (int i = 0; i != commonPackages.length; i++) {
+ NativeJavaPackage parent = top;
+ for (int j = 0; j != commonPackages[i].length; j++) {
+ parent = parent.forcePackage(commonPackages[i][j], scope);
+ }
+ }
+
+ // getClass implementation
+ IdFunctionObject getClass = new IdFunctionObject(top, FTAG, Id_getClass,
+ "getClass", 1, scope);
+
+ // We want to get a real alias, and not a distinct JavaPackage
+ // with the same packageName, so that we share classes and top
+ // that are underneath.
+ String[] topNames = { "java", "javax", "org", "com", "edu", "net" };
+ NativeJavaPackage[] topPackages = new NativeJavaPackage[topNames.length];
+ for (int i=0; i < topNames.length; i++) {
+ topPackages[i] = (NativeJavaPackage)top.get(topNames[i], top);
+ }
+
+ // It's safe to downcast here since initStandardObjects takes
+ // a ScriptableObject.
+ ScriptableObject global = (ScriptableObject) scope;
+
+ if (sealed) {
+ getClass.sealObject();
+ }
+ getClass.exportAsScopeProperty();
+ global.defineProperty("Packages", top, ScriptableObject.DONTENUM);
+ for (int i=0; i < topNames.length; i++) {
+ global.defineProperty(topNames[i], topPackages[i],
+ ScriptableObject.DONTENUM);
+ }
+ }
+
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (f.hasTag(FTAG)) {
+ if (f.methodId() == Id_getClass) {
+ return js_getClass(cx, scope, args);
+ }
+ }
+ throw f.unknown();
+ }
+
+ private Scriptable js_getClass(Context cx, Scriptable scope, Object[] args)
+ {
+ if (args.length > 0 && args[0] instanceof Wrapper) {
+ Scriptable result = this;
+ Class<?> cl = ((Wrapper) args[0]).unwrap().getClass();
+ // Evaluate the class name by getting successive properties of
+ // the string to find the appropriate NativeJavaClass object
+ String name = cl.getName();
+ int offset = 0;
+ for (;;) {
+ int index = name.indexOf('.', offset);
+ String propName = index == -1
+ ? name.substring(offset)
+ : name.substring(offset, index);
+ Object prop = result.get(propName, result);
+ if (!(prop instanceof Scriptable))
+ break; // fall through to error
+ result = (Scriptable) prop;
+ if (index == -1)
+ return result;
+ offset = index+1;
+ }
+ }
+ throw Context.reportRuntimeError0("msg.not.java.obj");
+ }
+
+ private static final Object FTAG = "JavaTopPackage";
+ private static final int Id_getClass = 1;
+}
+
diff --git a/src/org/mozilla/javascript/NativeMath.java b/src/org/mozilla/javascript/NativeMath.java
new file mode 100644
index 0000000..d91a80f
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeMath.java
@@ -0,0 +1,403 @@
+/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * This class implements the Math native object.
+ * See ECMA 15.8.
+ * @author Norris Boyd
+ */
+
+final class NativeMath extends IdScriptableObject
+{
+ static final long serialVersionUID = -8838847185801131569L;
+
+ private static final Object MATH_TAG = "Math";
+
+ static void init(Scriptable scope, boolean sealed)
+ {
+ NativeMath obj = new NativeMath();
+ obj.activatePrototypeMap(MAX_ID);
+ obj.setPrototype(getObjectPrototype(scope));
+ obj.setParentScope(scope);
+ if (sealed) { obj.sealObject(); }
+ ScriptableObject.defineProperty(scope, "Math", obj,
+ ScriptableObject.DONTENUM);
+ }
+
+ private NativeMath()
+ {
+ }
+
+ @Override
+ public String getClassName() { return "Math"; }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ if (id <= LAST_METHOD_ID) {
+ String name;
+ int arity;
+ switch (id) {
+ case Id_toSource: arity = 0; name = "toSource"; break;
+ case Id_abs: arity = 1; name = "abs"; break;
+ case Id_acos: arity = 1; name = "acos"; break;
+ case Id_asin: arity = 1; name = "asin"; break;
+ case Id_atan: arity = 1; name = "atan"; break;
+ case Id_atan2: arity = 2; name = "atan2"; break;
+ case Id_ceil: arity = 1; name = "ceil"; break;
+ case Id_cos: arity = 1; name = "cos"; break;
+ case Id_exp: arity = 1; name = "exp"; break;
+ case Id_floor: arity = 1; name = "floor"; break;
+ case Id_log: arity = 1; name = "log"; break;
+ case Id_max: arity = 2; name = "max"; break;
+ case Id_min: arity = 2; name = "min"; break;
+ case Id_pow: arity = 2; name = "pow"; break;
+ case Id_random: arity = 0; name = "random"; break;
+ case Id_round: arity = 1; name = "round"; break;
+ case Id_sin: arity = 1; name = "sin"; break;
+ case Id_sqrt: arity = 1; name = "sqrt"; break;
+ case Id_tan: arity = 1; name = "tan"; break;
+ default: throw new IllegalStateException(String.valueOf(id));
+ }
+ initPrototypeMethod(MATH_TAG, id, name, arity);
+ } else {
+ String name;
+ double x;
+ switch (id) {
+ case Id_E: x = Math.E; name = "E"; break;
+ case Id_PI: x = Math.PI; name = "PI"; break;
+ case Id_LN10: x = 2.302585092994046; name = "LN10"; break;
+ case Id_LN2: x = 0.6931471805599453; name = "LN2"; break;
+ case Id_LOG2E: x = 1.4426950408889634; name = "LOG2E"; break;
+ case Id_LOG10E: x = 0.4342944819032518; name = "LOG10E"; break;
+ case Id_SQRT1_2: x = 0.7071067811865476; name = "SQRT1_2"; break;
+ case Id_SQRT2: x = 1.4142135623730951; name = "SQRT2"; break;
+ default: throw new IllegalStateException(String.valueOf(id));
+ }
+ initPrototypeValue(id, name, ScriptRuntime.wrapNumber(x),
+ DONTENUM | READONLY | PERMANENT);
+ }
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(MATH_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ double x;
+ int methodId = f.methodId();
+ switch (methodId) {
+ case Id_toSource:
+ return "Math";
+
+ case Id_abs:
+ x = ScriptRuntime.toNumber(args, 0);
+ // abs(-0.0) should be 0.0, but -0.0 < 0.0 == false
+ x = (x == 0.0) ? 0.0 : (x < 0.0) ? -x : x;
+ break;
+
+ case Id_acos:
+ case Id_asin:
+ x = ScriptRuntime.toNumber(args, 0);
+ if (x == x && -1.0 <= x && x <= 1.0) {
+ x = (methodId == Id_acos) ? Math.acos(x) : Math.asin(x);
+ } else {
+ x = Double.NaN;
+ }
+ break;
+
+ case Id_atan:
+ x = ScriptRuntime.toNumber(args, 0);
+ x = Math.atan(x);
+ break;
+
+ case Id_atan2:
+ x = ScriptRuntime.toNumber(args, 0);
+ x = Math.atan2(x, ScriptRuntime.toNumber(args, 1));
+ break;
+
+ case Id_ceil:
+ x = ScriptRuntime.toNumber(args, 0);
+ x = Math.ceil(x);
+ break;
+
+ case Id_cos:
+ x = ScriptRuntime.toNumber(args, 0);
+ x = (x == Double.POSITIVE_INFINITY
+ || x == Double.NEGATIVE_INFINITY)
+ ? Double.NaN : Math.cos(x);
+ break;
+
+ case Id_exp:
+ x = ScriptRuntime.toNumber(args, 0);
+ x = (x == Double.POSITIVE_INFINITY) ? x
+ : (x == Double.NEGATIVE_INFINITY) ? 0.0
+ : Math.exp(x);
+ break;
+
+ case Id_floor:
+ x = ScriptRuntime.toNumber(args, 0);
+ x = Math.floor(x);
+ break;
+
+ case Id_log:
+ x = ScriptRuntime.toNumber(args, 0);
+ // Java's log(<0) = -Infinity; we need NaN
+ x = (x < 0) ? Double.NaN : Math.log(x);
+ break;
+
+ case Id_max:
+ case Id_min:
+ x = (methodId == Id_max)
+ ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
+ for (int i = 0; i != args.length; ++i) {
+ double d = ScriptRuntime.toNumber(args[i]);
+ if (d != d) {
+ x = d; // NaN
+ break;
+ }
+ if (methodId == Id_max) {
+ // if (x < d) x = d; does not work due to -0.0 >= +0.0
+ x = Math.max(x, d);
+ } else {
+ x = Math.min(x, d);
+ }
+ }
+ break;
+
+ case Id_pow:
+ x = ScriptRuntime.toNumber(args, 0);
+ x = js_pow(x, ScriptRuntime.toNumber(args, 1));
+ break;
+
+ case Id_random:
+ x = Math.random();
+ break;
+
+ case Id_round:
+ x = ScriptRuntime.toNumber(args, 0);
+ if (x == x && x != Double.POSITIVE_INFINITY
+ && x != Double.NEGATIVE_INFINITY)
+ {
+ // Round only finite x
+ long l = Math.round(x);
+ if (l != 0) {
+ x = l;
+ } else {
+ // We must propagate the sign of d into the result
+ if (x < 0.0) {
+ x = ScriptRuntime.negativeZero;
+ } else if (x != 0.0) {
+ x = 0.0;
+ }
+ }
+ }
+ break;
+
+ case Id_sin:
+ x = ScriptRuntime.toNumber(args, 0);
+ x = (x == Double.POSITIVE_INFINITY
+ || x == Double.NEGATIVE_INFINITY)
+ ? Double.NaN : Math.sin(x);
+ break;
+
+ case Id_sqrt:
+ x = ScriptRuntime.toNumber(args, 0);
+ x = Math.sqrt(x);
+ break;
+
+ case Id_tan:
+ x = ScriptRuntime.toNumber(args, 0);
+ x = Math.tan(x);
+ break;
+
+ default: throw new IllegalStateException(String.valueOf(methodId));
+ }
+ return ScriptRuntime.wrapNumber(x);
+ }
+
+ // See Ecma 15.8.2.13
+ private double js_pow(double x, double y) {
+ double result;
+ if (y != y) {
+ // y is NaN, result is always NaN
+ result = y;
+ } else if (y == 0) {
+ // Java's pow(NaN, 0) = NaN; we need 1
+ result = 1.0;
+ } else if (x == 0) {
+ // Many differences from Java's Math.pow
+ if (1 / x > 0) {
+ result = (y > 0) ? 0 : Double.POSITIVE_INFINITY;
+ } else {
+ // x is -0, need to check if y is an odd integer
+ long y_long = (long)y;
+ if (y_long == y && (y_long & 0x1) != 0) {
+ result = (y > 0) ? -0.0 : Double.NEGATIVE_INFINITY;
+ } else {
+ result = (y > 0) ? 0.0 : Double.POSITIVE_INFINITY;
+ }
+ }
+ } else {
+ result = Math.pow(x, y);
+ if (result != result) {
+ // Check for broken Java implementations that gives NaN
+ // when they should return something else
+ if (y == Double.POSITIVE_INFINITY) {
+ if (x < -1.0 || 1.0 < x) {
+ result = Double.POSITIVE_INFINITY;
+ } else if (-1.0 < x && x < 1.0) {
+ result = 0;
+ }
+ } else if (y == Double.NEGATIVE_INFINITY) {
+ if (x < -1.0 || 1.0 < x) {
+ result = 0;
+ } else if (-1.0 < x && x < 1.0) {
+ result = Double.POSITIVE_INFINITY;
+ }
+ } else if (x == Double.POSITIVE_INFINITY) {
+ result = (y > 0) ? Double.POSITIVE_INFINITY : 0.0;
+ } else if (x == Double.NEGATIVE_INFINITY) {
+ long y_long = (long)y;
+ if (y_long == y && (y_long & 0x1) != 0) {
+ // y is odd integer
+ result = (y > 0) ? Double.NEGATIVE_INFINITY : -0.0;
+ } else {
+ result = (y > 0) ? Double.POSITIVE_INFINITY : 0.0;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+// #string_id_map#
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #generated# Last update: 2004-03-17 13:51:32 CET
+ L0: { id = 0; String X = null; int c;
+ L: switch (s.length()) {
+ case 1: if (s.charAt(0)=='E') {id=Id_E; break L0;} break L;
+ case 2: if (s.charAt(0)=='P' && s.charAt(1)=='I') {id=Id_PI; break L0;} break L;
+ case 3: switch (s.charAt(0)) {
+ case 'L': if (s.charAt(2)=='2' && s.charAt(1)=='N') {id=Id_LN2; break L0;} break L;
+ case 'a': if (s.charAt(2)=='s' && s.charAt(1)=='b') {id=Id_abs; break L0;} break L;
+ case 'c': if (s.charAt(2)=='s' && s.charAt(1)=='o') {id=Id_cos; break L0;} break L;
+ case 'e': if (s.charAt(2)=='p' && s.charAt(1)=='x') {id=Id_exp; break L0;} break L;
+ case 'l': if (s.charAt(2)=='g' && s.charAt(1)=='o') {id=Id_log; break L0;} break L;
+ case 'm': c=s.charAt(2);
+ if (c=='n') { if (s.charAt(1)=='i') {id=Id_min; break L0;} }
+ else if (c=='x') { if (s.charAt(1)=='a') {id=Id_max; break L0;} }
+ break L;
+ case 'p': if (s.charAt(2)=='w' && s.charAt(1)=='o') {id=Id_pow; break L0;} break L;
+ case 's': if (s.charAt(2)=='n' && s.charAt(1)=='i') {id=Id_sin; break L0;} break L;
+ case 't': if (s.charAt(2)=='n' && s.charAt(1)=='a') {id=Id_tan; break L0;} break L;
+ } break L;
+ case 4: switch (s.charAt(1)) {
+ case 'N': X="LN10";id=Id_LN10; break L;
+ case 'c': X="acos";id=Id_acos; break L;
+ case 'e': X="ceil";id=Id_ceil; break L;
+ case 'q': X="sqrt";id=Id_sqrt; break L;
+ case 's': X="asin";id=Id_asin; break L;
+ case 't': X="atan";id=Id_atan; break L;
+ } break L;
+ case 5: switch (s.charAt(0)) {
+ case 'L': X="LOG2E";id=Id_LOG2E; break L;
+ case 'S': X="SQRT2";id=Id_SQRT2; break L;
+ case 'a': X="atan2";id=Id_atan2; break L;
+ case 'f': X="floor";id=Id_floor; break L;
+ case 'r': X="round";id=Id_round; break L;
+ } break L;
+ case 6: c=s.charAt(0);
+ if (c=='L') { X="LOG10E";id=Id_LOG10E; }
+ else if (c=='r') { X="random";id=Id_random; }
+ break L;
+ case 7: X="SQRT1_2";id=Id_SQRT1_2; break L;
+ case 8: X="toSource";id=Id_toSource; break L;
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_toSource = 1,
+ Id_abs = 2,
+ Id_acos = 3,
+ Id_asin = 4,
+ Id_atan = 5,
+ Id_atan2 = 6,
+ Id_ceil = 7,
+ Id_cos = 8,
+ Id_exp = 9,
+ Id_floor = 10,
+ Id_log = 11,
+ Id_max = 12,
+ Id_min = 13,
+ Id_pow = 14,
+ Id_random = 15,
+ Id_round = 16,
+ Id_sin = 17,
+ Id_sqrt = 18,
+ Id_tan = 19,
+
+ LAST_METHOD_ID = 19;
+
+ private static final int
+ Id_E = LAST_METHOD_ID + 1,
+ Id_PI = LAST_METHOD_ID + 2,
+ Id_LN10 = LAST_METHOD_ID + 3,
+ Id_LN2 = LAST_METHOD_ID + 4,
+ Id_LOG2E = LAST_METHOD_ID + 5,
+ Id_LOG10E = LAST_METHOD_ID + 6,
+ Id_SQRT1_2 = LAST_METHOD_ID + 7,
+ Id_SQRT2 = LAST_METHOD_ID + 8,
+
+ MAX_ID = LAST_METHOD_ID + 8;
+
+// #/string_id_map#
+}
diff --git a/src/org/mozilla/javascript/NativeNumber.java b/src/org/mozilla/javascript/NativeNumber.java
new file mode 100644
index 0000000..50ff847
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeNumber.java
@@ -0,0 +1,250 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Mike McCabe
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * This class implements the Number native object.
+ *
+ * See ECMA 15.7.
+ *
+ * @author Norris Boyd
+ */
+final class NativeNumber extends IdScriptableObject
+{
+ static final long serialVersionUID = 3504516769741512101L;
+
+ private static final Object NUMBER_TAG = "Number";
+
+ private static final int MAX_PRECISION = 100;
+
+ static void init(Scriptable scope, boolean sealed)
+ {
+ NativeNumber obj = new NativeNumber(0.0);
+ obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+ }
+
+ private NativeNumber(double number)
+ {
+ doubleValue = number;
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return "Number";
+ }
+
+ @Override
+ protected void fillConstructorProperties(IdFunctionObject ctor)
+ {
+ final int attr = ScriptableObject.DONTENUM |
+ ScriptableObject.PERMANENT |
+ ScriptableObject.READONLY;
+
+ ctor.defineProperty("NaN", ScriptRuntime.NaNobj, attr);
+ ctor.defineProperty("POSITIVE_INFINITY",
+ ScriptRuntime.wrapNumber(Double.POSITIVE_INFINITY),
+ attr);
+ ctor.defineProperty("NEGATIVE_INFINITY",
+ ScriptRuntime.wrapNumber(Double.NEGATIVE_INFINITY),
+ attr);
+ ctor.defineProperty("MAX_VALUE",
+ ScriptRuntime.wrapNumber(Double.MAX_VALUE),
+ attr);
+ ctor.defineProperty("MIN_VALUE",
+ ScriptRuntime.wrapNumber(Double.MIN_VALUE),
+ attr);
+
+ super.fillConstructorProperties(ctor);
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_constructor: arity=1; s="constructor"; break;
+ case Id_toString: arity=1; s="toString"; break;
+ case Id_toLocaleString: arity=1; s="toLocaleString"; break;
+ case Id_toSource: arity=0; s="toSource"; break;
+ case Id_valueOf: arity=0; s="valueOf"; break;
+ case Id_toFixed: arity=1; s="toFixed"; break;
+ case Id_toExponential: arity=1; s="toExponential"; break;
+ case Id_toPrecision: arity=1; s="toPrecision"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(NUMBER_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(NUMBER_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+ if (id == Id_constructor) {
+ double val = (args.length >= 1)
+ ? ScriptRuntime.toNumber(args[0]) : 0.0;
+ if (thisObj == null) {
+ // new Number(val) creates a new Number object.
+ return new NativeNumber(val);
+ }
+ // Number(val) converts val to a number value.
+ return ScriptRuntime.wrapNumber(val);
+ }
+
+ // The rest of Number.prototype methods require thisObj to be Number
+
+ if (!(thisObj instanceof NativeNumber))
+ throw incompatibleCallError(f);
+ double value = ((NativeNumber)thisObj).doubleValue;
+
+ switch (id) {
+
+ case Id_toString:
+ case Id_toLocaleString:
+ {
+ // toLocaleString is just an alias for toString for now
+ int base = (args.length == 0)
+ ? 10 : ScriptRuntime.toInt32(args[0]);
+ return ScriptRuntime.numberToString(value, base);
+ }
+
+ case Id_toSource:
+ return "(new Number("+ScriptRuntime.toString(value)+"))";
+
+ case Id_valueOf:
+ return ScriptRuntime.wrapNumber(value);
+
+ case Id_toFixed:
+ return num_to(value, args, DToA.DTOSTR_FIXED,
+ DToA.DTOSTR_FIXED, -20, 0);
+
+ case Id_toExponential:
+ return num_to(value, args, DToA.DTOSTR_STANDARD_EXPONENTIAL,
+ DToA.DTOSTR_EXPONENTIAL, 0, 1);
+
+ case Id_toPrecision:
+ return num_to(value, args, DToA.DTOSTR_STANDARD,
+ DToA.DTOSTR_PRECISION, 1, 0);
+
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ }
+
+ @Override
+ public String toString() {
+ return ScriptRuntime.numberToString(doubleValue, 10);
+ }
+
+ private static String num_to(double val,
+ Object[] args,
+ int zeroArgMode, int oneArgMode,
+ int precisionMin, int precisionOffset)
+ {
+ int precision;
+ if (args.length == 0) {
+ precision = 0;
+ oneArgMode = zeroArgMode;
+ } else {
+ /* We allow a larger range of precision than
+ ECMA requires; this is permitted by ECMA. */
+ precision = ScriptRuntime.toInt32(args[0]);
+ if (precision < precisionMin || precision > MAX_PRECISION) {
+ String msg = ScriptRuntime.getMessage1(
+ "msg.bad.precision", ScriptRuntime.toString(args[0]));
+ throw ScriptRuntime.constructError("RangeError", msg);
+ }
+ }
+ StringBuffer sb = new StringBuffer();
+ DToA.JS_dtostr(sb, oneArgMode, precision + precisionOffset, val);
+ return sb.toString();
+ }
+
+// #string_id_map#
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #generated# Last update: 2007-05-09 08:15:50 EDT
+ L0: { id = 0; String X = null; int c;
+ L: switch (s.length()) {
+ case 7: c=s.charAt(0);
+ if (c=='t') { X="toFixed";id=Id_toFixed; }
+ else if (c=='v') { X="valueOf";id=Id_valueOf; }
+ break L;
+ case 8: c=s.charAt(3);
+ if (c=='o') { X="toSource";id=Id_toSource; }
+ else if (c=='t') { X="toString";id=Id_toString; }
+ break L;
+ case 11: c=s.charAt(0);
+ if (c=='c') { X="constructor";id=Id_constructor; }
+ else if (c=='t') { X="toPrecision";id=Id_toPrecision; }
+ break L;
+ case 13: X="toExponential";id=Id_toExponential; break L;
+ case 14: X="toLocaleString";id=Id_toLocaleString; break L;
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_constructor = 1,
+ Id_toString = 2,
+ Id_toLocaleString = 3,
+ Id_toSource = 4,
+ Id_valueOf = 5,
+ Id_toFixed = 6,
+ Id_toExponential = 7,
+ Id_toPrecision = 8,
+ MAX_PROTOTYPE_ID = 8;
+
+// #/string_id_map#
+
+ private double doubleValue;
+}
diff --git a/src/org/mozilla/javascript/NativeObject.java b/src/org/mozilla/javascript/NativeObject.java
new file mode 100644
index 0000000..39f4dd3
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeObject.java
@@ -0,0 +1,321 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Bob Jervis
+ * Mike McCabe
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * This class implements the Object native object.
+ * See ECMA 15.2.
+ * @author Norris Boyd
+ */
+public class NativeObject extends IdScriptableObject
+{
+ static final long serialVersionUID = -6345305608474346996L;
+
+ private static final Object OBJECT_TAG = "Object";
+
+ static void init(Scriptable scope, boolean sealed)
+ {
+ NativeObject obj = new NativeObject();
+ obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return "Object";
+ }
+
+ @Override
+ public String toString()
+ {
+ return ScriptRuntime.defaultObjectToString(this);
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_constructor: arity=1; s="constructor"; break;
+ case Id_toString: arity=0; s="toString"; break;
+ case Id_toLocaleString: arity=0; s="toLocaleString"; break;
+ case Id_valueOf: arity=0; s="valueOf"; break;
+ case Id_hasOwnProperty: arity=1; s="hasOwnProperty"; break;
+ case Id_propertyIsEnumerable:
+ arity=1; s="propertyIsEnumerable"; break;
+ case Id_isPrototypeOf: arity=1; s="isPrototypeOf"; break;
+ case Id_toSource: arity=0; s="toSource"; break;
+ case Id___defineGetter__:
+ arity=2; s="__defineGetter__"; break;
+ case Id___defineSetter__:
+ arity=2; s="__defineSetter__"; break;
+ case Id___lookupGetter__:
+ arity=1; s="__lookupGetter__"; break;
+ case Id___lookupSetter__:
+ arity=1; s="__lookupSetter__"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(OBJECT_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(OBJECT_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+ switch (id) {
+ case Id_constructor: {
+ if (thisObj != null) {
+ // BaseFunction.construct will set up parent, proto
+ return f.construct(cx, scope, args);
+ }
+ if (args.length == 0 || args[0] == null
+ || args[0] == Undefined.instance)
+ {
+ return new NativeObject();
+ }
+ return ScriptRuntime.toObject(cx, scope, args[0]);
+ }
+
+ case Id_toLocaleString: // For now just alias toString
+ case Id_toString: {
+ if (cx.hasFeature(Context.FEATURE_TO_STRING_AS_SOURCE)) {
+ String s = ScriptRuntime.defaultObjectToSource(cx, scope,
+ thisObj, args);
+ int L = s.length();
+ if (L != 0 && s.charAt(0) == '(' && s.charAt(L - 1) == ')') {
+ // Strip () that surrounds toSource
+ s = s.substring(1, L - 1);
+ }
+ return s;
+ }
+ return ScriptRuntime.defaultObjectToString(thisObj);
+ }
+
+ case Id_valueOf:
+ return thisObj;
+
+ case Id_hasOwnProperty: {
+ boolean result;
+ if (args.length == 0) {
+ result = false;
+ } else {
+ String s = ScriptRuntime.toStringIdOrIndex(cx, args[0]);
+ if (s == null) {
+ int index = ScriptRuntime.lastIndexResult(cx);
+ result = thisObj.has(index, thisObj);
+ } else {
+ result = thisObj.has(s, thisObj);
+ }
+ }
+ return ScriptRuntime.wrapBoolean(result);
+ }
+
+ case Id_propertyIsEnumerable: {
+ boolean result;
+ if (args.length == 0) {
+ result = false;
+ } else {
+ String s = ScriptRuntime.toStringIdOrIndex(cx, args[0]);
+ if (s == null) {
+ int index = ScriptRuntime.lastIndexResult(cx);
+ result = thisObj.has(index, thisObj);
+ if (result && thisObj instanceof ScriptableObject) {
+ ScriptableObject so = (ScriptableObject)thisObj;
+ int attrs = so.getAttributes(index);
+ result = ((attrs & ScriptableObject.DONTENUM) == 0);
+ }
+ } else {
+ result = thisObj.has(s, thisObj);
+ if (result && thisObj instanceof ScriptableObject) {
+ ScriptableObject so = (ScriptableObject)thisObj;
+ int attrs = so.getAttributes(s);
+ result = ((attrs & ScriptableObject.DONTENUM) == 0);
+ }
+ }
+ }
+ return ScriptRuntime.wrapBoolean(result);
+ }
+
+ case Id_isPrototypeOf: {
+ boolean result = false;
+ if (args.length != 0 && args[0] instanceof Scriptable) {
+ Scriptable v = (Scriptable) args[0];
+ do {
+ v = v.getPrototype();
+ if (v == thisObj) {
+ result = true;
+ break;
+ }
+ } while (v != null);
+ }
+ return ScriptRuntime.wrapBoolean(result);
+ }
+
+ case Id_toSource:
+ return ScriptRuntime.defaultObjectToSource(cx, scope, thisObj,
+ args);
+ case Id___defineGetter__:
+ case Id___defineSetter__:
+ {
+ if (args.length < 2 || !(args[1] instanceof Callable)) {
+ Object badArg = (args.length >= 2 ? args[1]
+ : Undefined.instance);
+ throw ScriptRuntime.notFunctionError(badArg);
+ }
+ if (!(thisObj instanceof ScriptableObject)) {
+ throw Context.reportRuntimeError2(
+ "msg.extend.scriptable",
+ thisObj.getClass().getName(),
+ String.valueOf(args[0]));
+ }
+ ScriptableObject so = (ScriptableObject)thisObj;
+ String name = ScriptRuntime.toStringIdOrIndex(cx, args[0]);
+ int index = (name != null ? 0
+ : ScriptRuntime.lastIndexResult(cx));
+ Callable getterOrSetter = (Callable)args[1];
+ boolean isSetter = (id == Id___defineSetter__);
+ so.setGetterOrSetter(name, index, getterOrSetter, isSetter);
+ if (so instanceof NativeArray)
+ ((NativeArray)so).setDenseOnly(false);
+ }
+ return Undefined.instance;
+
+ case Id___lookupGetter__:
+ case Id___lookupSetter__:
+ {
+ if (args.length < 1 ||
+ !(thisObj instanceof ScriptableObject))
+ return Undefined.instance;
+
+ ScriptableObject so = (ScriptableObject)thisObj;
+ String name = ScriptRuntime.toStringIdOrIndex(cx, args[0]);
+ int index = (name != null ? 0
+ : ScriptRuntime.lastIndexResult(cx));
+ boolean isSetter = (id == Id___lookupSetter__);
+ Object gs;
+ for (;;) {
+ gs = so.getGetterOrSetter(name, index, isSetter);
+ if (gs != null)
+ break;
+ // If there is no getter or setter for the object itself,
+ // how about the prototype?
+ Scriptable v = so.getPrototype();
+ if (v == null)
+ break;
+ if (v instanceof ScriptableObject)
+ so = (ScriptableObject)v;
+ else
+ break;
+ }
+ if (gs != null)
+ return gs;
+ }
+ return Undefined.instance;
+
+ default:
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+ }
+
+// #string_id_map#
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #generated# Last update: 2007-05-09 08:15:55 EDT
+ L0: { id = 0; String X = null; int c;
+ L: switch (s.length()) {
+ case 7: X="valueOf";id=Id_valueOf; break L;
+ case 8: c=s.charAt(3);
+ if (c=='o') { X="toSource";id=Id_toSource; }
+ else if (c=='t') { X="toString";id=Id_toString; }
+ break L;
+ case 11: X="constructor";id=Id_constructor; break L;
+ case 13: X="isPrototypeOf";id=Id_isPrototypeOf; break L;
+ case 14: c=s.charAt(0);
+ if (c=='h') { X="hasOwnProperty";id=Id_hasOwnProperty; }
+ else if (c=='t') { X="toLocaleString";id=Id_toLocaleString; }
+ break L;
+ case 16: c=s.charAt(2);
+ if (c=='d') {
+ c=s.charAt(8);
+ if (c=='G') { X="__defineGetter__";id=Id___defineGetter__; }
+ else if (c=='S') { X="__defineSetter__";id=Id___defineSetter__; }
+ }
+ else if (c=='l') {
+ c=s.charAt(8);
+ if (c=='G') { X="__lookupGetter__";id=Id___lookupGetter__; }
+ else if (c=='S') { X="__lookupSetter__";id=Id___lookupSetter__; }
+ }
+ break L;
+ case 20: X="propertyIsEnumerable";id=Id_propertyIsEnumerable; break L;
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_constructor = 1,
+ Id_toString = 2,
+ Id_toLocaleString = 3,
+ Id_valueOf = 4,
+ Id_hasOwnProperty = 5,
+ Id_propertyIsEnumerable = 6,
+ Id_isPrototypeOf = 7,
+ Id_toSource = 8,
+ Id___defineGetter__ = 9,
+ Id___defineSetter__ = 10,
+ Id___lookupGetter__ = 11,
+ Id___lookupSetter__ = 12,
+ MAX_PROTOTYPE_ID = 12;
+
+// #/string_id_map#
+}
diff --git a/src/org/mozilla/javascript/NativeScript.java b/src/org/mozilla/javascript/NativeScript.java
new file mode 100644
index 0000000..ec1e559
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeScript.java
@@ -0,0 +1,230 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Roger Lawrence
+ * Mike McCabe
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * The JavaScript Script object.
+ *
+ * Note that the C version of the engine uses XDR as the format used
+ * by freeze and thaw. Since this depends on the internal format of
+ * structures in the C runtime, we cannot duplicate it.
+ *
+ * Since we cannot replace 'this' as a result of the compile method,
+ * will forward requests to execute to the nonnull 'script' field.
+ *
+ * @since 1.3
+ * @author Norris Boyd
+ */
+
+class NativeScript extends BaseFunction
+{
+ static final long serialVersionUID = -6795101161980121700L;
+
+ private static final Object SCRIPT_TAG = "Script";
+
+ static void init(Scriptable scope, boolean sealed)
+ {
+ NativeScript obj = new NativeScript(null);
+ obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+ }
+
+ private NativeScript(Script script)
+ {
+ this.script = script;
+ }
+
+ /**
+ * Returns the name of this JavaScript class, "Script".
+ */
+ @Override
+ public String getClassName()
+ {
+ return "Script";
+ }
+
+ @Override
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ if (script != null) {
+ return script.exec(cx, scope);
+ }
+ return Undefined.instance;
+ }
+
+ @Override
+ public Scriptable construct(Context cx, Scriptable scope, Object[] args)
+ {
+ throw Context.reportRuntimeError0("msg.script.is.not.constructor");
+ }
+
+ @Override
+ public int getLength()
+ {
+ return 0;
+ }
+
+ @Override
+ public int getArity()
+ {
+ return 0;
+ }
+
+ @Override
+ String decompile(int indent, int flags)
+ {
+ if (script instanceof NativeFunction) {
+ return ((NativeFunction)script).decompile(indent, flags);
+ }
+ return super.decompile(indent, flags);
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_constructor: arity=1; s="constructor"; break;
+ case Id_toString: arity=0; s="toString"; break;
+ case Id_exec: arity=0; s="exec"; break;
+ case Id_compile: arity=1; s="compile"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(SCRIPT_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(SCRIPT_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+ switch (id) {
+ case Id_constructor: {
+ String source = (args.length == 0)
+ ? ""
+ : ScriptRuntime.toString(args[0]);
+ Script script = compile(cx, source);
+ NativeScript nscript = new NativeScript(script);
+ ScriptRuntime.setObjectProtoAndParent(nscript, scope);
+ return nscript;
+ }
+
+ case Id_toString: {
+ NativeScript real = realThis(thisObj, f);
+ Script realScript = real.script;
+ if (realScript == null) { return ""; }
+ return cx.decompileScript(realScript, 0);
+ }
+
+ case Id_exec: {
+ throw Context.reportRuntimeError1(
+ "msg.cant.call.indirect", "exec");
+ }
+
+ case Id_compile: {
+ NativeScript real = realThis(thisObj, f);
+ String source = ScriptRuntime.toString(args, 0);
+ real.script = compile(cx, source);
+ return real;
+ }
+ }
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+
+ private static NativeScript realThis(Scriptable thisObj, IdFunctionObject f)
+ {
+ if (!(thisObj instanceof NativeScript))
+ throw incompatibleCallError(f);
+ return (NativeScript)thisObj;
+ }
+
+ private static Script compile(Context cx, String source)
+ {
+ int[] linep = { 0 };
+ String filename = Context.getSourcePositionFromStack(linep);
+ if (filename == null) {
+ filename = "<Script object>";
+ linep[0] = 1;
+ }
+ ErrorReporter reporter;
+ reporter = DefaultErrorReporter.forEval(cx.getErrorReporter());
+ return cx.compileString(source, null, reporter, filename,
+ linep[0], null);
+ }
+
+// #string_id_map#
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #generated# Last update: 2007-05-09 08:16:01 EDT
+ L0: { id = 0; String X = null;
+ L: switch (s.length()) {
+ case 4: X="exec";id=Id_exec; break L;
+ case 7: X="compile";id=Id_compile; break L;
+ case 8: X="toString";id=Id_toString; break L;
+ case 11: X="constructor";id=Id_constructor; break L;
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_constructor = 1,
+ Id_toString = 2,
+ Id_compile = 3,
+ Id_exec = 4,
+ MAX_PROTOTYPE_ID = 4;
+
+// #/string_id_map#
+
+ private Script script;
+}
+
diff --git a/src/org/mozilla/javascript/NativeString.java b/src/org/mozilla/javascript/NativeString.java
new file mode 100644
index 0000000..1734807
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeString.java
@@ -0,0 +1,995 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Tom Beauvais
+ * Norris Boyd
+ * Mike McCabe
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.text.Collator;
+
+/**
+ * This class implements the String native object.
+ *
+ * See ECMA 15.5.
+ *
+ * String methods for dealing with regular expressions are
+ * ported directly from C. Latest port is from version 1.40.12.19
+ * in the JSFUN13_BRANCH.
+ *
+ * @author Mike McCabe
+ * @author Norris Boyd
+ */
+final class NativeString extends IdScriptableObject
+{
+ static final long serialVersionUID = 920268368584188687L;
+
+ private static final Object STRING_TAG = "String";
+
+ static void init(Scriptable scope, boolean sealed)
+ {
+ NativeString obj = new NativeString("");
+ obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
+ }
+
+ private NativeString(String s) {
+ string = s;
+ }
+
+ @Override
+ public String getClassName() {
+ return "String";
+ }
+
+ private static final int
+ Id_length = 1,
+ MAX_INSTANCE_ID = 1;
+
+ @Override
+ protected int getMaxInstanceId()
+ {
+ return MAX_INSTANCE_ID;
+ }
+
+ @Override
+ protected int findInstanceIdInfo(String s)
+ {
+ if (s.equals("length")) {
+ return instanceIdInfo(DONTENUM | READONLY | PERMANENT, Id_length);
+ }
+ return super.findInstanceIdInfo(s);
+ }
+
+ @Override
+ protected String getInstanceIdName(int id)
+ {
+ if (id == Id_length) { return "length"; }
+ return super.getInstanceIdName(id);
+ }
+
+ @Override
+ protected Object getInstanceIdValue(int id)
+ {
+ if (id == Id_length) {
+ return ScriptRuntime.wrapInt(string.length());
+ }
+ return super.getInstanceIdValue(id);
+ }
+
+ @Override
+ protected void fillConstructorProperties(IdFunctionObject ctor)
+ {
+ addIdFunctionProperty(ctor, STRING_TAG, ConstructorId_fromCharCode,
+ "fromCharCode", 1);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_charAt, "charAt", 2);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_charCodeAt, "charCodeAt", 2);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_indexOf, "indexOf", 2);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_lastIndexOf, "lastIndexOf", 2);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_split, "split", 3);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_substring, "substring", 3);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_toLowerCase, "toLowerCase", 1);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_toUpperCase, "toUpperCase", 1);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_substr, "substr", 3);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_concat, "concat", 2);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_slice, "slice", 3);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_equalsIgnoreCase, "equalsIgnoreCase", 2);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_match, "match", 2);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_search, "search", 2);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_replace, "replace", 2);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_localeCompare, "localeCompare", 2);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_toLocaleLowerCase, "toLocaleLowerCase", 1);
+ addIdFunctionProperty(ctor, STRING_TAG,
+ ConstructorId_fromCharCode, "fromCharCode", 1);
+ super.fillConstructorProperties(ctor);
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_constructor: arity=1; s="constructor"; break;
+ case Id_toString: arity=0; s="toString"; break;
+ case Id_toSource: arity=0; s="toSource"; break;
+ case Id_valueOf: arity=0; s="valueOf"; break;
+ case Id_charAt: arity=1; s="charAt"; break;
+ case Id_charCodeAt: arity=1; s="charCodeAt"; break;
+ case Id_indexOf: arity=1; s="indexOf"; break;
+ case Id_lastIndexOf: arity=1; s="lastIndexOf"; break;
+ case Id_split: arity=2; s="split"; break;
+ case Id_substring: arity=2; s="substring"; break;
+ case Id_toLowerCase: arity=0; s="toLowerCase"; break;
+ case Id_toUpperCase: arity=0; s="toUpperCase"; break;
+ case Id_substr: arity=2; s="substr"; break;
+ case Id_concat: arity=1; s="concat"; break;
+ case Id_slice: arity=2; s="slice"; break;
+ case Id_bold: arity=0; s="bold"; break;
+ case Id_italics: arity=0; s="italics"; break;
+ case Id_fixed: arity=0; s="fixed"; break;
+ case Id_strike: arity=0; s="strike"; break;
+ case Id_small: arity=0; s="small"; break;
+ case Id_big: arity=0; s="big"; break;
+ case Id_blink: arity=0; s="blink"; break;
+ case Id_sup: arity=0; s="sup"; break;
+ case Id_sub: arity=0; s="sub"; break;
+ case Id_fontsize: arity=0; s="fontsize"; break;
+ case Id_fontcolor: arity=0; s="fontcolor"; break;
+ case Id_link: arity=0; s="link"; break;
+ case Id_anchor: arity=0; s="anchor"; break;
+ case Id_equals: arity=1; s="equals"; break;
+ case Id_equalsIgnoreCase: arity=1; s="equalsIgnoreCase"; break;
+ case Id_match: arity=1; s="match"; break;
+ case Id_search: arity=1; s="search"; break;
+ case Id_replace: arity=1; s="replace"; break;
+ case Id_localeCompare: arity=1; s="localeCompare"; break;
+ case Id_toLocaleLowerCase: arity=0; s="toLocaleLowerCase"; break;
+ case Id_toLocaleUpperCase: arity=0; s="toLocaleUpperCase"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(STRING_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(STRING_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+ again:
+ for(;;) {
+ switch (id) {
+ case ConstructorId_charAt:
+ case ConstructorId_charCodeAt:
+ case ConstructorId_indexOf:
+ case ConstructorId_lastIndexOf:
+ case ConstructorId_split:
+ case ConstructorId_substring:
+ case ConstructorId_toLowerCase:
+ case ConstructorId_toUpperCase:
+ case ConstructorId_substr:
+ case ConstructorId_concat:
+ case ConstructorId_slice:
+ case ConstructorId_equalsIgnoreCase:
+ case ConstructorId_match:
+ case ConstructorId_search:
+ case ConstructorId_replace:
+ case ConstructorId_localeCompare:
+ case ConstructorId_toLocaleLowerCase: {
+ thisObj = ScriptRuntime.toObject(scope,
+ ScriptRuntime.toString(args[0]));
+ Object[] newArgs = new Object[args.length-1];
+ for (int i=0; i < newArgs.length; i++)
+ newArgs[i] = args[i+1];
+ args = newArgs;
+ id = -id;
+ continue again;
+ }
+
+ case ConstructorId_fromCharCode: {
+ int N = args.length;
+ if (N < 1)
+ return "";
+ StringBuffer sb = new StringBuffer(N);
+ for (int i = 0; i != N; ++i) {
+ sb.append(ScriptRuntime.toUint16(args[i]));
+ }
+ return sb.toString();
+ }
+
+ case Id_constructor: {
+ String s = (args.length >= 1)
+ ? ScriptRuntime.toString(args[0]) : "";
+ if (thisObj == null) {
+ // new String(val) creates a new String object.
+ return new NativeString(s);
+ }
+ // String(val) converts val to a string value.
+ return s;
+ }
+
+ case Id_toString:
+ case Id_valueOf:
+ // ECMA 15.5.4.2: 'the toString function is not generic.
+ return realThis(thisObj, f).string;
+
+ case Id_toSource: {
+ String s = realThis(thisObj, f).string;
+ return "(new String(\""+ScriptRuntime.escapeString(s)+"\"))";
+ }
+
+ case Id_charAt:
+ case Id_charCodeAt: {
+ // See ECMA 15.5.4.[4,5]
+ String target = ScriptRuntime.toString(thisObj);
+ double pos = ScriptRuntime.toInteger(args, 0);
+ if (pos < 0 || pos >= target.length()) {
+ if (id == Id_charAt) return "";
+ else return ScriptRuntime.NaNobj;
+ }
+ char c = target.charAt((int)pos);
+ if (id == Id_charAt) return String.valueOf(c);
+ else return ScriptRuntime.wrapInt(c);
+ }
+
+ case Id_indexOf:
+ return ScriptRuntime.wrapInt(js_indexOf(
+ ScriptRuntime.toString(thisObj), args));
+
+ case Id_lastIndexOf:
+ return ScriptRuntime.wrapInt(js_lastIndexOf(
+ ScriptRuntime.toString(thisObj), args));
+
+ case Id_split:
+ return js_split(cx, scope, ScriptRuntime.toString(thisObj),
+ args);
+
+ case Id_substring:
+ return js_substring(cx, ScriptRuntime.toString(thisObj), args);
+
+ case Id_toLowerCase:
+ // See ECMA 15.5.4.11
+ return ScriptRuntime.toString(thisObj).toLowerCase();
+
+ case Id_toUpperCase:
+ // See ECMA 15.5.4.12
+ return ScriptRuntime.toString(thisObj).toUpperCase();
+
+ case Id_substr:
+ return js_substr(ScriptRuntime.toString(thisObj), args);
+
+ case Id_concat:
+ return js_concat(ScriptRuntime.toString(thisObj), args);
+
+ case Id_slice:
+ return js_slice(ScriptRuntime.toString(thisObj), args);
+
+ case Id_bold:
+ return tagify(thisObj, "b", null, null);
+
+ case Id_italics:
+ return tagify(thisObj, "i", null, null);
+
+ case Id_fixed:
+ return tagify(thisObj, "tt", null, null);
+
+ case Id_strike:
+ return tagify(thisObj, "strike", null, null);
+
+ case Id_small:
+ return tagify(thisObj, "small", null, null);
+
+ case Id_big:
+ return tagify(thisObj, "big", null, null);
+
+ case Id_blink:
+ return tagify(thisObj, "blink", null, null);
+
+ case Id_sup:
+ return tagify(thisObj, "sup", null, null);
+
+ case Id_sub:
+ return tagify(thisObj, "sub", null, null);
+
+ case Id_fontsize:
+ return tagify(thisObj, "font", "size", args);
+
+ case Id_fontcolor:
+ return tagify(thisObj, "font", "color", args);
+
+ case Id_link:
+ return tagify(thisObj, "a", "href", args);
+
+ case Id_anchor:
+ return tagify(thisObj, "a", "name", args);
+
+ case Id_equals:
+ case Id_equalsIgnoreCase: {
+ String s1 = ScriptRuntime.toString(thisObj);
+ String s2 = ScriptRuntime.toString(args, 0);
+ return ScriptRuntime.wrapBoolean(
+ (id == Id_equals) ? s1.equals(s2)
+ : s1.equalsIgnoreCase(s2));
+ }
+
+ case Id_match:
+ case Id_search:
+ case Id_replace:
+ {
+ int actionType;
+ if (id == Id_match) {
+ actionType = RegExpProxy.RA_MATCH;
+ } else if (id == Id_search) {
+ actionType = RegExpProxy.RA_SEARCH;
+ } else {
+ actionType = RegExpProxy.RA_REPLACE;
+ }
+ return ScriptRuntime.checkRegExpProxy(cx).
+ action(cx, scope, thisObj, args, actionType);
+ }
+ // ECMA-262 1 5.5.4.9
+ case Id_localeCompare:
+ {
+ // For now, create and configure a collator instance. I can't
+ // actually imagine that this'd be slower than caching them
+ // a la ClassCache, so we aren't trying to outsmart ourselves
+ // with a caching mechanism for now.
+ Collator collator = Collator.getInstance(cx.getLocale());
+ collator.setStrength(Collator.IDENTICAL);
+ collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
+ return ScriptRuntime.wrapNumber(collator.compare(
+ ScriptRuntime.toString(thisObj),
+ ScriptRuntime.toString(args, 0)));
+ }
+ case Id_toLocaleLowerCase:
+ {
+ return ScriptRuntime.toString(thisObj)
+ .toLowerCase(cx.getLocale());
+ }
+ case Id_toLocaleUpperCase:
+ {
+ return ScriptRuntime.toString(thisObj)
+ .toUpperCase(cx.getLocale());
+ }
+ }
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+ }
+
+ private static NativeString realThis(Scriptable thisObj, IdFunctionObject f)
+ {
+ if (!(thisObj instanceof NativeString))
+ throw incompatibleCallError(f);
+ return (NativeString)thisObj;
+ }
+
+ /*
+ * HTML composition aids.
+ */
+ private static String tagify(Object thisObj, String tag,
+ String attribute, Object[] args)
+ {
+ String str = ScriptRuntime.toString(thisObj);
+ StringBuffer result = new StringBuffer();
+ result.append('<');
+ result.append(tag);
+ if (attribute != null) {
+ result.append(' ');
+ result.append(attribute);
+ result.append("=\"");
+ result.append(ScriptRuntime.toString(args, 0));
+ result.append('"');
+ }
+ result.append('>');
+ result.append(str);
+ result.append("</");
+ result.append(tag);
+ result.append('>');
+ return result.toString();
+ }
+
+ @Override
+ public String toString() {
+ return string;
+ }
+
+ /* Make array-style property lookup work for strings.
+ * XXX is this ECMA? A version check is probably needed. In js too.
+ */
+ @Override
+ public Object get(int index, Scriptable start) {
+ if (0 <= index && index < string.length()) {
+ return string.substring(index, index + 1);
+ }
+ return super.get(index, start);
+ }
+
+ @Override
+ public void put(int index, Scriptable start, Object value) {
+ if (0 <= index && index < string.length()) {
+ return;
+ }
+ super.put(index, start, value);
+ }
+
+ /*
+ *
+ * See ECMA 15.5.4.6. Uses Java String.indexOf()
+ * OPT to add - BMH searching from jsstr.c.
+ */
+ private static int js_indexOf(String target, Object[] args) {
+ String search = ScriptRuntime.toString(args, 0);
+ double begin = ScriptRuntime.toInteger(args, 1);
+
+ if (begin > target.length()) {
+ return -1;
+ } else {
+ if (begin < 0)
+ begin = 0;
+ return target.indexOf(search, (int)begin);
+ }
+ }
+
+ /*
+ *
+ * See ECMA 15.5.4.7
+ *
+ */
+ private static int js_lastIndexOf(String target, Object[] args) {
+ String search = ScriptRuntime.toString(args, 0);
+ double end = ScriptRuntime.toNumber(args, 1);
+
+ if (end != end || end > target.length())
+ end = target.length();
+ else if (end < 0)
+ end = 0;
+
+ return target.lastIndexOf(search, (int)end);
+ }
+
+ /*
+ * Used by js_split to find the next split point in target,
+ * starting at offset ip and looking either for the given
+ * separator substring, or for the next re match. ip and
+ * matchlen must be reference variables (assumed to be arrays of
+ * length 1) so they can be updated in the leading whitespace or
+ * re case.
+ *
+ * Return -1 on end of string, >= 0 for a valid index of the next
+ * separator occurrence if found, or the string length if no
+ * separator is found.
+ */
+ private static int find_split(Context cx, Scriptable scope, String target,
+ String separator, int version,
+ RegExpProxy reProxy, Scriptable re,
+ int[] ip, int[] matchlen, boolean[] matched,
+ String[][] parensp)
+ {
+ int i = ip[0];
+ int length = target.length();
+
+ /*
+ * Perl4 special case for str.split(' '), only if the user has selected
+ * JavaScript1.2 explicitly. Split on whitespace, and skip leading w/s.
+ * Strange but true, apparently modeled after awk.
+ */
+ if (version == Context.VERSION_1_2 &&
+ re == null && separator.length() == 1 && separator.charAt(0) == ' ')
+ {
+ /* Skip leading whitespace if at front of str. */
+ if (i == 0) {
+ while (i < length && Character.isWhitespace(target.charAt(i)))
+ i++;
+ ip[0] = i;
+ }
+
+ /* Don't delimit whitespace at end of string. */
+ if (i == length)
+ return -1;
+
+ /* Skip over the non-whitespace chars. */
+ while (i < length
+ && !Character.isWhitespace(target.charAt(i)))
+ i++;
+
+ /* Now skip the next run of whitespace. */
+ int j = i;
+ while (j < length && Character.isWhitespace(target.charAt(j)))
+ j++;
+
+ /* Update matchlen to count delimiter chars. */
+ matchlen[0] = j - i;
+ return i;
+ }
+
+ /*
+ * Stop if past end of string. If at end of string, we will
+ * return target length, so that
+ *
+ * "ab,".split(',') => new Array("ab", "")
+ *
+ * and the resulting array converts back to the string "ab,"
+ * for symmetry. NB: This differs from perl, which drops the
+ * trailing empty substring if the LIMIT argument is omitted.
+ */
+ if (i > length)
+ return -1;
+
+ /*
+ * Match a regular expression against the separator at or
+ * above index i. Return -1 at end of string instead of
+ * trying for a match, so we don't get stuck in a loop.
+ */
+ if (re != null) {
+ return reProxy.find_split(cx, scope, target, separator, re,
+ ip, matchlen, matched, parensp);
+ }
+
+ /*
+ * Deviate from ECMA by never splitting an empty string by any separator
+ * string into a non-empty array (an array of length 1 that contains the
+ * empty string).
+ */
+ if (version != Context.VERSION_DEFAULT && version < Context.VERSION_1_3
+ && length == 0)
+ return -1;
+
+ /*
+ * Special case: if sep is the empty string, split str into
+ * one character substrings. Let our caller worry about
+ * whether to split once at end of string into an empty
+ * substring.
+ *
+ * For 1.2 compatibility, at the end of the string, we return the length as
+ * the result, and set the separator length to 1 -- this allows the caller
+ * to include an additional null string at the end of the substring list.
+ */
+ if (separator.length() == 0) {
+ if (version == Context.VERSION_1_2) {
+ if (i == length) {
+ matchlen[0] = 1;
+ return i;
+ }
+ return i + 1;
+ }
+ return (i == length) ? -1 : i + 1;
+ }
+
+ /* Punt to j.l.s.indexOf; return target length if separator is
+ * not found.
+ */
+ if (ip[0] >= length)
+ return length;
+
+ i = target.indexOf(separator, ip[0]);
+
+ return (i != -1) ? i : length;
+ }
+
+ /*
+ * See ECMA 15.5.4.8. Modified to match JS 1.2 - optionally takes
+ * a limit argument and accepts a regular expression as the split
+ * argument.
+ */
+ private static Object js_split(Context cx, Scriptable scope,
+ String target, Object[] args)
+ {
+ // create an empty Array to return;
+ Scriptable top = getTopLevelScope(scope);
+ Scriptable result = ScriptRuntime.newObject(cx, top, "Array", null);
+
+ // return an array consisting of the target if no separator given
+ // don't check against undefined, because we want
+ // 'fooundefinedbar'.split(void 0) to split to ['foo', 'bar']
+ if (args.length < 1) {
+ result.put(0, result, target);
+ return result;
+ }
+
+ // Use the second argument as the split limit, if given.
+ boolean limited = (args.length > 1) && (args[1] != Undefined.instance);
+ long limit = 0; // Initialize to avoid warning.
+ if (limited) {
+ /* Clamp limit between 0 and 1 + string length. */
+ limit = ScriptRuntime.toUint32(args[1]);
+ if (limit > target.length())
+ limit = 1 + target.length();
+ }
+
+ String separator = null;
+ int[] matchlen = new int[1];
+ Scriptable re = null;
+ RegExpProxy reProxy = null;
+ if (args[0] instanceof Scriptable) {
+ reProxy = ScriptRuntime.getRegExpProxy(cx);
+ if (reProxy != null) {
+ Scriptable test = (Scriptable)args[0];
+ if (reProxy.isRegExp(test)) {
+ re = test;
+ }
+ }
+ }
+ if (re == null) {
+ separator = ScriptRuntime.toString(args[0]);
+ matchlen[0] = separator.length();
+ }
+
+ // split target with separator or re
+ int[] ip = { 0 };
+ int match;
+ int len = 0;
+ boolean[] matched = { false };
+ String[][] parens = { null };
+ int version = cx.getLanguageVersion();
+ while ((match = find_split(cx, scope, target, separator, version,
+ reProxy, re, ip, matchlen, matched, parens))
+ >= 0)
+ {
+ if ((limited && len >= limit) || (match > target.length()))
+ break;
+
+ String substr;
+ if (target.length() == 0)
+ substr = target;
+ else
+ substr = target.substring(ip[0], match);
+
+ result.put(len, result, substr);
+ len++;
+ /*
+ * Imitate perl's feature of including parenthesized substrings
+ * that matched part of the delimiter in the new array, after the
+ * split substring that was delimited.
+ */
+ if (re != null && matched[0] == true) {
+ int size = parens[0].length;
+ for (int num = 0; num < size; num++) {
+ if (limited && len >= limit)
+ break;
+ result.put(len, result, parens[0][num]);
+ len++;
+ }
+ matched[0] = false;
+ }
+ ip[0] = match + matchlen[0];
+
+ if (version < Context.VERSION_1_3
+ && version != Context.VERSION_DEFAULT)
+ {
+ /*
+ * Deviate from ECMA to imitate Perl, which omits a final
+ * split unless a limit argument is given and big enough.
+ */
+ if (!limited && ip[0] == target.length())
+ break;
+ }
+ }
+ return result;
+ }
+
+ /*
+ * See ECMA 15.5.4.15
+ */
+ private static String js_substring(Context cx, String target,
+ Object[] args)
+ {
+ int length = target.length();
+ double start = ScriptRuntime.toInteger(args, 0);
+ double end;
+
+ if (start < 0)
+ start = 0;
+ else if (start > length)
+ start = length;
+
+ if (args.length <= 1 || args[1] == Undefined.instance) {
+ end = length;
+ } else {
+ end = ScriptRuntime.toInteger(args[1]);
+ if (end < 0)
+ end = 0;
+ else if (end > length)
+ end = length;
+
+ // swap if end < start
+ if (end < start) {
+ if (cx.getLanguageVersion() != Context.VERSION_1_2) {
+ double temp = start;
+ start = end;
+ end = temp;
+ } else {
+ // Emulate old JDK1.0 java.lang.String.substring()
+ end = start;
+ }
+ }
+ }
+ return target.substring((int)start, (int)end);
+ }
+
+ int getLength() {
+ return string.length();
+ }
+
+ /*
+ * Non-ECMA methods.
+ */
+ private static String js_substr(String target, Object[] args) {
+ if (args.length < 1)
+ return target;
+
+ double begin = ScriptRuntime.toInteger(args[0]);
+ double end;
+ int length = target.length();
+
+ if (begin < 0) {
+ begin += length;
+ if (begin < 0)
+ begin = 0;
+ } else if (begin > length) {
+ begin = length;
+ }
+
+ if (args.length == 1) {
+ end = length;
+ } else {
+ end = ScriptRuntime.toInteger(args[1]);
+ if (end < 0)
+ end = 0;
+ end += begin;
+ if (end > length)
+ end = length;
+ }
+
+ return target.substring((int)begin, (int)end);
+ }
+
+ /*
+ * Python-esque sequence operations.
+ */
+ private static String js_concat(String target, Object[] args) {
+ int N = args.length;
+ if (N == 0) { return target; }
+ else if (N == 1) {
+ String arg = ScriptRuntime.toString(args[0]);
+ return target.concat(arg);
+ }
+
+ // Find total capacity for the final string to avoid unnecessary
+ // re-allocations in StringBuffer
+ int size = target.length();
+ String[] argsAsStrings = new String[N];
+ for (int i = 0; i != N; ++i) {
+ String s = ScriptRuntime.toString(args[i]);
+ argsAsStrings[i] = s;
+ size += s.length();
+ }
+
+ StringBuffer result = new StringBuffer(size);
+ result.append(target);
+ for (int i = 0; i != N; ++i) {
+ result.append(argsAsStrings[i]);
+ }
+ return result.toString();
+ }
+
+ private static String js_slice(String target, Object[] args) {
+ if (args.length != 0) {
+ double begin = ScriptRuntime.toInteger(args[0]);
+ double end;
+ int length = target.length();
+ if (begin < 0) {
+ begin += length;
+ if (begin < 0)
+ begin = 0;
+ } else if (begin > length) {
+ begin = length;
+ }
+
+ if (args.length == 1) {
+ end = length;
+ } else {
+ end = ScriptRuntime.toInteger(args[1]);
+ if (end < 0) {
+ end += length;
+ if (end < 0)
+ end = 0;
+ } else if (end > length) {
+ end = length;
+ }
+ if (end < begin)
+ end = begin;
+ }
+ return target.substring((int)begin, (int)end);
+ }
+ return target;
+ }
+
+// #string_id_map#
+
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #generated# Last update: 2007-05-01 22:11:49 EDT
+ L0: { id = 0; String X = null; int c;
+ L: switch (s.length()) {
+ case 3: c=s.charAt(2);
+ if (c=='b') { if (s.charAt(0)=='s' && s.charAt(1)=='u') {id=Id_sub; break L0;} }
+ else if (c=='g') { if (s.charAt(0)=='b' && s.charAt(1)=='i') {id=Id_big; break L0;} }
+ else if (c=='p') { if (s.charAt(0)=='s' && s.charAt(1)=='u') {id=Id_sup; break L0;} }
+ break L;
+ case 4: c=s.charAt(0);
+ if (c=='b') { X="bold";id=Id_bold; }
+ else if (c=='l') { X="link";id=Id_link; }
+ break L;
+ case 5: switch (s.charAt(4)) {
+ case 'd': X="fixed";id=Id_fixed; break L;
+ case 'e': X="slice";id=Id_slice; break L;
+ case 'h': X="match";id=Id_match; break L;
+ case 'k': X="blink";id=Id_blink; break L;
+ case 'l': X="small";id=Id_small; break L;
+ case 't': X="split";id=Id_split; break L;
+ } break L;
+ case 6: switch (s.charAt(1)) {
+ case 'e': X="search";id=Id_search; break L;
+ case 'h': X="charAt";id=Id_charAt; break L;
+ case 'n': X="anchor";id=Id_anchor; break L;
+ case 'o': X="concat";id=Id_concat; break L;
+ case 'q': X="equals";id=Id_equals; break L;
+ case 't': X="strike";id=Id_strike; break L;
+ case 'u': X="substr";id=Id_substr; break L;
+ } break L;
+ case 7: switch (s.charAt(1)) {
+ case 'a': X="valueOf";id=Id_valueOf; break L;
+ case 'e': X="replace";id=Id_replace; break L;
+ case 'n': X="indexOf";id=Id_indexOf; break L;
+ case 't': X="italics";id=Id_italics; break L;
+ } break L;
+ case 8: c=s.charAt(4);
+ if (c=='r') { X="toString";id=Id_toString; }
+ else if (c=='s') { X="fontsize";id=Id_fontsize; }
+ else if (c=='u') { X="toSource";id=Id_toSource; }
+ break L;
+ case 9: c=s.charAt(0);
+ if (c=='f') { X="fontcolor";id=Id_fontcolor; }
+ else if (c=='s') { X="substring";id=Id_substring; }
+ break L;
+ case 10: X="charCodeAt";id=Id_charCodeAt; break L;
+ case 11: switch (s.charAt(2)) {
+ case 'L': X="toLowerCase";id=Id_toLowerCase; break L;
+ case 'U': X="toUpperCase";id=Id_toUpperCase; break L;
+ case 'n': X="constructor";id=Id_constructor; break L;
+ case 's': X="lastIndexOf";id=Id_lastIndexOf; break L;
+ } break L;
+ case 13: X="localeCompare";id=Id_localeCompare; break L;
+ case 16: X="equalsIgnoreCase";id=Id_equalsIgnoreCase; break L;
+ case 17: c=s.charAt(8);
+ if (c=='L') { X="toLocaleLowerCase";id=Id_toLocaleLowerCase; }
+ else if (c=='U') { X="toLocaleUpperCase";id=Id_toLocaleUpperCase; }
+ break L;
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ ConstructorId_fromCharCode = -1,
+
+ Id_constructor = 1,
+ Id_toString = 2,
+ Id_toSource = 3,
+ Id_valueOf = 4,
+ Id_charAt = 5,
+ Id_charCodeAt = 6,
+ Id_indexOf = 7,
+ Id_lastIndexOf = 8,
+ Id_split = 9,
+ Id_substring = 10,
+ Id_toLowerCase = 11,
+ Id_toUpperCase = 12,
+ Id_substr = 13,
+ Id_concat = 14,
+ Id_slice = 15,
+ Id_bold = 16,
+ Id_italics = 17,
+ Id_fixed = 18,
+ Id_strike = 19,
+ Id_small = 20,
+ Id_big = 21,
+ Id_blink = 22,
+ Id_sup = 23,
+ Id_sub = 24,
+ Id_fontsize = 25,
+ Id_fontcolor = 26,
+ Id_link = 27,
+ Id_anchor = 28,
+ Id_equals = 29,
+ Id_equalsIgnoreCase = 30,
+ Id_match = 31,
+ Id_search = 32,
+ Id_replace = 33,
+ Id_localeCompare = 34,
+ Id_toLocaleLowerCase = 35,
+ Id_toLocaleUpperCase = 36,
+ MAX_PROTOTYPE_ID = 36;
+
+// #/string_id_map#
+
+ private static final int
+ ConstructorId_charAt = -Id_charAt,
+ ConstructorId_charCodeAt = -Id_charCodeAt,
+ ConstructorId_indexOf = -Id_indexOf,
+ ConstructorId_lastIndexOf = -Id_lastIndexOf,
+ ConstructorId_split = -Id_split,
+ ConstructorId_substring = -Id_substring,
+ ConstructorId_toLowerCase = -Id_toLowerCase,
+ ConstructorId_toUpperCase = -Id_toUpperCase,
+ ConstructorId_substr = -Id_substr,
+ ConstructorId_concat = -Id_concat,
+ ConstructorId_slice = -Id_slice,
+ ConstructorId_equalsIgnoreCase = -Id_equalsIgnoreCase,
+ ConstructorId_match = -Id_match,
+ ConstructorId_search = -Id_search,
+ ConstructorId_replace = -Id_replace,
+ ConstructorId_localeCompare = -Id_localeCompare,
+ ConstructorId_toLocaleLowerCase = -Id_toLocaleLowerCase;
+
+ private String string;
+}
+
diff --git a/src/org/mozilla/javascript/NativeWith.java b/src/org/mozilla/javascript/NativeWith.java
new file mode 100644
index 0000000..5891e91
--- /dev/null
+++ b/src/org/mozilla/javascript/NativeWith.java
@@ -0,0 +1,207 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.Serializable;
+
+/**
+ * This class implements the object lookup required for the
+ * <code>with</code> statement.
+ * It simply delegates every action to its prototype except
+ * for operations on its parent.
+ */
+public class NativeWith implements Scriptable, IdFunctionCall, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ static void init(Scriptable scope, boolean sealed)
+ {
+ NativeWith obj = new NativeWith();
+
+ obj.setParentScope(scope);
+ obj.setPrototype(ScriptableObject.getObjectPrototype(scope));
+
+ IdFunctionObject ctor = new IdFunctionObject(obj, FTAG, Id_constructor,
+ "With", 0, scope);
+ ctor.markAsConstructor(obj);
+ if (sealed) {
+ ctor.sealObject();
+ }
+ ctor.exportAsScopeProperty();
+ }
+
+ private NativeWith() {
+ }
+
+ protected NativeWith(Scriptable parent, Scriptable prototype) {
+ this.parent = parent;
+ this.prototype = prototype;
+ }
+
+ public String getClassName() {
+ return "With";
+ }
+
+ public boolean has(String id, Scriptable start)
+ {
+ return prototype.has(id, prototype);
+ }
+
+ public boolean has(int index, Scriptable start)
+ {
+ return prototype.has(index, prototype);
+ }
+
+ public Object get(String id, Scriptable start)
+ {
+ if (start == this)
+ start = prototype;
+ return prototype.get(id, start);
+ }
+
+ public Object get(int index, Scriptable start)
+ {
+ if (start == this)
+ start = prototype;
+ return prototype.get(index, start);
+ }
+
+ public void put(String id, Scriptable start, Object value)
+ {
+ if (start == this)
+ start = prototype;
+ prototype.put(id, start, value);
+ }
+
+ public void put(int index, Scriptable start, Object value)
+ {
+ if (start == this)
+ start = prototype;
+ prototype.put(index, start, value);
+ }
+
+ public void delete(String id)
+ {
+ prototype.delete(id);
+ }
+
+ public void delete(int index)
+ {
+ prototype.delete(index);
+ }
+
+ public Scriptable getPrototype() {
+ return prototype;
+ }
+
+ public void setPrototype(Scriptable prototype) {
+ this.prototype = prototype;
+ }
+
+ public Scriptable getParentScope() {
+ return parent;
+ }
+
+ public void setParentScope(Scriptable parent) {
+ this.parent = parent;
+ }
+
+ public Object[] getIds() {
+ return prototype.getIds();
+ }
+
+ public Object getDefaultValue(Class<?> typeHint) {
+ return prototype.getDefaultValue(typeHint);
+ }
+
+ public boolean hasInstance(Scriptable value) {
+ return prototype.hasInstance(value);
+ }
+
+ /**
+ * Must return null to continue looping or the final collection result.
+ */
+ protected Object updateDotQuery(boolean value)
+ {
+ // NativeWith itself does not support it
+ throw new IllegalStateException();
+ }
+
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (f.hasTag(FTAG)) {
+ if (f.methodId() == Id_constructor) {
+ throw Context.reportRuntimeError1("msg.cant.call.indirect", "With");
+ }
+ }
+ throw f.unknown();
+ }
+
+ static boolean isWithFunction(Object functionObj)
+ {
+ if (functionObj instanceof IdFunctionObject) {
+ IdFunctionObject f = (IdFunctionObject)functionObj;
+ return f.hasTag(FTAG) && f.methodId() == Id_constructor;
+ }
+ return false;
+ }
+
+ static Object newWithSpecial(Context cx, Scriptable scope, Object[] args)
+ {
+ ScriptRuntime.checkDeprecated(cx, "With");
+ scope = ScriptableObject.getTopLevelScope(scope);
+ NativeWith thisObj = new NativeWith();
+ thisObj.setPrototype(args.length == 0
+ ? ScriptableObject.getClassPrototype(scope,
+ "Object")
+ : ScriptRuntime.toObject(cx, scope, args[0]));
+ thisObj.setParentScope(scope);
+ return thisObj;
+ }
+
+ private static final Object FTAG = "With";
+
+ private static final int
+ Id_constructor = 1;
+
+ protected Scriptable prototype;
+ protected Scriptable parent;
+}
diff --git a/src/org/mozilla/javascript/Node.java b/src/org/mozilla/javascript/Node.java
new file mode 100644
index 0000000..d6f2f20
--- /dev/null
+++ b/src/org/mozilla/javascript/Node.java
@@ -0,0 +1,1399 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Roshan James
+ * Roger Lawrence
+ * Mike McCabe
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.util.Map;
+import java.util.LinkedHashMap;
+import java.util.Iterator;
+import java.util.Collections;
+
+/**
+ * This class implements the root of the intermediate representation.
+ *
+ * @author Norris Boyd
+ * @author Mike McCabe
+ */
+
+public class Node
+{
+ public static final int
+ FUNCTION_PROP = 1,
+ LOCAL_PROP = 2,
+ LOCAL_BLOCK_PROP = 3,
+ REGEXP_PROP = 4,
+ CASEARRAY_PROP = 5,
+ /*
+ the following properties are defined and manipulated by the
+ optimizer -
+ TARGETBLOCK_PROP - the block referenced by a branch node
+ VARIABLE_PROP - the variable referenced by a BIND or NAME node
+ ISNUMBER_PROP - this node generates code on Number children and
+ delivers a Number result (as opposed to Objects)
+ DIRECTCALL_PROP - this call node should emit code to test the function
+ object against the known class and call direct if it
+ matches.
+ */
+
+ TARGETBLOCK_PROP = 6,
+ VARIABLE_PROP = 7,
+ ISNUMBER_PROP = 8,
+ DIRECTCALL_PROP = 9,
+ SPECIALCALL_PROP = 10,
+ SKIP_INDEXES_PROP = 11, // array of skipped indexes of array literal
+ OBJECT_IDS_PROP = 12, // array of properties for object literal
+ INCRDECR_PROP = 13, // pre or post type of increment/decrement
+ CATCH_SCOPE_PROP = 14, // index of catch scope block in catch
+ LABEL_ID_PROP = 15, // label id: code generation uses it
+ MEMBER_TYPE_PROP = 16, // type of element access operation
+ NAME_PROP = 17, // property name
+ CONTROL_BLOCK_PROP = 18, // flags a control block that can drop off
+ PARENTHESIZED_PROP = 19, // expression is parenthesized
+ GENERATOR_END_PROP = 20,
+ DESTRUCTURING_ARRAY_LENGTH = 21,
+ DESTRUCTURING_NAMES= 22,
+ LAST_PROP = 22;
+
+ // values of ISNUMBER_PROP to specify
+ // which of the children are Number types
+ public static final int
+ BOTH = 0,
+ LEFT = 1,
+ RIGHT = 2;
+
+ public static final int // values for SPECIALCALL_PROP
+ NON_SPECIALCALL = 0,
+ SPECIALCALL_EVAL = 1,
+ SPECIALCALL_WITH = 2;
+
+ public static final int // flags for INCRDECR_PROP
+ DECR_FLAG = 0x1,
+ POST_FLAG = 0x2;
+
+ public static final int // flags for MEMBER_TYPE_PROP
+ PROPERTY_FLAG = 0x1, // property access: element is valid name
+ ATTRIBUTE_FLAG = 0x2, // x. at y or x.. at y
+ DESCENDANTS_FLAG = 0x4; // x..y or x.. at i
+
+ private static class NumberNode extends Node
+ {
+ NumberNode(double number)
+ {
+ super(Token.NUMBER);
+ this.number = number;
+ }
+
+ double number;
+ }
+
+ private static class StringNode extends Node
+ {
+ StringNode(int type, String str) {
+ super(type);
+ this.str = str;
+ }
+
+ String str;
+ Node.Scope scope;
+ }
+
+ public static class Jump extends Node
+ {
+ public Jump(int type)
+ {
+ super(type);
+ }
+
+ Jump(int type, int lineno)
+ {
+ super(type, lineno);
+ }
+
+ Jump(int type, Node child)
+ {
+ super(type, child);
+ }
+
+ Jump(int type, Node child, int lineno)
+ {
+ super(type, child, lineno);
+ }
+
+ public final Jump getJumpStatement()
+ {
+ if (!(type == Token.BREAK || type == Token.CONTINUE)) Kit.codeBug();
+ return jumpNode;
+ }
+
+ public final void setJumpStatement(Jump jumpStatement)
+ {
+ if (!(type == Token.BREAK || type == Token.CONTINUE)) Kit.codeBug();
+ if (jumpStatement == null) Kit.codeBug();
+ if (this.jumpNode != null) Kit.codeBug(); //only once
+ this.jumpNode = jumpStatement;
+ }
+
+ public final Node getDefault()
+ {
+ if (!(type == Token.SWITCH)) Kit.codeBug();
+ return target2;
+ }
+
+ public final void setDefault(Node defaultTarget)
+ {
+ if (!(type == Token.SWITCH)) Kit.codeBug();
+ if (defaultTarget.type != Token.TARGET) Kit.codeBug();
+ if (target2 != null) Kit.codeBug(); //only once
+ target2 = defaultTarget;
+ }
+
+ public final Node getFinally()
+ {
+ if (!(type == Token.TRY)) Kit.codeBug();
+ return target2;
+ }
+
+ public final void setFinally(Node finallyTarget)
+ {
+ if (!(type == Token.TRY)) Kit.codeBug();
+ if (finallyTarget.type != Token.TARGET) Kit.codeBug();
+ if (target2 != null) Kit.codeBug(); //only once
+ target2 = finallyTarget;
+ }
+
+ public final Jump getLoop()
+ {
+ if (!(type == Token.LABEL)) Kit.codeBug();
+ return jumpNode;
+ }
+
+ public final void setLoop(Jump loop)
+ {
+ if (!(type == Token.LABEL)) Kit.codeBug();
+ if (loop == null) Kit.codeBug();
+ if (jumpNode != null) Kit.codeBug(); //only once
+ jumpNode = loop;
+ }
+
+ public final Node getContinue()
+ {
+ if (type != Token.LOOP) Kit.codeBug();
+ return target2;
+ }
+
+ public final void setContinue(Node continueTarget)
+ {
+ if (type != Token.LOOP) Kit.codeBug();
+ if (continueTarget.type != Token.TARGET) Kit.codeBug();
+ if (target2 != null) Kit.codeBug(); //only once
+ target2 = continueTarget;
+ }
+
+ public Node target;
+ private Node target2;
+ private Jump jumpNode;
+ }
+
+ static class Symbol {
+ Symbol(int declType, String name) {
+ this.declType = declType;
+ this.name = name;
+ this.index = -1;
+ }
+ /**
+ * One of Token.FUNCTION, Token.LP (for parameters), Token.VAR,
+ * Token.LET, or Token.CONST
+ */
+ int declType;
+ int index;
+ String name;
+ Node.Scope containingTable;
+ }
+
+ static class Scope extends Jump {
+ public Scope(int nodeType) {
+ super(nodeType);
+ }
+
+ public Scope(int nodeType, int lineno) {
+ super(nodeType, lineno);
+ }
+
+ public Scope(int nodeType, Node n, int lineno) {
+ super(nodeType, n, lineno);
+ }
+
+ /*
+ * Creates a new scope node, moving symbol table information
+ * from "scope" to the new node, and making "scope" a nested
+ * scope contained by the new node.
+ * Useful for injecting a new scope in a scope chain.
+ */
+ public static Scope splitScope(Scope scope) {
+ Scope result = new Scope(scope.getType());
+ result.symbolTable = scope.symbolTable;
+ scope.symbolTable = null;
+ result.parent = scope.parent;
+ scope.parent = result;
+ result.top = scope.top;
+ return result;
+ }
+
+ public static void joinScopes(Scope source, Scope dest) {
+ source.ensureSymbolTable();
+ dest.ensureSymbolTable();
+ if (!Collections.disjoint(source.symbolTable.keySet(),
+ dest.symbolTable.keySet()))
+ {
+ throw Kit.codeBug();
+ }
+ dest.symbolTable.putAll(source.symbolTable);
+ }
+
+ public void setParent(Scope parent) {
+ this.parent = parent;
+ this.top = parent == null ? (ScriptOrFnNode)this : parent.top;
+ }
+
+ public Scope getParentScope() {
+ return parent;
+ }
+
+ public Scope getDefiningScope(String name) {
+ for (Scope sn=this; sn != null; sn = sn.parent) {
+ if (sn.symbolTable == null)
+ continue;
+ if (sn.symbolTable.containsKey(name))
+ return sn;
+ }
+ return null;
+ }
+
+ public Symbol getSymbol(String name) {
+ return symbolTable == null ? null : symbolTable.get(name);
+ }
+
+ public void putSymbol(String name, Symbol symbol) {
+ ensureSymbolTable();
+ symbolTable.put(name, symbol);
+ symbol.containingTable = this;
+ top.addSymbol(symbol);
+ }
+
+ public Map<String,Symbol> getSymbolTable() {
+ return symbolTable;
+ }
+
+ private void ensureSymbolTable() {
+ if (symbolTable == null) {
+ symbolTable = new LinkedHashMap<String,Symbol>(5);
+ }
+ }
+
+ // Use LinkedHashMap so that the iteration order is the insertion order
+ protected LinkedHashMap<String,Symbol> symbolTable;
+ private Scope parent;
+ private ScriptOrFnNode top;
+ }
+
+ private static class PropListItem
+ {
+ PropListItem next;
+ int type;
+ int intValue;
+ Object objectValue;
+ }
+
+
+ public Node(int nodeType) {
+ type = nodeType;
+ }
+
+ public Node(int nodeType, Node child) {
+ type = nodeType;
+ first = last = child;
+ child.next = null;
+ }
+
+ public Node(int nodeType, Node left, Node right) {
+ type = nodeType;
+ first = left;
+ last = right;
+ left.next = right;
+ right.next = null;
+ }
+
+ public Node(int nodeType, Node left, Node mid, Node right) {
+ type = nodeType;
+ first = left;
+ last = right;
+ left.next = mid;
+ mid.next = right;
+ right.next = null;
+ }
+
+ public Node(int nodeType, int line) {
+ type = nodeType;
+ lineno = line;
+ }
+
+ public Node(int nodeType, Node child, int line) {
+ this(nodeType, child);
+ lineno = line;
+ }
+
+ public Node(int nodeType, Node left, Node right, int line) {
+ this(nodeType, left, right);
+ lineno = line;
+ }
+
+ public Node(int nodeType, Node left, Node mid, Node right, int line) {
+ this(nodeType, left, mid, right);
+ lineno = line;
+ }
+
+ public static Node newNumber(double number) {
+ return new NumberNode(number);
+ }
+
+ public static Node newString(String str) {
+ return new StringNode(Token.STRING, str);
+ }
+
+ public static Node newString(int type, String str) {
+ return new StringNode(type, str);
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public boolean hasChildren() {
+ return first != null;
+ }
+
+ public Node getFirstChild() {
+ return first;
+ }
+
+ public Node getLastChild() {
+ return last;
+ }
+
+ public Node getNext() {
+ return next;
+ }
+
+ public Node getChildBefore(Node child) {
+ if (child == first)
+ return null;
+ Node n = first;
+ while (n.next != child) {
+ n = n.next;
+ if (n == null)
+ throw new RuntimeException("node is not a child");
+ }
+ return n;
+ }
+
+ public Node getLastSibling() {
+ Node n = this;
+ while (n.next != null) {
+ n = n.next;
+ }
+ return n;
+ }
+
+ public void addChildToFront(Node child) {
+ child.next = first;
+ first = child;
+ if (last == null) {
+ last = child;
+ }
+ }
+
+ public void addChildToBack(Node child) {
+ child.next = null;
+ if (last == null) {
+ first = last = child;
+ return;
+ }
+ last.next = child;
+ last = child;
+ }
+
+ public void addChildrenToFront(Node children) {
+ Node lastSib = children.getLastSibling();
+ lastSib.next = first;
+ first = children;
+ if (last == null) {
+ last = lastSib;
+ }
+ }
+
+ public void addChildrenToBack(Node children) {
+ if (last != null) {
+ last.next = children;
+ }
+ last = children.getLastSibling();
+ if (first == null) {
+ first = children;
+ }
+ }
+
+ /**
+ * Add 'child' before 'node'.
+ */
+ public void addChildBefore(Node newChild, Node node) {
+ if (newChild.next != null)
+ throw new RuntimeException(
+ "newChild had siblings in addChildBefore");
+ if (first == node) {
+ newChild.next = first;
+ first = newChild;
+ return;
+ }
+ Node prev = getChildBefore(node);
+ addChildAfter(newChild, prev);
+ }
+
+ /**
+ * Add 'child' after 'node'.
+ */
+ public void addChildAfter(Node newChild, Node node) {
+ if (newChild.next != null)
+ throw new RuntimeException(
+ "newChild had siblings in addChildAfter");
+ newChild.next = node.next;
+ node.next = newChild;
+ if (last == node)
+ last = newChild;
+ }
+
+ public void removeChild(Node child) {
+ Node prev = getChildBefore(child);
+ if (prev == null)
+ first = first.next;
+ else
+ prev.next = child.next;
+ if (child == last) last = prev;
+ child.next = null;
+ }
+
+ public void replaceChild(Node child, Node newChild) {
+ newChild.next = child.next;
+ if (child == first) {
+ first = newChild;
+ } else {
+ Node prev = getChildBefore(child);
+ prev.next = newChild;
+ }
+ if (child == last)
+ last = newChild;
+ child.next = null;
+ }
+
+ public void replaceChildAfter(Node prevChild, Node newChild) {
+ Node child = prevChild.next;
+ newChild.next = child.next;
+ prevChild.next = newChild;
+ if (child == last)
+ last = newChild;
+ child.next = null;
+ }
+
+ private static final String propToString(int propType)
+ {
+ if (Token.printTrees) {
+ // If Context.printTrees is false, the compiler
+ // can remove all these strings.
+ switch (propType) {
+ case FUNCTION_PROP: return "function";
+ case LOCAL_PROP: return "local";
+ case LOCAL_BLOCK_PROP: return "local_block";
+ case REGEXP_PROP: return "regexp";
+ case CASEARRAY_PROP: return "casearray";
+
+ case TARGETBLOCK_PROP: return "targetblock";
+ case VARIABLE_PROP: return "variable";
+ case ISNUMBER_PROP: return "isnumber";
+ case DIRECTCALL_PROP: return "directcall";
+
+ case SPECIALCALL_PROP: return "specialcall";
+ case SKIP_INDEXES_PROP: return "skip_indexes";
+ case OBJECT_IDS_PROP: return "object_ids_prop";
+ case INCRDECR_PROP: return "incrdecr_prop";
+ case CATCH_SCOPE_PROP: return "catch_scope_prop";
+ case LABEL_ID_PROP: return "label_id_prop";
+ case MEMBER_TYPE_PROP: return "member_type_prop";
+ case NAME_PROP: return "name_prop";
+ case CONTROL_BLOCK_PROP: return "control_block_prop";
+ case PARENTHESIZED_PROP: return "parenthesized_prop";
+ case GENERATOR_END_PROP: return "generator_end";
+ case DESTRUCTURING_ARRAY_LENGTH:
+ return "destructuring_array_length";
+ case DESTRUCTURING_NAMES:return "destructuring_names";
+
+ default: Kit.codeBug();
+ }
+ }
+ return null;
+ }
+
+ private PropListItem lookupProperty(int propType)
+ {
+ PropListItem x = propListHead;
+ while (x != null && propType != x.type) {
+ x = x.next;
+ }
+ return x;
+ }
+
+ private PropListItem ensureProperty(int propType)
+ {
+ PropListItem item = lookupProperty(propType);
+ if (item == null) {
+ item = new PropListItem();
+ item.type = propType;
+ item.next = propListHead;
+ propListHead = item;
+ }
+ return item;
+ }
+
+ public void removeProp(int propType)
+ {
+ PropListItem x = propListHead;
+ if (x != null) {
+ PropListItem prev = null;
+ while (x.type != propType) {
+ prev = x;
+ x = x.next;
+ if (x == null) { return; }
+ }
+ if (prev == null) {
+ propListHead = x.next;
+ } else {
+ prev.next = x.next;
+ }
+ }
+ }
+
+ public Object getProp(int propType)
+ {
+ PropListItem item = lookupProperty(propType);
+ if (item == null) { return null; }
+ return item.objectValue;
+ }
+
+ public int getIntProp(int propType, int defaultValue)
+ {
+ PropListItem item = lookupProperty(propType);
+ if (item == null) { return defaultValue; }
+ return item.intValue;
+ }
+
+ public int getExistingIntProp(int propType)
+ {
+ PropListItem item = lookupProperty(propType);
+ if (item == null) { Kit.codeBug(); }
+ return item.intValue;
+ }
+
+ public void putProp(int propType, Object prop)
+ {
+ if (prop == null) {
+ removeProp(propType);
+ } else {
+ PropListItem item = ensureProperty(propType);
+ item.objectValue = prop;
+ }
+ }
+
+ public void putIntProp(int propType, int prop)
+ {
+ PropListItem item = ensureProperty(propType);
+ item.intValue = prop;
+ }
+
+ public int getLineno() {
+ return lineno;
+ }
+
+ /** Can only be called when <tt>getType() == Token.NUMBER</tt> */
+ public final double getDouble() {
+ return ((NumberNode)this).number;
+ }
+
+ public final void setDouble(double number) {
+ ((NumberNode)this).number = number;
+ }
+
+ /** Can only be called when node has String context. */
+ public final String getString() {
+ return ((StringNode)this).str;
+ }
+
+ /** Can only be called when node has String context. */
+ public final void setString(String s) {
+ if (s == null) Kit.codeBug();
+ ((StringNode)this).str = s;
+ }
+
+ /** Can only be called when node has String context. */
+ public final Scope getScope() {
+ return ((StringNode)this).scope;
+ }
+
+ /** Can only be called when node has String context. */
+ public final void setScope(Scope s) {
+ if (s == null) Kit.codeBug();
+ if (!(this instanceof StringNode)) {
+ throw Kit.codeBug();
+ }
+ ((StringNode)this).scope = s;
+ }
+
+ public static Node newTarget()
+ {
+ return new Node(Token.TARGET);
+ }
+
+ public final int labelId()
+ {
+ if (type != Token.TARGET && type != Token.YIELD) Kit.codeBug();
+ return getIntProp(LABEL_ID_PROP, -1);
+ }
+
+ public void labelId(int labelId)
+ {
+ if (type != Token.TARGET && type != Token.YIELD) Kit.codeBug();
+ putIntProp(LABEL_ID_PROP, labelId);
+ }
+
+
+ /**
+ * Does consistent-return analysis on the function body when strict mode is
+ * enabled.
+ *
+ * function (x) { return (x+1) }
+ * is ok, but
+ * function (x) { if (x < 0) return (x+1); }
+ * is not becuase the function can potentially return a value when the
+ * condition is satisfied and if not, the function does not explicitly
+ * return value.
+ *
+ * This extends to checking mismatches such as "return" and "return <value>"
+ * used in the same function. Warnings are not emitted if inconsistent
+ * returns exist in code that can be statically shown to be unreachable.
+ * Ex.
+ * function (x) { while (true) { ... if (..) { return value } ... } }
+ * emits no warning. However if the loop had a break statement, then a
+ * warning would be emitted.
+ *
+ * The consistency analysis looks at control structures such as loops, ifs,
+ * switch, try-catch-finally blocks, examines the reachable code paths and
+ * warns the user about an inconsistent set of termination possibilities.
+ *
+ * Caveat: Since the parser flattens many control structures into almost
+ * straight-line code with gotos, it makes such analysis hard. Hence this
+ * analyser is written to taken advantage of patterns of code generated by
+ * the parser (for loops, try blocks and such) and does not do a full
+ * control flow analysis of the gotos and break/continue statements.
+ * Future changes to the parser will affect this analysis.
+ */
+
+ /**
+ * These flags enumerate the possible ways a statement/function can
+ * terminate. These flags are used by endCheck() and by the Parser to
+ * detect inconsistent return usage.
+ *
+ * END_UNREACHED is reserved for code paths that are assumed to always be
+ * able to execute (example: throw, continue)
+ *
+ * END_DROPS_OFF indicates if the statement can transfer control to the
+ * next one. Statement such as return dont. A compound statement may have
+ * some branch that drops off control to the next statement.
+ *
+ * END_RETURNS indicates that the statement can return (without arguments)
+ * END_RETURNS_VALUE indicates that the statement can return a value.
+ *
+ * A compound statement such as
+ * if (condition) {
+ * return value;
+ * }
+ * Will be detected as (END_DROPS_OFF | END_RETURN_VALUE) by endCheck()
+ */
+ static final int END_UNREACHED = 0;
+ static final int END_DROPS_OFF = 1;
+ static final int END_RETURNS = 2;
+ static final int END_RETURNS_VALUE = 4;
+ static final int END_YIELDS = 8;
+
+ /**
+ * Checks that every return usage in a function body is consistent with the
+ * requirements of strict-mode.
+ * @return true if the function satisfies strict mode requirement.
+ */
+ public boolean hasConsistentReturnUsage()
+ {
+ int n = endCheck();
+ return (n & END_RETURNS_VALUE) == 0 ||
+ (n & (END_DROPS_OFF|END_RETURNS|END_YIELDS)) == 0;
+ }
+
+ /**
+ * Returns in the then and else blocks must be consistent with each other.
+ * If there is no else block, then the return statement can fall through.
+ * @return logical OR of END_* flags
+ */
+ private int endCheckIf()
+ {
+ Node th, el;
+ int rv = END_UNREACHED;
+
+ th = next;
+ el = ((Jump)this).target;
+
+ rv = th.endCheck();
+
+ if (el != null)
+ rv |= el.endCheck();
+ else
+ rv |= END_DROPS_OFF;
+
+ return rv;
+ }
+
+ /**
+ * Consistency of return statements is checked between the case statements.
+ * If there is no default, then the switch can fall through. If there is a
+ * default,we check to see if all code paths in the default return or if
+ * there is a code path that can fall through.
+ * @return logical OR of END_* flags
+ */
+ private int endCheckSwitch()
+ {
+ Node n;
+ int rv = END_UNREACHED;
+
+ // examine the cases
+ for (n = first.next; n != null; n = n.next)
+ {
+ if (n.type == Token.CASE) {
+ rv |= ((Jump)n).target.endCheck();
+ } else
+ break;
+ }
+
+ // we don't care how the cases drop into each other
+ rv &= ~END_DROPS_OFF;
+
+ // examine the default
+ n = ((Jump)this).getDefault();
+ if (n != null)
+ rv |= n.endCheck();
+ else
+ rv |= END_DROPS_OFF;
+
+ // remove the switch block
+ rv |= getIntProp(CONTROL_BLOCK_PROP, END_UNREACHED);
+
+ return rv;
+ }
+
+ /**
+ * If the block has a finally, return consistency is checked in the
+ * finally block. If all code paths in the finally returns, then the
+ * returns in the try-catch blocks don't matter. If there is a code path
+ * that does not return or if there is no finally block, the returns
+ * of the try and catch blocks are checked for mismatch.
+ * @return logical OR of END_* flags
+ */
+ private int endCheckTry()
+ {
+ Node n;
+ int rv = END_UNREACHED;
+
+ // check the finally if it exists
+ n = ((Jump)this).getFinally();
+ if(n != null) {
+ rv = n.next.first.endCheck();
+ } else {
+ rv = END_DROPS_OFF;
+ }
+
+ // if the finally block always returns, then none of the returns
+ // in the try or catch blocks matter
+ if ((rv & END_DROPS_OFF) != 0) {
+ rv &= ~END_DROPS_OFF;
+
+ // examine the try block
+ rv |= first.endCheck();
+
+ // check each catch block
+ n = ((Jump)this).target;
+ if (n != null)
+ {
+ // point to the first catch_scope
+ for (n = n.next.first; n != null; n = n.next.next)
+ {
+ // check the block of user code in the catch_scope
+ rv |= n.next.first.next.first.endCheck();
+ }
+ }
+ }
+
+ return rv;
+ }
+
+ /**
+ * Return statement in the loop body must be consistent. The default
+ * assumption for any kind of a loop is that it will eventually terminate.
+ * The only exception is a loop with a constant true condition. Code that
+ * follows such a loop is examined only if one can statically determine
+ * that there is a break out of the loop.
+ * for(<> ; <>; <>) {}
+ * for(<> in <> ) {}
+ * while(<>) { }
+ * do { } while(<>)
+ * @return logical OR of END_* flags
+ */
+ private int endCheckLoop()
+ {
+ Node n;
+ int rv = END_UNREACHED;
+
+ // To find the loop body, we look at the second to last node of the
+ // loop node, which should be the predicate that the loop should
+ // satisfy.
+ // The target of the predicate is the loop-body for all 4 kinds of
+ // loops.
+ for (n = first; n.next != last; n = n.next) {
+ /* skip */
+ }
+ if (n.type != Token.IFEQ)
+ return END_DROPS_OFF;
+
+ // The target's next is the loop body block
+ rv = ((Jump)n).target.next.endCheck();
+
+ // check to see if the loop condition is true
+ if (n.first.type == Token.TRUE)
+ rv &= ~END_DROPS_OFF;
+
+ // look for effect of breaks
+ rv |= getIntProp(CONTROL_BLOCK_PROP, END_UNREACHED);
+
+ return rv;
+ }
+
+
+ /**
+ * A general block of code is examined statement by statement. If any
+ * statement (even compound ones) returns in all branches, then subsequent
+ * statements are not examined.
+ * @return logical OR of END_* flags
+ */
+ private int endCheckBlock()
+ {
+ Node n;
+ int rv = END_DROPS_OFF;
+
+ // check each statment and if the statement can continue onto the next
+ // one, then check the next statement
+ for (n=first; ((rv & END_DROPS_OFF) != 0) && n != null; n = n.next)
+ {
+ rv &= ~END_DROPS_OFF;
+ rv |= n.endCheck();
+ }
+ return rv;
+ }
+
+ /**
+ * A labelled statement implies that there maybe a break to the label. The
+ * function processes the labelled statement and then checks the
+ * CONTROL_BLOCK_PROP property to see if there is ever a break to the
+ * particular label.
+ * @return logical OR of END_* flags
+ */
+ private int endCheckLabel()
+ {
+ int rv = END_UNREACHED;
+
+ rv = next.endCheck();
+ rv |= getIntProp(CONTROL_BLOCK_PROP, END_UNREACHED);
+
+ return rv;
+ }
+
+ /**
+ * When a break is encountered annotate the statement being broken
+ * out of by setting its CONTROL_BLOCK_PROP property.
+ * @return logical OR of END_* flags
+ */
+ private int endCheckBreak()
+ {
+ Node n = ((Jump) this).jumpNode;
+ n.putIntProp(CONTROL_BLOCK_PROP, END_DROPS_OFF);
+ return END_UNREACHED;
+ }
+
+ /**
+ * endCheck() examines the body of a function, doing a basic reachability
+ * analysis and returns a combination of flags END_* flags that indicate
+ * how the function execution can terminate. These constitute only the
+ * pessimistic set of termination conditions. It is possible that at
+ * runtime certain code paths will never be actually taken. Hence this
+ * analysis will flag errors in cases where there may not be errors.
+ * @return logical OR of END_* flags
+ */
+ private int endCheck()
+ {
+ switch(type)
+ {
+ case Token.BREAK:
+ return endCheckBreak();
+
+ case Token.EXPR_VOID:
+ if (this.first != null)
+ return first.endCheck();
+ return END_DROPS_OFF;
+
+ case Token.YIELD:
+ return END_YIELDS;
+
+ case Token.CONTINUE:
+ case Token.THROW:
+ return END_UNREACHED;
+
+ case Token.RETURN:
+ if (this.first != null)
+ return END_RETURNS_VALUE;
+ else
+ return END_RETURNS;
+
+ case Token.TARGET:
+ if (next != null)
+ return next.endCheck();
+ else
+ return END_DROPS_OFF;
+
+ case Token.LOOP:
+ return endCheckLoop();
+
+ case Token.LOCAL_BLOCK:
+ case Token.BLOCK:
+ // there are several special kinds of blocks
+ if (first == null)
+ return END_DROPS_OFF;
+
+ switch(first.type) {
+ case Token.LABEL:
+ return first.endCheckLabel();
+
+ case Token.IFNE:
+ return first.endCheckIf();
+
+ case Token.SWITCH:
+ return first.endCheckSwitch();
+
+ case Token.TRY:
+ return first.endCheckTry();
+
+ default:
+ return endCheckBlock();
+ }
+
+ default:
+ return END_DROPS_OFF;
+ }
+ }
+
+ public boolean hasSideEffects()
+ {
+ switch (type) {
+ case Token.EXPR_VOID:
+ case Token.COMMA:
+ if (last != null)
+ return last.hasSideEffects();
+ else
+ return true;
+
+ case Token.HOOK:
+ if (first == null ||
+ first.next == null ||
+ first.next.next == null)
+ Kit.codeBug();
+ return first.next.hasSideEffects() &&
+ first.next.next.hasSideEffects();
+
+ case Token.AND:
+ case Token.OR:
+ if (first == null || last == null)
+ Kit.codeBug();
+ return first.hasSideEffects() || last.hasSideEffects();
+
+ case Token.ERROR: // Avoid cascaded error messages
+ case Token.EXPR_RESULT:
+ case Token.ASSIGN:
+ case Token.ASSIGN_ADD:
+ case Token.ASSIGN_SUB:
+ case Token.ASSIGN_MUL:
+ case Token.ASSIGN_DIV:
+ case Token.ASSIGN_MOD:
+ case Token.ASSIGN_BITOR:
+ case Token.ASSIGN_BITXOR:
+ case Token.ASSIGN_BITAND:
+ case Token.ASSIGN_LSH:
+ case Token.ASSIGN_RSH:
+ case Token.ASSIGN_URSH:
+ case Token.ENTERWITH:
+ case Token.LEAVEWITH:
+ case Token.RETURN:
+ case Token.GOTO:
+ case Token.IFEQ:
+ case Token.IFNE:
+ case Token.NEW:
+ case Token.DELPROP:
+ case Token.SETNAME:
+ case Token.SETPROP:
+ case Token.SETELEM:
+ case Token.CALL:
+ case Token.THROW:
+ case Token.RETHROW:
+ case Token.SETVAR:
+ case Token.CATCH_SCOPE:
+ case Token.RETURN_RESULT:
+ case Token.SET_REF:
+ case Token.DEL_REF:
+ case Token.REF_CALL:
+ case Token.TRY:
+ case Token.SEMI:
+ case Token.INC:
+ case Token.DEC:
+ case Token.EXPORT:
+ case Token.IMPORT:
+ case Token.IF:
+ case Token.ELSE:
+ case Token.SWITCH:
+ case Token.WHILE:
+ case Token.DO:
+ case Token.FOR:
+ case Token.BREAK:
+ case Token.CONTINUE:
+ case Token.VAR:
+ case Token.CONST:
+ case Token.LET:
+ case Token.LETEXPR:
+ case Token.WITH:
+ case Token.WITHEXPR:
+ case Token.CATCH:
+ case Token.FINALLY:
+ case Token.BLOCK:
+ case Token.LABEL:
+ case Token.TARGET:
+ case Token.LOOP:
+ case Token.JSR:
+ case Token.SETPROP_OP:
+ case Token.SETELEM_OP:
+ case Token.LOCAL_BLOCK:
+ case Token.SET_REF_OP:
+ case Token.YIELD:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ if (Token.printTrees) {
+ StringBuffer sb = new StringBuffer();
+ toString(new ObjToIntMap(), sb);
+ return sb.toString();
+ }
+ return String.valueOf(type);
+ }
+
+ private void toString(ObjToIntMap printIds, StringBuffer sb)
+ {
+ if (Token.printTrees) {
+ sb.append(Token.name(type));
+ if (this instanceof StringNode) {
+ sb.append(' ');
+ sb.append(getString());
+ Scope scope = getScope();
+ if (scope != null) {
+ sb.append("[scope: ");
+ appendPrintId(scope, printIds, sb);
+ sb.append("]");
+ }
+ } else if (this instanceof Node.Scope) {
+ if (this instanceof ScriptOrFnNode) {
+ ScriptOrFnNode sof = (ScriptOrFnNode)this;
+ if (this instanceof FunctionNode) {
+ FunctionNode fn = (FunctionNode)this;
+ sb.append(' ');
+ sb.append(fn.getFunctionName());
+ }
+ sb.append(" [source name: ");
+ sb.append(sof.getSourceName());
+ sb.append("] [encoded source length: ");
+ sb.append(sof.getEncodedSourceEnd()
+ - sof.getEncodedSourceStart());
+ sb.append("] [base line: ");
+ sb.append(sof.getBaseLineno());
+ sb.append("] [end line: ");
+ sb.append(sof.getEndLineno());
+ sb.append(']');
+ }
+ if (((Node.Scope)this).symbolTable != null) {
+ sb.append(" [scope ");
+ appendPrintId(this, printIds, sb);
+ sb.append(": ");
+ Iterator<String> iter =
+ ((Node.Scope) this).symbolTable.keySet().iterator();
+ while (iter.hasNext()) {
+ sb.append(iter.next());
+ sb.append(" ");
+ }
+ sb.append("]");
+ }
+ } else if (this instanceof Jump) {
+ Jump jump = (Jump)this;
+ if (type == Token.BREAK || type == Token.CONTINUE) {
+ sb.append(" [label: ");
+ appendPrintId(jump.getJumpStatement(), printIds, sb);
+ sb.append(']');
+ } else if (type == Token.TRY) {
+ Node catchNode = jump.target;
+ Node finallyTarget = jump.getFinally();
+ if (catchNode != null) {
+ sb.append(" [catch: ");
+ appendPrintId(catchNode, printIds, sb);
+ sb.append(']');
+ }
+ if (finallyTarget != null) {
+ sb.append(" [finally: ");
+ appendPrintId(finallyTarget, printIds, sb);
+ sb.append(']');
+ }
+ } else if (type == Token.LABEL || type == Token.LOOP
+ || type == Token.SWITCH)
+ {
+ sb.append(" [break: ");
+ appendPrintId(jump.target, printIds, sb);
+ sb.append(']');
+ if (type == Token.LOOP) {
+ sb.append(" [continue: ");
+ appendPrintId(jump.getContinue(), printIds, sb);
+ sb.append(']');
+ }
+ } else {
+ sb.append(" [target: ");
+ appendPrintId(jump.target, printIds, sb);
+ sb.append(']');
+ }
+ } else if (type == Token.NUMBER) {
+ sb.append(' ');
+ sb.append(getDouble());
+ } else if (type == Token.TARGET) {
+ sb.append(' ');
+ appendPrintId(this, printIds, sb);
+ }
+ if (lineno != -1) {
+ sb.append(' ');
+ sb.append(lineno);
+ }
+
+ for (PropListItem x = propListHead; x != null; x = x.next) {
+ int type = x.type;
+ sb.append(" [");
+ sb.append(propToString(type));
+ sb.append(": ");
+ String value;
+ switch (type) {
+ case TARGETBLOCK_PROP : // can't add this as it recurses
+ value = "target block property";
+ break;
+ case LOCAL_BLOCK_PROP : // can't add this as it is dull
+ value = "last local block";
+ break;
+ case ISNUMBER_PROP:
+ switch (x.intValue) {
+ case BOTH:
+ value = "both";
+ break;
+ case RIGHT:
+ value = "right";
+ break;
+ case LEFT:
+ value = "left";
+ break;
+ default:
+ throw Kit.codeBug();
+ }
+ break;
+ case SPECIALCALL_PROP:
+ switch (x.intValue) {
+ case SPECIALCALL_EVAL:
+ value = "eval";
+ break;
+ case SPECIALCALL_WITH:
+ value = "with";
+ break;
+ default:
+ // NON_SPECIALCALL should not be stored
+ throw Kit.codeBug();
+ }
+ break;
+ case OBJECT_IDS_PROP: {
+ Object[] a = (Object[]) x.objectValue;
+ value = "[";
+ for (int i=0; i < a.length; i++) {
+ value += a[i].toString();
+ if (i+1 < a.length)
+ value += ", ";
+ }
+ value += "]";
+ break;
+ }
+ default :
+ Object obj = x.objectValue;
+ if (obj != null) {
+ value = obj.toString();
+ } else {
+ value = String.valueOf(x.intValue);
+ }
+ break;
+ }
+ sb.append(value);
+ sb.append(']');
+ }
+ }
+ }
+
+ public String toStringTree(ScriptOrFnNode treeTop) {
+ if (Token.printTrees) {
+ StringBuffer sb = new StringBuffer();
+ toStringTreeHelper(treeTop, this, null, 0, sb);
+ return sb.toString();
+ }
+ return null;
+ }
+
+ private static void toStringTreeHelper(ScriptOrFnNode treeTop, Node n,
+ ObjToIntMap printIds,
+ int level, StringBuffer sb)
+ {
+ if (Token.printTrees) {
+ if (printIds == null) {
+ printIds = new ObjToIntMap();
+ generatePrintIds(treeTop, printIds);
+ }
+ for (int i = 0; i != level; ++i) {
+ sb.append(" ");
+ }
+ n.toString(printIds, sb);
+ sb.append('\n');
+ for (Node cursor = n.getFirstChild(); cursor != null;
+ cursor = cursor.getNext())
+ {
+ if (cursor.getType() == Token.FUNCTION) {
+ int fnIndex = cursor.getExistingIntProp(Node.FUNCTION_PROP);
+ FunctionNode fn = treeTop.getFunctionNode(fnIndex);
+ toStringTreeHelper(fn, fn, null, level + 1, sb);
+ } else {
+ toStringTreeHelper(treeTop, cursor, printIds, level + 1, sb);
+ }
+ }
+ }
+ }
+
+ private static void generatePrintIds(Node n, ObjToIntMap map)
+ {
+ if (Token.printTrees) {
+ map.put(n, map.size());
+ for (Node cursor = n.getFirstChild(); cursor != null;
+ cursor = cursor.getNext())
+ {
+ generatePrintIds(cursor, map);
+ }
+ }
+ }
+
+ private static void appendPrintId(Node n, ObjToIntMap printIds,
+ StringBuffer sb)
+ {
+ if (Token.printTrees) {
+ if (n != null) {
+ int id = printIds.get(n, -1);
+ sb.append('#');
+ if (id != -1) {
+ sb.append(id + 1);
+ } else {
+ sb.append("<not_available>");
+ }
+ }
+ }
+ }
+
+ int type; // type of the node; Token.NAME for example
+ Node next; // next sibling
+ private Node first; // first element of a linked list of children
+ private Node last; // last element of a linked list of children
+ protected int lineno = -1;
+
+ /**
+ * Linked list of properties. Since vast majority of nodes would have
+ * no more then 2 properties, linked list saves memory and provides
+ * fast lookup. If this does not holds, propListHead can be replaced
+ * by UintMap.
+ */
+ private PropListItem propListHead;
+}
diff --git a/src/org/mozilla/javascript/NodeTransformer.java b/src/org/mozilla/javascript/NodeTransformer.java
new file mode 100644
index 0000000..201c6f2
--- /dev/null
+++ b/src/org/mozilla/javascript/NodeTransformer.java
@@ -0,0 +1,565 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Bob Jervis
+ * Roger Lawrence
+ * Mike McCabe
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class transforms a tree to a lower-level representation for codegen.
+ *
+ * @see Node
+ * @author Norris Boyd
+ */
+
+public class NodeTransformer
+{
+
+ public NodeTransformer()
+ {
+ }
+
+ public final void transform(ScriptOrFnNode tree)
+ {
+ transformCompilationUnit(tree);
+ for (int i = 0; i != tree.getFunctionCount(); ++i) {
+ FunctionNode fn = tree.getFunctionNode(i);
+ transform(fn);
+ }
+ }
+
+ private void transformCompilationUnit(ScriptOrFnNode tree)
+ {
+ loops = new ObjArray();
+ loopEnds = new ObjArray();
+
+ // to save against upchecks if no finally blocks are used.
+ hasFinally = false;
+
+ // Flatten all only if we are not using scope objects for block scope
+ boolean createScopeObjects = tree.getType() != Token.FUNCTION ||
+ ((FunctionNode)tree).requiresActivation();
+ tree.flattenSymbolTable(!createScopeObjects);
+
+ //uncomment to print tree before transformation
+ //if (Token.printTrees) System.out.println(tree.toStringTree(tree));
+ transformCompilationUnit_r(tree, tree, tree, createScopeObjects);
+ }
+
+ private void transformCompilationUnit_r(final ScriptOrFnNode tree,
+ final Node parent,
+ Node.Scope scope,
+ boolean createScopeObjects)
+ {
+ Node node = null;
+ siblingLoop:
+ for (;;) {
+ Node previous = null;
+ if (node == null) {
+ node = parent.getFirstChild();
+ } else {
+ previous = node;
+ node = node.getNext();
+ }
+ if (node == null) {
+ break;
+ }
+
+ int type = node.getType();
+ if (createScopeObjects &&
+ (type == Token.BLOCK || type == Token.LOOP ||
+ type == Token.ARRAYCOMP) &&
+ (node instanceof Node.Scope))
+ {
+ Node.Scope newScope = (Node.Scope) node;
+ if (newScope.symbolTable != null) {
+ // transform to let statement so we get a with statement
+ // created to contain scoped let variables
+ Node let = new Node(type == Token.ARRAYCOMP ? Token.LETEXPR
+ : Token.LET);
+ Node innerLet = new Node(Token.LET);
+ let.addChildToBack(innerLet);
+ for (String name: newScope.symbolTable.keySet()) {
+ innerLet.addChildToBack(Node.newString(Token.NAME, name));
+ }
+ newScope.symbolTable = null; // so we don't transform again
+ Node oldNode = node;
+ node = replaceCurrent(parent, previous, node, let);
+ type = node.getType();
+ let.addChildToBack(oldNode);
+ }
+ }
+
+ switch (type) {
+
+ case Token.LABEL:
+ case Token.SWITCH:
+ case Token.LOOP:
+ loops.push(node);
+ loopEnds.push(((Node.Jump)node).target);
+ break;
+
+ case Token.WITH:
+ {
+ loops.push(node);
+ Node leave = node.getNext();
+ if (leave.getType() != Token.LEAVEWITH) {
+ Kit.codeBug();
+ }
+ loopEnds.push(leave);
+ break;
+ }
+
+ case Token.TRY:
+ {
+ Node.Jump jump = (Node.Jump)node;
+ Node finallytarget = jump.getFinally();
+ if (finallytarget != null) {
+ hasFinally = true;
+ loops.push(node);
+ loopEnds.push(finallytarget);
+ }
+ break;
+ }
+
+ case Token.TARGET:
+ case Token.LEAVEWITH:
+ if (!loopEnds.isEmpty() && loopEnds.peek() == node) {
+ loopEnds.pop();
+ loops.pop();
+ }
+ break;
+
+ case Token.YIELD:
+ ((FunctionNode)tree).addResumptionPoint(node);
+ break;
+
+ case Token.RETURN:
+ {
+ boolean isGenerator = tree.getType() == Token.FUNCTION
+ && ((FunctionNode)tree).isGenerator();
+ if (isGenerator) {
+ node.putIntProp(Node.GENERATOR_END_PROP, 1);
+ }
+ /* If we didn't support try/finally, it wouldn't be
+ * necessary to put LEAVEWITH nodes here... but as
+ * we do need a series of JSR FINALLY nodes before
+ * each RETURN, we need to ensure that each finally
+ * block gets the correct scope... which could mean
+ * that some LEAVEWITH nodes are necessary.
+ */
+ if (!hasFinally)
+ break; // skip the whole mess.
+ Node unwindBlock = null;
+ for (int i=loops.size()-1; i >= 0; i--) {
+ Node n = (Node) loops.get(i);
+ int elemtype = n.getType();
+ if (elemtype == Token.TRY || elemtype == Token.WITH) {
+ Node unwind;
+ if (elemtype == Token.TRY) {
+ Node.Jump jsrnode = new Node.Jump(Token.JSR);
+ Node jsrtarget = ((Node.Jump)n).getFinally();
+ jsrnode.target = jsrtarget;
+ unwind = jsrnode;
+ } else {
+ unwind = new Node(Token.LEAVEWITH);
+ }
+ if (unwindBlock == null) {
+ unwindBlock = new Node(Token.BLOCK,
+ node.getLineno());
+ }
+ unwindBlock.addChildToBack(unwind);
+ }
+ }
+ if (unwindBlock != null) {
+ Node returnNode = node;
+ Node returnExpr = returnNode.getFirstChild();
+ node = replaceCurrent(parent, previous, node, unwindBlock);
+ if (returnExpr == null || isGenerator) {
+ unwindBlock.addChildToBack(returnNode);
+ } else {
+ Node store = new Node(Token.EXPR_RESULT, returnExpr);
+ unwindBlock.addChildToFront(store);
+ returnNode = new Node(Token.RETURN_RESULT);
+ unwindBlock.addChildToBack(returnNode);
+ // transform return expression
+ transformCompilationUnit_r(tree, store, scope,
+ createScopeObjects);
+ }
+ // skip transformCompilationUnit_r to avoid infinite loop
+ continue siblingLoop;
+ }
+ break;
+ }
+
+ case Token.BREAK:
+ case Token.CONTINUE:
+ {
+ Node.Jump jump = (Node.Jump)node;
+ Node.Jump jumpStatement = jump.getJumpStatement();
+ if (jumpStatement == null) Kit.codeBug();
+
+ for (int i = loops.size(); ;) {
+ if (i == 0) {
+ // Parser/IRFactory ensure that break/continue
+ // always has a jump statement associated with it
+ // which should be found
+ throw Kit.codeBug();
+ }
+ --i;
+ Node n = (Node) loops.get(i);
+ if (n == jumpStatement) {
+ break;
+ }
+
+ int elemtype = n.getType();
+ if (elemtype == Token.WITH) {
+ Node leave = new Node(Token.LEAVEWITH);
+ previous = addBeforeCurrent(parent, previous, node,
+ leave);
+ } else if (elemtype == Token.TRY) {
+ Node.Jump tryNode = (Node.Jump)n;
+ Node.Jump jsrFinally = new Node.Jump(Token.JSR);
+ jsrFinally.target = tryNode.getFinally();
+ previous = addBeforeCurrent(parent, previous, node,
+ jsrFinally);
+ }
+ }
+
+ if (type == Token.BREAK) {
+ jump.target = jumpStatement.target;
+ } else {
+ jump.target = jumpStatement.getContinue();
+ }
+ jump.setType(Token.GOTO);
+
+ break;
+ }
+
+ case Token.CALL:
+ visitCall(node, tree);
+ break;
+
+ case Token.NEW:
+ visitNew(node, tree);
+ break;
+
+ case Token.LETEXPR:
+ case Token.LET: {
+ Node child = node.getFirstChild();
+ if (child.getType() == Token.LET) {
+ // We have a let statement or expression rather than a
+ // let declaration
+ boolean createWith = tree.getType() != Token.FUNCTION
+ || ((FunctionNode)tree).requiresActivation();
+ node = visitLet(createWith, parent, previous, node);
+ break;
+ } else {
+ // fall through to process let declaration...
+ }
+ }
+ /* fall through */
+ case Token.CONST:
+ case Token.VAR:
+ {
+ Node result = new Node(Token.BLOCK);
+ for (Node cursor = node.getFirstChild(); cursor != null;) {
+ // Move cursor to next before createAssignment gets chance
+ // to change n.next
+ Node n = cursor;
+ cursor = cursor.getNext();
+ if (n.getType() == Token.NAME) {
+ if (!n.hasChildren())
+ continue;
+ Node init = n.getFirstChild();
+ n.removeChild(init);
+ n.setType(Token.BINDNAME);
+ n = new Node(type == Token.CONST ?
+ Token.SETCONST :
+ Token.SETNAME,
+ n, init);
+ } else {
+ // May be a destructuring assignment already transformed
+ // to a LETEXPR
+ if (n.getType() != Token.LETEXPR)
+ throw Kit.codeBug();
+ }
+ Node pop = new Node(Token.EXPR_VOID, n, node.getLineno());
+ result.addChildToBack(pop);
+ }
+ node = replaceCurrent(parent, previous, node, result);
+ break;
+ }
+
+ case Token.TYPEOFNAME: {
+ Node.Scope defining = scope.getDefiningScope(node.getString());
+ if (defining != null) {
+ node.setScope(defining);
+ }
+ }
+ break;
+
+ case Token.TYPEOF:
+ case Token.IFNE: {
+ /* We want to suppress warnings for undefined property o.p
+ * for the following constructs: typeof o.p, if (o.p),
+ * if (!o.p), if (o.p == undefined), if (undefined == o.p)
+ */
+ Node child = node.getFirstChild();
+ if (type == Token.IFNE) {
+ while (child.getType() == Token.NOT) {
+ child = child.getFirstChild();
+ }
+ if (child.getType() == Token.EQ ||
+ child.getType() == Token.NE)
+ {
+ Node first = child.getFirstChild();
+ Node last = child.getLastChild();
+ if (first.getType() == Token.NAME &&
+ first.getString().equals("undefined"))
+ child = last;
+ else if (last.getType() == Token.NAME &&
+ last.getString().equals("undefined"))
+ child = first;
+ }
+ }
+ if (child.getType() == Token.GETPROP)
+ child.setType(Token.GETPROPNOWARN);
+ break;
+ }
+
+ case Token.NAME:
+ case Token.SETNAME:
+ case Token.SETCONST:
+ case Token.DELPROP:
+ {
+ // Turn name to var for faster access if possible
+ if (createScopeObjects) {
+ break;
+ }
+ Node nameSource;
+ if (type == Token.NAME) {
+ nameSource = node;
+ } else {
+ nameSource = node.getFirstChild();
+ if (nameSource.getType() != Token.BINDNAME) {
+ if (type == Token.DELPROP) {
+ break;
+ }
+ throw Kit.codeBug();
+ }
+ }
+ if (nameSource.getScope() != null) {
+ break; // already have a scope set
+ }
+ String name = nameSource.getString();
+ Node.Scope defining = scope.getDefiningScope(name);
+ if (defining != null) {
+ nameSource.setScope(defining);
+ if (type == Token.NAME) {
+ node.setType(Token.GETVAR);
+ } else if (type == Token.SETNAME) {
+ node.setType(Token.SETVAR);
+ nameSource.setType(Token.STRING);
+ } else if (type == Token.SETCONST) {
+ node.setType(Token.SETCONSTVAR);
+ nameSource.setType(Token.STRING);
+ } else if (type == Token.DELPROP) {
+ // Local variables are by definition permanent
+ Node n = new Node(Token.FALSE);
+ node = replaceCurrent(parent, previous, node, n);
+ } else {
+ throw Kit.codeBug();
+ }
+ }
+ break;
+ }
+ }
+
+ transformCompilationUnit_r(tree, node,
+ node instanceof Node.Scope ? (Node.Scope)node : scope,
+ createScopeObjects);
+ }
+ }
+
+ protected void visitNew(Node node, ScriptOrFnNode tree) {
+ }
+
+ protected void visitCall(Node node, ScriptOrFnNode tree) {
+ }
+
+ protected Node visitLet(boolean createWith, Node parent, Node previous,
+ Node scopeNode)
+ {
+ Node vars = scopeNode.getFirstChild();
+ Node body = vars.getNext();
+ scopeNode.removeChild(vars);
+ scopeNode.removeChild(body);
+ boolean isExpression = scopeNode.getType() == Token.LETEXPR;
+ Node result;
+ Node newVars;
+ if (createWith) {
+ result = new Node(isExpression ? Token.WITHEXPR : Token.BLOCK);
+ result = replaceCurrent(parent, previous, scopeNode, result);
+ ArrayList<Object> list = new ArrayList<Object>();
+ Node objectLiteral = new Node(Token.OBJECTLIT);
+ for (Node v=vars.getFirstChild(); v != null; v = v.getNext()) {
+ Node current = v;
+ if (current.getType() == Token.LETEXPR) {
+ // destructuring in let expr, e.g. let ([x, y] = [3, 4]) {}
+ List<?> destructuringNames = (List<?>)
+ current.getProp(Node.DESTRUCTURING_NAMES);
+ Node c = current.getFirstChild();
+ if (c.getType() != Token.LET) throw Kit.codeBug();
+ // Add initialization code to front of body
+ if (isExpression) {
+ body = new Node(Token.COMMA, c.getNext(), body);
+ } else {
+ body = new Node(Token.BLOCK,
+ new Node(Token.EXPR_VOID, c.getNext()),
+ body);
+ }
+ // Update "list" and "objectLiteral" for the variables
+ // defined in the destructuring assignment
+ if (destructuringNames != null) {
+ list.addAll(destructuringNames);
+ for (int i=0; i < destructuringNames.size(); i++) {
+ objectLiteral.addChildToBack(
+ new Node(Token.VOID, Node.newNumber(0.0)));
+ }
+ }
+ current = c.getFirstChild(); // should be a NAME, checked below
+ }
+ if (current.getType() != Token.NAME) throw Kit.codeBug();
+ list.add(ScriptRuntime.getIndexObject(current.getString()));
+ Node init = current.getFirstChild();
+ if (init == null) {
+ init = new Node(Token.VOID, Node.newNumber(0.0));
+ }
+ objectLiteral.addChildToBack(init);
+ }
+ objectLiteral.putProp(Node.OBJECT_IDS_PROP, list.toArray());
+ newVars = new Node(Token.ENTERWITH, objectLiteral);
+ result.addChildToBack(newVars);
+ result.addChildToBack(new Node(Token.WITH, body));
+ result.addChildToBack(new Node(Token.LEAVEWITH));
+ } else {
+ result = new Node(isExpression ? Token.COMMA : Token.BLOCK);
+ result = replaceCurrent(parent, previous, scopeNode, result);
+ newVars = new Node(Token.COMMA);
+ for (Node v=vars.getFirstChild(); v != null; v = v.getNext()) {
+ Node current = v;
+ if (current.getType() == Token.LETEXPR) {
+ // destructuring in let expr, e.g. let ([x, y] = [3, 4]) {}
+ Node c = current.getFirstChild();
+ if (c.getType() != Token.LET) throw Kit.codeBug();
+ // Add initialization code to front of body
+ if (isExpression) {
+ body = new Node(Token.COMMA, c.getNext(), body);
+ } else {
+ body = new Node(Token.BLOCK,
+ new Node(Token.EXPR_VOID, c.getNext()),
+ body);
+ }
+ // We're removing the LETEXPR, so move the symbols
+ Node.Scope.joinScopes((Node.Scope)current,
+ (Node.Scope)scopeNode);
+ current = c.getFirstChild(); // should be a NAME, checked below
+ }
+ if (current.getType() != Token.NAME) throw Kit.codeBug();
+ Node stringNode = Node.newString(current.getString());
+ stringNode.setScope((Node.Scope)scopeNode);
+ Node init = current.getFirstChild();
+ if (init == null) {
+ init = new Node(Token.VOID, Node.newNumber(0.0));
+ }
+ newVars.addChildToBack(new Node(Token.SETVAR, stringNode, init));
+ }
+ if (isExpression) {
+ result.addChildToBack(newVars);
+ scopeNode.setType(Token.COMMA);
+ result.addChildToBack(scopeNode);
+ scopeNode.addChildToBack(body);
+ } else {
+ result.addChildToBack(new Node(Token.EXPR_VOID, newVars));
+ scopeNode.setType(Token.BLOCK);
+ result.addChildToBack(scopeNode);
+ scopeNode.addChildrenToBack(body);
+ }
+ }
+ return result;
+ }
+
+ private static Node addBeforeCurrent(Node parent, Node previous,
+ Node current, Node toAdd)
+ {
+ if (previous == null) {
+ if (!(current == parent.getFirstChild())) Kit.codeBug();
+ parent.addChildToFront(toAdd);
+ } else {
+ if (!(current == previous.getNext())) Kit.codeBug();
+ parent.addChildAfter(toAdd, previous);
+ }
+ return toAdd;
+ }
+
+ private static Node replaceCurrent(Node parent, Node previous,
+ Node current, Node replacement)
+ {
+ if (previous == null) {
+ if (!(current == parent.getFirstChild())) Kit.codeBug();
+ parent.replaceChild(current, replacement);
+ } else if (previous.next == current) {
+ // Check cachedPrev.next == current is necessary due to possible
+ // tree mutations
+ parent.replaceChildAfter(previous, replacement);
+ } else {
+ parent.replaceChild(current, replacement);
+ }
+ return replacement;
+ }
+
+ private ObjArray loops;
+ private ObjArray loopEnds;
+ private boolean hasFinally;
+}
diff --git a/src/org/mozilla/javascript/ObjArray.java b/src/org/mozilla/javascript/ObjArray.java
new file mode 100644
index 0000000..a9636a3
--- /dev/null
+++ b/src/org/mozilla/javascript/ObjArray.java
@@ -0,0 +1,388 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.Serializable;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+Implementation of resizable array with focus on minimizing memory usage by storing few initial array elements in object fields. Can also be used as a stack.
+*/
+
+public class ObjArray implements Serializable
+{
+ static final long serialVersionUID = 4174889037736658296L;
+
+ public ObjArray() { }
+
+ public final boolean isSealed()
+ {
+ return sealed;
+ }
+
+ public final void seal()
+ {
+ sealed = true;
+ }
+
+ public final boolean isEmpty()
+ {
+ return size == 0;
+ }
+
+ public final int size()
+ {
+ return size;
+ }
+
+ public final void setSize(int newSize)
+ {
+ if (newSize < 0) throw new IllegalArgumentException();
+ if (sealed) throw onSeledMutation();
+ int N = size;
+ if (newSize < N) {
+ for (int i = newSize; i != N; ++i) {
+ setImpl(i, null);
+ }
+ } else if (newSize > N) {
+ if (newSize > FIELDS_STORE_SIZE) {
+ ensureCapacity(newSize);
+ }
+ }
+ size = newSize;
+ }
+
+ public final Object get(int index)
+ {
+ if (!(0 <= index && index < size)) throw onInvalidIndex(index, size);
+ return getImpl(index);
+ }
+
+ public final void set(int index, Object value)
+ {
+ if (!(0 <= index && index < size)) throw onInvalidIndex(index, size);
+ if (sealed) throw onSeledMutation();
+ setImpl(index, value);
+ }
+
+ private Object getImpl(int index)
+ {
+ switch (index) {
+ case 0: return f0;
+ case 1: return f1;
+ case 2: return f2;
+ case 3: return f3;
+ case 4: return f4;
+ }
+ return data[index - FIELDS_STORE_SIZE];
+ }
+
+ private void setImpl(int index, Object value)
+ {
+ switch (index) {
+ case 0: f0 = value; break;
+ case 1: f1 = value; break;
+ case 2: f2 = value; break;
+ case 3: f3 = value; break;
+ case 4: f4 = value; break;
+ default: data[index - FIELDS_STORE_SIZE] = value;
+ }
+
+ }
+
+ public int indexOf(Object obj)
+ {
+ int N = size;
+ for (int i = 0; i != N; ++i) {
+ Object current = getImpl(i);
+ if (current == obj || (current != null && current.equals(obj))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public int lastIndexOf(Object obj)
+ {
+ for (int i = size; i != 0;) {
+ --i;
+ Object current = getImpl(i);
+ if (current == obj || (current != null && current.equals(obj))) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public final Object peek()
+ {
+ int N = size;
+ if (N == 0) throw onEmptyStackTopRead();
+ return getImpl(N - 1);
+ }
+
+ public final Object pop()
+ {
+ if (sealed) throw onSeledMutation();
+ int N = size;
+ --N;
+ Object top;
+ switch (N) {
+ case -1: throw onEmptyStackTopRead();
+ case 0: top = f0; f0 = null; break;
+ case 1: top = f1; f1 = null; break;
+ case 2: top = f2; f2 = null; break;
+ case 3: top = f3; f3 = null; break;
+ case 4: top = f4; f4 = null; break;
+ default:
+ top = data[N - FIELDS_STORE_SIZE];
+ data[N - FIELDS_STORE_SIZE] = null;
+ }
+ size = N;
+ return top;
+ }
+
+ public final void push(Object value)
+ {
+ add(value);
+ }
+
+ public final void add(Object value)
+ {
+ if (sealed) throw onSeledMutation();
+ int N = size;
+ if (N >= FIELDS_STORE_SIZE) {
+ ensureCapacity(N + 1);
+ }
+ size = N + 1;
+ setImpl(N, value);
+ }
+
+ public final void add(int index, Object value)
+ {
+ int N = size;
+ if (!(0 <= index && index <= N)) throw onInvalidIndex(index, N + 1);
+ if (sealed) throw onSeledMutation();
+ Object tmp;
+ switch (index) {
+ case 0:
+ if (N == 0) { f0 = value; break; }
+ tmp = f0; f0 = value; value = tmp;
+ case 1:
+ if (N == 1) { f1 = value; break; }
+ tmp = f1; f1 = value; value = tmp;
+ case 2:
+ if (N == 2) { f2 = value; break; }
+ tmp = f2; f2 = value; value = tmp;
+ case 3:
+ if (N == 3) { f3 = value; break; }
+ tmp = f3; f3 = value; value = tmp;
+ case 4:
+ if (N == 4) { f4 = value; break; }
+ tmp = f4; f4 = value; value = tmp;
+
+ index = FIELDS_STORE_SIZE;
+ default:
+ ensureCapacity(N + 1);
+ if (index != N) {
+ System.arraycopy(data, index - FIELDS_STORE_SIZE,
+ data, index - FIELDS_STORE_SIZE + 1,
+ N - index);
+ }
+ data[index - FIELDS_STORE_SIZE] = value;
+ }
+ size = N + 1;
+ }
+
+ public final void remove(int index)
+ {
+ int N = size;
+ if (!(0 <= index && index < N)) throw onInvalidIndex(index, N);
+ if (sealed) throw onSeledMutation();
+ --N;
+ switch (index) {
+ case 0:
+ if (N == 0) { f0 = null; break; }
+ f0 = f1;
+ case 1:
+ if (N == 1) { f1 = null; break; }
+ f1 = f2;
+ case 2:
+ if (N == 2) { f2 = null; break; }
+ f2 = f3;
+ case 3:
+ if (N == 3) { f3 = null; break; }
+ f3 = f4;
+ case 4:
+ if (N == 4) { f4 = null; break; }
+ f4 = data[0];
+
+ index = FIELDS_STORE_SIZE;
+ default:
+ if (index != N) {
+ System.arraycopy(data, index - FIELDS_STORE_SIZE + 1,
+ data, index - FIELDS_STORE_SIZE,
+ N - index);
+ }
+ data[N - FIELDS_STORE_SIZE] = null;
+ }
+ size = N;
+ }
+
+ public final void clear()
+ {
+ if (sealed) throw onSeledMutation();
+ int N = size;
+ for (int i = 0; i != N; ++i) {
+ setImpl(i, null);
+ }
+ size = 0;
+ }
+
+ public final Object[] toArray()
+ {
+ Object[] array = new Object[size];
+ toArray(array, 0);
+ return array;
+ }
+
+ public final void toArray(Object[] array)
+ {
+ toArray(array, 0);
+ }
+
+ public final void toArray(Object[] array, int offset)
+ {
+ int N = size;
+ switch (N) {
+ default:
+ System.arraycopy(data, 0, array, offset + FIELDS_STORE_SIZE,
+ N - FIELDS_STORE_SIZE);
+ case 5: array[offset + 4] = f4;
+ case 4: array[offset + 3] = f3;
+ case 3: array[offset + 2] = f2;
+ case 2: array[offset + 1] = f1;
+ case 1: array[offset + 0] = f0;
+ case 0: break;
+ }
+ }
+
+ private void ensureCapacity(int minimalCapacity)
+ {
+ int required = minimalCapacity - FIELDS_STORE_SIZE;
+ if (required <= 0) throw new IllegalArgumentException();
+ if (data == null) {
+ int alloc = FIELDS_STORE_SIZE * 2;
+ if (alloc < required) {
+ alloc = required;
+ }
+ data = new Object[alloc];
+ } else {
+ int alloc = data.length;
+ if (alloc < required) {
+ if (alloc <= FIELDS_STORE_SIZE) {
+ alloc = FIELDS_STORE_SIZE * 2;
+ } else {
+ alloc *= 2;
+ }
+ if (alloc < required) {
+ alloc = required;
+ }
+ Object[] tmp = new Object[alloc];
+ if (size > FIELDS_STORE_SIZE) {
+ System.arraycopy(data, 0, tmp, 0,
+ size - FIELDS_STORE_SIZE);
+ }
+ data = tmp;
+ }
+ }
+ }
+
+ private static RuntimeException onInvalidIndex(int index, int upperBound)
+ {
+ // \u2209 is "NOT ELEMENT OF"
+ String msg = index+" \u2209 [0, "+upperBound+')';
+ throw new IndexOutOfBoundsException(msg);
+ }
+
+ private static RuntimeException onEmptyStackTopRead()
+ {
+ throw new RuntimeException("Empty stack");
+ }
+
+ private static RuntimeException onSeledMutation()
+ {
+ throw new IllegalStateException("Attempt to modify sealed array");
+ }
+
+ private void writeObject(ObjectOutputStream os) throws IOException
+ {
+ os.defaultWriteObject();
+ int N = size;
+ for (int i = 0; i != N; ++i) {
+ Object obj = getImpl(i);
+ os.writeObject(obj);
+ }
+ }
+
+ private void readObject(ObjectInputStream is)
+ throws IOException, ClassNotFoundException
+ {
+ is.defaultReadObject(); // It reads size
+ int N = size;
+ if (N > FIELDS_STORE_SIZE) {
+ data = new Object[N - FIELDS_STORE_SIZE];
+ }
+ for (int i = 0; i != N; ++i) {
+ Object obj = is.readObject();
+ setImpl(i, obj);
+ }
+ }
+
+// Number of data elements
+ private int size;
+
+ private boolean sealed;
+
+ private static final int FIELDS_STORE_SIZE = 5;
+ private transient Object f0, f1, f2, f3, f4;
+ private transient Object[] data;
+}
diff --git a/src/org/mozilla/javascript/ObjToIntMap.java b/src/org/mozilla/javascript/ObjToIntMap.java
new file mode 100644
index 0000000..4aa7d23
--- /dev/null
+++ b/src/org/mozilla/javascript/ObjToIntMap.java
@@ -0,0 +1,697 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.Serializable;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * Map to associate objects to integers.
+ * The map does not synchronize any of its operation, so either use
+ * it from a single thread or do own synchronization or perform all mutation
+ * operations on one thread before passing the map to others
+ *
+ * @author Igor Bukanov
+ *
+ */
+
+public class ObjToIntMap implements Serializable
+{
+ static final long serialVersionUID = -1542220580748809402L;
+
+// Map implementation via hashtable,
+// follows "The Art of Computer Programming" by Donald E. Knuth
+
+// ObjToIntMap is a copy cat of ObjToIntMap with API adjusted to object keys
+
+ public static class Iterator {
+
+ Iterator(ObjToIntMap master) {
+ this.master = master;
+ }
+
+ final void init(Object[] keys, int[] values, int keyCount) {
+ this.keys = keys;
+ this.values = values;
+ this.cursor = -1;
+ this.remaining = keyCount;
+ }
+
+ public void start() {
+ master.initIterator(this);
+ next();
+ }
+
+ public boolean done() {
+ return remaining < 0;
+ }
+
+ public void next() {
+ if (remaining == -1) Kit.codeBug();
+ if (remaining == 0) {
+ remaining = -1;
+ cursor = -1;
+ }else {
+ for (++cursor; ; ++cursor) {
+ Object key = keys[cursor];
+ if (key != null && key != DELETED) {
+ --remaining;
+ break;
+ }
+ }
+ }
+ }
+
+ public Object getKey() {
+ Object key = keys[cursor];
+ if (key == UniqueTag.NULL_VALUE) { key = null; }
+ return key;
+ }
+
+ public int getValue() {
+ return values[cursor];
+ }
+
+ public void setValue(int value) {
+ values[cursor] = value;
+ }
+
+ ObjToIntMap master;
+ private int cursor;
+ private int remaining;
+ private Object[] keys;
+ private int[] values;
+ }
+
+ public ObjToIntMap() {
+ this(4);
+ }
+
+ public ObjToIntMap(int keyCountHint) {
+ if (keyCountHint < 0) Kit.codeBug();
+ // Table grow when number of stored keys >= 3/4 of max capacity
+ int minimalCapacity = keyCountHint * 4 / 3;
+ int i;
+ for (i = 2; (1 << i) < minimalCapacity; ++i) { }
+ power = i;
+ if (check && power < 2) Kit.codeBug();
+ }
+
+ public boolean isEmpty() {
+ return keyCount == 0;
+ }
+
+ public int size() {
+ return keyCount;
+ }
+
+ public boolean has(Object key) {
+ if (key == null) { key = UniqueTag.NULL_VALUE; }
+ return 0 <= findIndex(key);
+ }
+
+ /**
+ * Get integer value assigned with key.
+ * @return key integer value or defaultValue if key is absent
+ */
+ public int get(Object key, int defaultValue) {
+ if (key == null) { key = UniqueTag.NULL_VALUE; }
+ int index = findIndex(key);
+ if (0 <= index) {
+ return values[index];
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Get integer value assigned with key.
+ * @return key integer value
+ * @throws RuntimeException if key does not exist
+ */
+ public int getExisting(Object key) {
+ if (key == null) { key = UniqueTag.NULL_VALUE; }
+ int index = findIndex(key);
+ if (0 <= index) {
+ return values[index];
+ }
+ // Key must exist
+ Kit.codeBug();
+ return 0;
+ }
+
+ public void put(Object key, int value) {
+ if (key == null) { key = UniqueTag.NULL_VALUE; }
+ int index = ensureIndex(key);
+ values[index] = value;
+ }
+
+ /**
+ * If table already contains a key that equals to keyArg, return that key
+ * while setting its value to zero, otherwise add keyArg with 0 value to
+ * the table and return it.
+ */
+ public Object intern(Object keyArg) {
+ boolean nullKey = false;
+ if (keyArg == null) {
+ nullKey = true;
+ keyArg = UniqueTag.NULL_VALUE;
+ }
+ int index = ensureIndex(keyArg);
+ values[index] = 0;
+ return (nullKey) ? null : keys[index];
+ }
+
+ public void remove(Object key) {
+ if (key == null) { key = UniqueTag.NULL_VALUE; }
+ int index = findIndex(key);
+ if (0 <= index) {
+ keys[index] = DELETED;
+ --keyCount;
+ }
+ }
+
+ public void clear() {
+ int i = keys.length;
+ while (i != 0) {
+ keys[--i] = null;
+ }
+ keyCount = 0;
+ occupiedCount = 0;
+ }
+
+ public Iterator newIterator() {
+ return new Iterator(this);
+ }
+
+ // The sole purpose of the method is to avoid accessing private fields
+ // from the Iterator inner class to workaround JDK 1.1 compiler bug which
+ // generates code triggering VerifierError on recent JVMs
+ final void initIterator(Iterator i) {
+ i.init(keys, values, keyCount);
+ }
+
+ /** Return array of present keys */
+ public Object[] getKeys() {
+ Object[] array = new Object[keyCount];
+ getKeys(array, 0);
+ return array;
+ }
+
+ public void getKeys(Object[] array, int offset) {
+ int count = keyCount;
+ for (int i = 0; count != 0; ++i) {
+ Object key = keys[i];
+ if (key != null && key != DELETED) {
+ if (key == UniqueTag.NULL_VALUE) { key = null; }
+ array[offset] = key;
+ ++offset;
+ --count;
+ }
+ }
+ }
+
+ private static int tableLookupStep(int fraction, int mask, int power) {
+ int shift = 32 - 2 * power;
+ if (shift >= 0) {
+ return ((fraction >>> shift) & mask) | 1;
+ }
+ else {
+ return (fraction & (mask >>> -shift)) | 1;
+ }
+ }
+
+ private int findIndex(Object key) {
+ if (keys != null) {
+ int hash = key.hashCode();
+ int fraction = hash * A;
+ int index = fraction >>> (32 - power);
+ Object test = keys[index];
+ if (test != null) {
+ int N = 1 << power;
+ if (test == key
+ || (values[N + index] == hash && test.equals(key)))
+ {
+ return index;
+ }
+ // Search in table after first failed attempt
+ int mask = N - 1;
+ int step = tableLookupStep(fraction, mask, power);
+ int n = 0;
+ for (;;) {
+ if (check) {
+ if (n >= occupiedCount) Kit.codeBug();
+ ++n;
+ }
+ index = (index + step) & mask;
+ test = keys[index];
+ if (test == null) {
+ break;
+ }
+ if (test == key
+ || (values[N + index] == hash && test.equals(key)))
+ {
+ return index;
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
+// Insert key that is not present to table without deleted entries
+// and enough free space
+ private int insertNewKey(Object key, int hash) {
+ if (check && occupiedCount != keyCount) Kit.codeBug();
+ if (check && keyCount == 1 << power) Kit.codeBug();
+ int fraction = hash * A;
+ int index = fraction >>> (32 - power);
+ int N = 1 << power;
+ if (keys[index] != null) {
+ int mask = N - 1;
+ int step = tableLookupStep(fraction, mask, power);
+ int firstIndex = index;
+ do {
+ if (check && keys[index] == DELETED) Kit.codeBug();
+ index = (index + step) & mask;
+ if (check && firstIndex == index) Kit.codeBug();
+ } while (keys[index] != null);
+ }
+ keys[index] = key;
+ values[N + index] = hash;
+ ++occupiedCount;
+ ++keyCount;
+
+ return index;
+ }
+
+ private void rehashTable() {
+ if (keys == null) {
+ if (check && keyCount != 0) Kit.codeBug();
+ if (check && occupiedCount != 0) Kit.codeBug();
+ int N = 1 << power;
+ keys = new Object[N];
+ values = new int[2 * N];
+ }
+ else {
+ // Check if removing deleted entries would free enough space
+ if (keyCount * 2 >= occupiedCount) {
+ // Need to grow: less then half of deleted entries
+ ++power;
+ }
+ int N = 1 << power;
+ Object[] oldKeys = keys;
+ int[] oldValues = values;
+ int oldN = oldKeys.length;
+ keys = new Object[N];
+ values = new int[2 * N];
+
+ int remaining = keyCount;
+ occupiedCount = keyCount = 0;
+ for (int i = 0; remaining != 0; ++i) {
+ Object key = oldKeys[i];
+ if (key != null && key != DELETED) {
+ int keyHash = oldValues[oldN + i];
+ int index = insertNewKey(key, keyHash);
+ values[index] = oldValues[i];
+ --remaining;
+ }
+ }
+ }
+ }
+
+// Ensure key index creating one if necessary
+ private int ensureIndex(Object key) {
+ int hash = key.hashCode();
+ int index = -1;
+ int firstDeleted = -1;
+ if (keys != null) {
+ int fraction = hash * A;
+ index = fraction >>> (32 - power);
+ Object test = keys[index];
+ if (test != null) {
+ int N = 1 << power;
+ if (test == key
+ || (values[N + index] == hash && test.equals(key)))
+ {
+ return index;
+ }
+ if (test == DELETED) {
+ firstDeleted = index;
+ }
+
+ // Search in table after first failed attempt
+ int mask = N - 1;
+ int step = tableLookupStep(fraction, mask, power);
+ int n = 0;
+ for (;;) {
+ if (check) {
+ if (n >= occupiedCount) Kit.codeBug();
+ ++n;
+ }
+ index = (index + step) & mask;
+ test = keys[index];
+ if (test == null) {
+ break;
+ }
+ if (test == key
+ || (values[N + index] == hash && test.equals(key)))
+ {
+ return index;
+ }
+ if (test == DELETED && firstDeleted < 0) {
+ firstDeleted = index;
+ }
+ }
+ }
+ }
+ // Inserting of new key
+ if (check && keys != null && keys[index] != null)
+ Kit.codeBug();
+ if (firstDeleted >= 0) {
+ index = firstDeleted;
+ }
+ else {
+ // Need to consume empty entry: check occupation level
+ if (keys == null || occupiedCount * 4 >= (1 << power) * 3) {
+ // Too litle unused entries: rehash
+ rehashTable();
+ return insertNewKey(key, hash);
+ }
+ ++occupiedCount;
+ }
+ keys[index] = key;
+ values[(1 << power) + index] = hash;
+ ++keyCount;
+ return index;
+ }
+
+ private void writeObject(ObjectOutputStream out)
+ throws IOException
+ {
+ out.defaultWriteObject();
+
+ int count = keyCount;
+ for (int i = 0; count != 0; ++i) {
+ Object key = keys[i];
+ if (key != null && key != DELETED) {
+ --count;
+ out.writeObject(key);
+ out.writeInt(values[i]);
+ }
+ }
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+
+ int writtenKeyCount = keyCount;
+ if (writtenKeyCount != 0) {
+ keyCount = 0;
+ int N = 1 << power;
+ keys = new Object[N];
+ values = new int[2 * N];
+ for (int i = 0; i != writtenKeyCount; ++i) {
+ Object key = in.readObject();
+ int hash = key.hashCode();
+ int index = insertNewKey(key, hash);
+ values[index] = in.readInt();
+ }
+ }
+ }
+
+// A == golden_ratio * (1 << 32) = ((sqrt(5) - 1) / 2) * (1 << 32)
+// See Knuth etc.
+ private static final int A = 0x9e3779b9;
+
+ private static final Object DELETED = new Object();
+
+// Structure of kyes and values arrays (N == 1 << power):
+// keys[0 <= i < N]: key value or null or DELETED mark
+// values[0 <= i < N]: value of key at keys[i]
+// values[N <= i < 2*N]: hash code of key at keys[i-N]
+
+ private transient Object[] keys;
+ private transient int[] values;
+
+ private int power;
+ private int keyCount;
+ private transient int occupiedCount; // == keyCount + deleted_count
+
+// If true, enables consitency checks
+ private static final boolean check = false;
+
+/* TEST START
+
+ public static void main(String[] args) {
+ if (!check) {
+ System.err.println("Set check to true and re-run");
+ throw new RuntimeException("Set check to true and re-run");
+ }
+
+ ObjToIntMap map;
+ map = new ObjToIntMap(0);
+ testHash(map, 3);
+ map = new ObjToIntMap(0);
+ testHash(map, 10 * 1000);
+ map = new ObjToIntMap();
+ testHash(map, 10 * 1000);
+ map = new ObjToIntMap(30 * 1000);
+ testHash(map, 10 * 100);
+ map.clear();
+ testHash(map, 4);
+ map = new ObjToIntMap(0);
+ testHash(map, 10 * 100);
+ }
+
+ private static void testHash(ObjToIntMap map, int N) {
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(i);
+ check(-1 == map.get(key, -1));
+ map.put(key, i);
+ check(i == map.get(key, -1));
+ }
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(i);
+ map.put(key, i);
+ check(i == map.get(key, -1));
+ }
+
+ check(map.size() == N);
+
+ System.out.print("."); System.out.flush();
+ Object[] keys = map.getKeys();
+ check(keys.length == N);
+ for (int i = 0; i != N; ++i) {
+ Object key = keys[i];
+ check(map.has(key));
+ }
+
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(i);
+ check(i == map.get(key, -1));
+ }
+
+ int Nsqrt = -1;
+ for (int i = 0; ; ++i) {
+ if (i * i >= N) {
+ Nsqrt = i;
+ break;
+ }
+ }
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(i * i);
+ map.put(key, i);
+ check(i == map.get(key, -1));
+ }
+
+ check(map.size() == 2 * N - Nsqrt);
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(i * i);
+ check(i == map.get(key, -1));
+ }
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(-1 - i * i);
+ map.put(key, i);
+ check(i == map.get(key, -1));
+ }
+
+ check(map.size() == 3 * N - Nsqrt);
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(-1 - i * i);
+ map.remove(key);
+ check(!map.has(key));
+ }
+
+ check(map.size() == 2 * N - Nsqrt);
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(i * i);
+ check(i == map.get(key, -1));
+ }
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(i);
+ int j = intSqrt(i);
+ if (j * j == i) {
+ check(j == map.get(key, -1));
+ }else {
+ check(i == map.get(key, -1));
+ }
+ }
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(i * i);
+ map.remove(key);
+ check(-2 == map.get(key, -2));
+ }
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(i);
+ map.put(key, i);
+ check(i == map.get(key, -2));
+ }
+
+ check(map.size() == N);
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(i);
+ check(i == map.get(key, -1));
+ }
+
+ System.out.print("."); System.out.flush();
+ ObjToIntMap copy = (ObjToIntMap)writeAndRead(map);
+ check(copy.size() == N);
+
+ for (int i = 0; i != N; ++i) {
+ Object key = testKey(i);
+ check(i == copy.get(key, -1));
+ }
+
+ System.out.print("."); System.out.flush();
+ checkSameMaps(copy, map);
+
+ System.out.println(); System.out.flush();
+ }
+
+ private static void checkSameMaps(ObjToIntMap map1, ObjToIntMap map2) {
+ check(map1.size() == map2.size());
+ Object[] keys = map1.getKeys();
+ check(keys.length == map1.size());
+ for (int i = 0; i != keys.length; ++i) {
+ check(map1.get(keys[i], -1) == map2.get(keys[i], -1));
+ }
+ }
+
+ private static void check(boolean condition) {
+ if (!condition) Kit.codeBug();
+ }
+
+ private static Object[] testPool;
+
+ private static Object testKey(int i) {
+ int MAX_POOL = 100;
+ if (0 <= i && i < MAX_POOL) {
+ if (testPool != null && testPool[i] != null) {
+ return testPool[i];
+ }
+ }
+ Object x = new Double(i + 0.5);
+ if (0 <= i && i < MAX_POOL) {
+ if (testPool == null) {
+ testPool = new Object[MAX_POOL];
+ }
+ testPool[i] = x;
+ }
+ return x;
+ }
+
+ private static int intSqrt(int i) {
+ int approx = (int)Math.sqrt(i) + 1;
+ while (approx * approx > i) {
+ --approx;
+ }
+ return approx;
+ }
+
+ private static Object writeAndRead(Object obj) {
+ try {
+ java.io.ByteArrayOutputStream
+ bos = new java.io.ByteArrayOutputStream();
+ java.io.ObjectOutputStream
+ out = new java.io.ObjectOutputStream(bos);
+ out.writeObject(obj);
+ out.close();
+ byte[] data = bos.toByteArray();
+ java.io.ByteArrayInputStream
+ bis = new java.io.ByteArrayInputStream(data);
+ java.io.ObjectInputStream
+ in = new java.io.ObjectInputStream(bis);
+ Object result = in.readObject();
+ in.close();
+ return result;
+ }catch (Exception ex) {
+ throw new RuntimeException("Unexpected");
+ }
+ }
+
+// TEST END */
+
+}
diff --git a/src/org/mozilla/javascript/Parser.java b/src/org/mozilla/javascript/Parser.java
new file mode 100644
index 0000000..285ce34
--- /dev/null
+++ b/src/org/mozilla/javascript/Parser.java
@@ -0,0 +1,2507 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Mike Ang
+ * Igor Bukanov
+ * Yuh-Ruey Chen
+ * Ethan Hugg
+ * Bob Jervis
+ * Terry Lucas
+ * Mike McCabe
+ * Milen Nankov
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.Reader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class implements the JavaScript parser.
+ *
+ * It is based on the C source files jsparse.c and jsparse.h
+ * in the jsref package.
+ *
+ * @see TokenStream
+ *
+ * @author Mike McCabe
+ * @author Brendan Eich
+ */
+
+public class Parser
+{
+ // TokenInformation flags : currentFlaggedToken stores them together
+ // with token type
+ final static int
+ CLEAR_TI_MASK = 0xFFFF, // mask to clear token information bits
+ TI_AFTER_EOL = 1 << 16, // first token of the source line
+ TI_CHECK_LABEL = 1 << 17; // indicates to check for label
+
+ CompilerEnvirons compilerEnv;
+ private ErrorReporter errorReporter;
+ private String sourceURI;
+ boolean calledByCompileFunction;
+
+ private TokenStream ts;
+ private int currentFlaggedToken;
+ private int syntaxErrorCount;
+
+ private IRFactory nf;
+
+ private int nestingOfFunction;
+
+ private Decompiler decompiler;
+ private String encodedSource;
+
+// The following are per function variables and should be saved/restored
+// during function parsing.
+// XXX Move to separated class?
+ ScriptOrFnNode currentScriptOrFn;
+ Node.Scope currentScope;
+ private int nestingOfWith;
+ private Map<String,Node> labelSet; // map of label names into nodes
+ private ObjArray loopSet;
+ private ObjArray loopAndSwitchSet;
+ private int endFlags;
+// end of per function variables
+
+ public int getCurrentLineNumber() {
+ return ts.getLineno();
+ }
+
+ // Exception to unwind
+ private static class ParserException extends RuntimeException
+ {
+ static final long serialVersionUID = 5882582646773765630L;
+ }
+
+ public Parser(CompilerEnvirons compilerEnv, ErrorReporter errorReporter)
+ {
+ this.compilerEnv = compilerEnv;
+ this.errorReporter = errorReporter;
+ }
+
+ protected Decompiler createDecompiler(CompilerEnvirons compilerEnv)
+ {
+ return new Decompiler();
+ }
+
+ void addStrictWarning(String messageId, String messageArg)
+ {
+ if (compilerEnv.isStrictMode())
+ addWarning(messageId, messageArg);
+ }
+
+ void addWarning(String messageId, String messageArg)
+ {
+ String message = ScriptRuntime.getMessage1(messageId, messageArg);
+ if (compilerEnv.reportWarningAsError()) {
+ ++syntaxErrorCount;
+ errorReporter.error(message, sourceURI, ts.getLineno(),
+ ts.getLine(), ts.getOffset());
+ } else
+ errorReporter.warning(message, sourceURI, ts.getLineno(),
+ ts.getLine(), ts.getOffset());
+ }
+
+ void addError(String messageId)
+ {
+ ++syntaxErrorCount;
+ String message = ScriptRuntime.getMessage0(messageId);
+ errorReporter.error(message, sourceURI, ts.getLineno(),
+ ts.getLine(), ts.getOffset());
+ }
+
+ void addError(String messageId, String messageArg)
+ {
+ ++syntaxErrorCount;
+ String message = ScriptRuntime.getMessage1(messageId, messageArg);
+ errorReporter.error(message, sourceURI, ts.getLineno(),
+ ts.getLine(), ts.getOffset());
+ }
+
+ RuntimeException reportError(String messageId)
+ {
+ addError(messageId);
+
+ // Throw a ParserException exception to unwind the recursive descent
+ // parse.
+ throw new ParserException();
+ }
+
+ private int peekToken()
+ throws IOException
+ {
+ int tt = currentFlaggedToken;
+ if (tt == Token.EOF) {
+ tt = ts.getToken();
+ if (tt == Token.EOL) {
+ do {
+ tt = ts.getToken();
+ } while (tt == Token.EOL);
+ tt |= TI_AFTER_EOL;
+ }
+ currentFlaggedToken = tt;
+ }
+ return tt & CLEAR_TI_MASK;
+ }
+
+ private int peekFlaggedToken()
+ throws IOException
+ {
+ peekToken();
+ return currentFlaggedToken;
+ }
+
+ private void consumeToken()
+ {
+ currentFlaggedToken = Token.EOF;
+ }
+
+ private int nextToken()
+ throws IOException
+ {
+ int tt = peekToken();
+ consumeToken();
+ return tt;
+ }
+
+ private int nextFlaggedToken()
+ throws IOException
+ {
+ peekToken();
+ int ttFlagged = currentFlaggedToken;
+ consumeToken();
+ return ttFlagged;
+ }
+
+ private boolean matchToken(int toMatch)
+ throws IOException
+ {
+ int tt = peekToken();
+ if (tt != toMatch) {
+ return false;
+ }
+ consumeToken();
+ return true;
+ }
+
+ private int peekTokenOrEOL()
+ throws IOException
+ {
+ int tt = peekToken();
+ // Check for last peeked token flags
+ if ((currentFlaggedToken & TI_AFTER_EOL) != 0) {
+ tt = Token.EOL;
+ }
+ return tt;
+ }
+
+ private void setCheckForLabel()
+ {
+ if ((currentFlaggedToken & CLEAR_TI_MASK) != Token.NAME)
+ throw Kit.codeBug();
+ currentFlaggedToken |= TI_CHECK_LABEL;
+ }
+
+ private void mustMatchToken(int toMatch, String messageId)
+ throws IOException, ParserException
+ {
+ if (!matchToken(toMatch)) {
+ reportError(messageId);
+ }
+ }
+
+ private void mustHaveXML()
+ {
+ if (!compilerEnv.isXmlAvailable()) {
+ reportError("msg.XML.not.available");
+ }
+ }
+
+ public String getEncodedSource()
+ {
+ return encodedSource;
+ }
+
+ public boolean eof()
+ {
+ return ts.eof();
+ }
+
+ boolean insideFunction()
+ {
+ return nestingOfFunction != 0;
+ }
+
+ void pushScope(Node node) {
+ Node.Scope scopeNode = (Node.Scope) node;
+ if (scopeNode.getParentScope() != null) throw Kit.codeBug();
+ scopeNode.setParent(currentScope);
+ currentScope = scopeNode;
+ }
+
+ void popScope() {
+ currentScope = currentScope.getParentScope();
+ }
+
+ private Node enterLoop(Node loopLabel, boolean doPushScope)
+ {
+ Node loop = nf.createLoopNode(loopLabel, ts.getLineno());
+ if (loopSet == null) {
+ loopSet = new ObjArray();
+ if (loopAndSwitchSet == null) {
+ loopAndSwitchSet = new ObjArray();
+ }
+ }
+ loopSet.push(loop);
+ loopAndSwitchSet.push(loop);
+ if (doPushScope) {
+ pushScope(loop);
+ }
+ return loop;
+ }
+
+ private void exitLoop(boolean doPopScope)
+ {
+ loopSet.pop();
+ loopAndSwitchSet.pop();
+ if (doPopScope) {
+ popScope();
+ }
+ }
+
+ private Node enterSwitch(Node switchSelector, int lineno)
+ {
+ Node switchNode = nf.createSwitch(switchSelector, lineno);
+ if (loopAndSwitchSet == null) {
+ loopAndSwitchSet = new ObjArray();
+ }
+ loopAndSwitchSet.push(switchNode);
+ return switchNode;
+ }
+
+ private void exitSwitch()
+ {
+ loopAndSwitchSet.pop();
+ }
+
+ /*
+ * Build a parse tree from the given sourceString.
+ *
+ * @return an Object representing the parsed
+ * program. If the parse fails, null will be returned. (The
+ * parse failure will result in a call to the ErrorReporter from
+ * CompilerEnvirons.)
+ */
+ public ScriptOrFnNode parse(String sourceString,
+ String sourceURI, int lineno)
+ {
+ this.sourceURI = sourceURI;
+ this.ts = new TokenStream(this, null, sourceString, lineno);
+ try {
+ return parse();
+ } catch (IOException ex) {
+ // Should never happen
+ throw new IllegalStateException();
+ }
+ }
+
+ /*
+ * Build a parse tree from the given sourceString.
+ *
+ * @return an Object representing the parsed
+ * program. If the parse fails, null will be returned. (The
+ * parse failure will result in a call to the ErrorReporter from
+ * CompilerEnvirons.)
+ */
+ public ScriptOrFnNode parse(Reader sourceReader,
+ String sourceURI, int lineno)
+ throws IOException
+ {
+ this.sourceURI = sourceURI;
+ this.ts = new TokenStream(this, sourceReader, null, lineno);
+ return parse();
+ }
+
+ private ScriptOrFnNode parse()
+ throws IOException
+ {
+ this.decompiler = createDecompiler(compilerEnv);
+ this.nf = new IRFactory(this);
+ currentScriptOrFn = nf.createScript();
+ currentScope = currentScriptOrFn;
+ int sourceStartOffset = decompiler.getCurrentOffset();
+ this.encodedSource = null;
+ decompiler.addToken(Token.SCRIPT);
+
+ this.currentFlaggedToken = Token.EOF;
+ this.syntaxErrorCount = 0;
+
+ int baseLineno = ts.getLineno(); // line number where source starts
+
+ /* so we have something to add nodes to until
+ * we've collected all the source */
+ Node pn = nf.createLeaf(Token.BLOCK);
+
+ try {
+ for (;;) {
+ int tt = peekToken();
+
+ if (tt <= Token.EOF) {
+ break;
+ }
+
+ Node n;
+ if (tt == Token.FUNCTION) {
+ consumeToken();
+ try {
+ n = function(calledByCompileFunction
+ ? FunctionNode.FUNCTION_EXPRESSION
+ : FunctionNode.FUNCTION_STATEMENT);
+ } catch (ParserException e) {
+ break;
+ }
+ } else {
+ n = statement();
+ }
+ nf.addChildToBack(pn, n);
+ }
+ } catch (StackOverflowError ex) {
+ String msg = ScriptRuntime.getMessage0(
+ "msg.too.deep.parser.recursion");
+ throw Context.reportRuntimeError(msg, sourceURI,
+ ts.getLineno(), null, 0);
+ }
+
+ if (this.syntaxErrorCount != 0) {
+ String msg = String.valueOf(this.syntaxErrorCount);
+ msg = ScriptRuntime.getMessage1("msg.got.syntax.errors", msg);
+ throw errorReporter.runtimeError(msg, sourceURI, baseLineno,
+ null, 0);
+ }
+
+ currentScriptOrFn.setSourceName(sourceURI);
+ currentScriptOrFn.setBaseLineno(baseLineno);
+ currentScriptOrFn.setEndLineno(ts.getLineno());
+
+ int sourceEndOffset = decompiler.getCurrentOffset();
+ currentScriptOrFn.setEncodedSourceBounds(sourceStartOffset,
+ sourceEndOffset);
+
+ nf.initScript(currentScriptOrFn, pn);
+
+ if (compilerEnv.isGeneratingSource()) {
+ encodedSource = decompiler.getEncodedSource();
+ }
+ this.decompiler = null; // It helps GC
+
+ return currentScriptOrFn;
+ }
+
+ /*
+ * The C version of this function takes an argument list,
+ * which doesn't seem to be needed for tree generation...
+ * it'd only be useful for checking argument hiding, which
+ * I'm not doing anyway...
+ */
+ private Node parseFunctionBody()
+ throws IOException
+ {
+ ++nestingOfFunction;
+ Node pn = nf.createBlock(ts.getLineno());
+ try {
+ bodyLoop: for (;;) {
+ Node n;
+ int tt = peekToken();
+ switch (tt) {
+ case Token.ERROR:
+ case Token.EOF:
+ case Token.RC:
+ break bodyLoop;
+
+ case Token.FUNCTION:
+ consumeToken();
+ n = function(FunctionNode.FUNCTION_STATEMENT);
+ break;
+ default:
+ n = statement();
+ break;
+ }
+ nf.addChildToBack(pn, n);
+ }
+ } catch (ParserException e) {
+ // Ignore it
+ } finally {
+ --nestingOfFunction;
+ }
+
+ return pn;
+ }
+
+ private Node function(int functionType)
+ throws IOException, ParserException
+ {
+ int syntheticType = functionType;
+ int baseLineno = ts.getLineno(); // line number where source starts
+
+ int functionSourceStart = decompiler.markFunctionStart(functionType);
+ String name;
+ Node memberExprNode = null;
+ if (matchToken(Token.NAME)) {
+ name = ts.getString();
+ decompiler.addName(name);
+ if (!matchToken(Token.LP)) {
+ if (compilerEnv.isAllowMemberExprAsFunctionName()) {
+ // Extension to ECMA: if 'function <name>' does not follow
+ // by '(', assume <name> starts memberExpr
+ Node memberExprHead = nf.createName(name);
+ name = "";
+ memberExprNode = memberExprTail(false, memberExprHead);
+ }
+ mustMatchToken(Token.LP, "msg.no.paren.parms");
+ }
+ } else if (matchToken(Token.LP)) {
+ // Anonymous function
+ name = "";
+ } else {
+ name = "";
+ if (compilerEnv.isAllowMemberExprAsFunctionName()) {
+ // Note that memberExpr can not start with '(' like
+ // in function (1+2).toString(), because 'function (' already
+ // processed as anonymous function
+ memberExprNode = memberExpr(false);
+ }
+ mustMatchToken(Token.LP, "msg.no.paren.parms");
+ }
+
+ if (memberExprNode != null) {
+ syntheticType = FunctionNode.FUNCTION_EXPRESSION;
+ }
+
+ if (syntheticType != FunctionNode.FUNCTION_EXPRESSION &&
+ name.length() > 0)
+ {
+ // Function statements define a symbol in the enclosing scope
+ defineSymbol(Token.FUNCTION, false, name);
+ }
+
+ boolean nested = insideFunction();
+
+ FunctionNode fnNode = nf.createFunction(name);
+ if (nested || nestingOfWith > 0) {
+ // 1. Nested functions are not affected by the dynamic scope flag
+ // as dynamic scope is already a parent of their scope.
+ // 2. Functions defined under the with statement also immune to
+ // this setup, in which case dynamic scope is ignored in favor
+ // of with object.
+ fnNode.itsIgnoreDynamicScope = true;
+ }
+ int functionIndex = currentScriptOrFn.addFunction(fnNode);
+
+ int functionSourceEnd;
+
+ ScriptOrFnNode savedScriptOrFn = currentScriptOrFn;
+ currentScriptOrFn = fnNode;
+ Node.Scope savedCurrentScope = currentScope;
+ currentScope = fnNode;
+ int savedNestingOfWith = nestingOfWith;
+ nestingOfWith = 0;
+ Map<String,Node> savedLabelSet = labelSet;
+ labelSet = null;
+ ObjArray savedLoopSet = loopSet;
+ loopSet = null;
+ ObjArray savedLoopAndSwitchSet = loopAndSwitchSet;
+ loopAndSwitchSet = null;
+ int savedFunctionEndFlags = endFlags;
+ endFlags = 0;
+
+ Node destructuring = null;
+ Node body;
+ try {
+ decompiler.addToken(Token.LP);
+ if (!matchToken(Token.RP)) {
+ boolean first = true;
+ do {
+ if (!first)
+ decompiler.addToken(Token.COMMA);
+ first = false;
+ int tt = peekToken();
+ if (tt == Token.LB || tt == Token.LC) {
+ // Destructuring assignment for parameters: add a
+ // dummy parameter name, and add a statement to the
+ // body to initialize variables from the destructuring
+ // assignment
+ if (destructuring == null) {
+ destructuring = new Node(Token.COMMA);
+ }
+ String parmName = currentScriptOrFn.getNextTempName();
+ defineSymbol(Token.LP, false, parmName);
+ destructuring.addChildToBack(
+ nf.createDestructuringAssignment(Token.VAR,
+ primaryExpr(), nf.createName(parmName)));
+ } else {
+ mustMatchToken(Token.NAME, "msg.no.parm");
+ String s = ts.getString();
+ defineSymbol(Token.LP, false, s);
+ decompiler.addName(s);
+ }
+ } while (matchToken(Token.COMMA));
+
+ mustMatchToken(Token.RP, "msg.no.paren.after.parms");
+ }
+ decompiler.addToken(Token.RP);
+
+ mustMatchToken(Token.LC, "msg.no.brace.body");
+ decompiler.addEOL(Token.LC);
+ body = parseFunctionBody();
+ if (destructuring != null) {
+ body.addChildToFront(
+ new Node(Token.EXPR_VOID, destructuring, ts.getLineno()));
+ }
+ mustMatchToken(Token.RC, "msg.no.brace.after.body");
+
+ if (compilerEnv.isStrictMode() && !body.hasConsistentReturnUsage())
+ {
+ String msg = name.length() > 0 ? "msg.no.return.value"
+ : "msg.anon.no.return.value";
+ addStrictWarning(msg, name);
+ }
+
+ if (syntheticType == FunctionNode.FUNCTION_EXPRESSION &&
+ name.length() > 0 && currentScope.getSymbol(name) == null)
+ {
+ // Function expressions define a name only in the body of the
+ // function, and only if not hidden by a parameter name
+ defineSymbol(Token.FUNCTION, false, name);
+ }
+
+ decompiler.addToken(Token.RC);
+ functionSourceEnd = decompiler.markFunctionEnd(functionSourceStart);
+ if (functionType != FunctionNode.FUNCTION_EXPRESSION) {
+ // Add EOL only if function is not part of expression
+ // since it gets SEMI + EOL from Statement in that case
+ decompiler.addToken(Token.EOL);
+ }
+ }
+ finally {
+ endFlags = savedFunctionEndFlags;
+ loopAndSwitchSet = savedLoopAndSwitchSet;
+ loopSet = savedLoopSet;
+ labelSet = savedLabelSet;
+ nestingOfWith = savedNestingOfWith;
+ currentScriptOrFn = savedScriptOrFn;
+ currentScope = savedCurrentScope;
+ }
+
+ fnNode.setEncodedSourceBounds(functionSourceStart, functionSourceEnd);
+ fnNode.setSourceName(sourceURI);
+ fnNode.setBaseLineno(baseLineno);
+ fnNode.setEndLineno(ts.getLineno());
+
+ Node pn = nf.initFunction(fnNode, functionIndex, body, syntheticType);
+ if (memberExprNode != null) {
+ pn = nf.createAssignment(Token.ASSIGN, memberExprNode, pn);
+ if (functionType != FunctionNode.FUNCTION_EXPRESSION) {
+ // XXX check JScript behavior: should it be createExprStatement?
+ pn = nf.createExprStatementNoReturn(pn, baseLineno);
+ }
+ }
+ return pn;
+ }
+
+ private Node statements(Node scope)
+ throws IOException
+ {
+ Node pn = scope != null ? scope : nf.createBlock(ts.getLineno());
+
+ int tt;
+ while ((tt = peekToken()) > Token.EOF && tt != Token.RC) {
+ nf.addChildToBack(pn, statement());
+ }
+
+ return pn;
+ }
+
+ private Node condition()
+ throws IOException, ParserException
+ {
+ mustMatchToken(Token.LP, "msg.no.paren.cond");
+ decompiler.addToken(Token.LP);
+ Node pn = expr(false);
+ mustMatchToken(Token.RP, "msg.no.paren.after.cond");
+ decompiler.addToken(Token.RP);
+
+ // Report strict warning on code like "if (a = 7) ...". Suppress the
+ // warning if the condition is parenthesized, like "if ((a = 7)) ...".
+ if (pn.getProp(Node.PARENTHESIZED_PROP) == null &&
+ (pn.getType() == Token.SETNAME || pn.getType() == Token.SETPROP ||
+ pn.getType() == Token.SETELEM))
+ {
+ addStrictWarning("msg.equal.as.assign", "");
+ }
+ return pn;
+ }
+
+ // match a NAME; return null if no match.
+ private Node matchJumpLabelName()
+ throws IOException, ParserException
+ {
+ Node label = null;
+
+ int tt = peekTokenOrEOL();
+ if (tt == Token.NAME) {
+ consumeToken();
+ String name = ts.getString();
+ decompiler.addName(name);
+ if (labelSet != null) {
+ label = labelSet.get(name);
+ }
+ if (label == null) {
+ reportError("msg.undef.label");
+ }
+ }
+
+ return label;
+ }
+
+ private Node statement()
+ throws IOException
+ {
+ try {
+ Node pn = statementHelper(null);
+ if (pn != null) {
+ if (compilerEnv.isStrictMode() && !pn.hasSideEffects())
+ addStrictWarning("msg.no.side.effects", "");
+ return pn;
+ }
+ } catch (ParserException e) { }
+
+ // skip to end of statement
+ int lineno = ts.getLineno();
+ guessingStatementEnd: for (;;) {
+ int tt = peekTokenOrEOL();
+ consumeToken();
+ switch (tt) {
+ case Token.ERROR:
+ case Token.EOF:
+ case Token.EOL:
+ case Token.SEMI:
+ break guessingStatementEnd;
+ }
+ }
+ return nf.createExprStatement(nf.createName("error"), lineno);
+ }
+
+ private Node statementHelper(Node statementLabel)
+ throws IOException, ParserException
+ {
+ Node pn = null;
+ int tt = peekToken();
+
+ switch (tt) {
+ case Token.IF: {
+ consumeToken();
+
+ decompiler.addToken(Token.IF);
+ int lineno = ts.getLineno();
+ Node cond = condition();
+ decompiler.addEOL(Token.LC);
+ Node ifTrue = statement();
+ Node ifFalse = null;
+ if (matchToken(Token.ELSE)) {
+ decompiler.addToken(Token.RC);
+ decompiler.addToken(Token.ELSE);
+ decompiler.addEOL(Token.LC);
+ ifFalse = statement();
+ }
+ decompiler.addEOL(Token.RC);
+ pn = nf.createIf(cond, ifTrue, ifFalse, lineno);
+ return pn;
+ }
+
+ case Token.SWITCH: {
+ consumeToken();
+
+ decompiler.addToken(Token.SWITCH);
+ int lineno = ts.getLineno();
+ mustMatchToken(Token.LP, "msg.no.paren.switch");
+ decompiler.addToken(Token.LP);
+ pn = enterSwitch(expr(false), lineno);
+ try {
+ mustMatchToken(Token.RP, "msg.no.paren.after.switch");
+ decompiler.addToken(Token.RP);
+ mustMatchToken(Token.LC, "msg.no.brace.switch");
+ decompiler.addEOL(Token.LC);
+
+ boolean hasDefault = false;
+ switchLoop: for (;;) {
+ tt = nextToken();
+ Node caseExpression;
+ switch (tt) {
+ case Token.RC:
+ break switchLoop;
+
+ case Token.CASE:
+ decompiler.addToken(Token.CASE);
+ caseExpression = expr(false);
+ mustMatchToken(Token.COLON, "msg.no.colon.case");
+ decompiler.addEOL(Token.COLON);
+ break;
+
+ case Token.DEFAULT:
+ if (hasDefault) {
+ reportError("msg.double.switch.default");
+ }
+ decompiler.addToken(Token.DEFAULT);
+ hasDefault = true;
+ caseExpression = null;
+ mustMatchToken(Token.COLON, "msg.no.colon.case");
+ decompiler.addEOL(Token.COLON);
+ break;
+
+ default:
+ reportError("msg.bad.switch");
+ break switchLoop;
+ }
+
+ Node block = nf.createLeaf(Token.BLOCK);
+ while ((tt = peekToken()) != Token.RC
+ && tt != Token.CASE
+ && tt != Token.DEFAULT
+ && tt != Token.EOF)
+ {
+ nf.addChildToBack(block, statement());
+ }
+
+ // caseExpression == null => add default label
+ nf.addSwitchCase(pn, caseExpression, block);
+ }
+ decompiler.addEOL(Token.RC);
+ nf.closeSwitch(pn);
+ } finally {
+ exitSwitch();
+ }
+ return pn;
+ }
+
+ case Token.WHILE: {
+ consumeToken();
+ decompiler.addToken(Token.WHILE);
+
+ Node loop = enterLoop(statementLabel, true);
+ try {
+ Node cond = condition();
+ decompiler.addEOL(Token.LC);
+ Node body = statement();
+ decompiler.addEOL(Token.RC);
+ pn = nf.createWhile(loop, cond, body);
+ } finally {
+ exitLoop(true);
+ }
+ return pn;
+ }
+
+ case Token.DO: {
+ consumeToken();
+ decompiler.addToken(Token.DO);
+ decompiler.addEOL(Token.LC);
+
+ Node loop = enterLoop(statementLabel, true);
+ try {
+ Node body = statement();
+ decompiler.addToken(Token.RC);
+ mustMatchToken(Token.WHILE, "msg.no.while.do");
+ decompiler.addToken(Token.WHILE);
+ Node cond = condition();
+ pn = nf.createDoWhile(loop, body, cond);
+ } finally {
+ exitLoop(true);
+ }
+ // Always auto-insert semicolon to follow SpiderMonkey:
+ // It is required by ECMAScript but is ignored by the rest of
+ // world, see bug 238945
+ matchToken(Token.SEMI);
+ decompiler.addEOL(Token.SEMI);
+ return pn;
+ }
+
+ case Token.FOR: {
+ consumeToken();
+ boolean isForEach = false;
+ decompiler.addToken(Token.FOR);
+
+ Node loop = enterLoop(statementLabel, true);
+ try {
+ Node init; // Node init is also foo in 'foo in object'
+ Node cond; // Node cond is also object in 'foo in object'
+ Node incr = null;
+ Node body;
+ int declType = -1;
+
+ // See if this is a for each () instead of just a for ()
+ if (matchToken(Token.NAME)) {
+ decompiler.addName(ts.getString());
+ if (ts.getString().equals("each")) {
+ isForEach = true;
+ } else {
+ reportError("msg.no.paren.for");
+ }
+ }
+
+ mustMatchToken(Token.LP, "msg.no.paren.for");
+ decompiler.addToken(Token.LP);
+ tt = peekToken();
+ if (tt == Token.SEMI) {
+ init = nf.createLeaf(Token.EMPTY);
+ } else {
+ if (tt == Token.VAR || tt == Token.LET) {
+ // set init to a var list or initial
+ consumeToken(); // consume the token
+ decompiler.addToken(tt);
+ init = variables(true, tt);
+ declType = tt;
+ }
+ else {
+ init = expr(true);
+ }
+ }
+
+ if (matchToken(Token.IN)) {
+ decompiler.addToken(Token.IN);
+ // 'cond' is the object over which we're iterating
+ cond = expr(false);
+ } else { // ordinary for loop
+ mustMatchToken(Token.SEMI, "msg.no.semi.for");
+ decompiler.addToken(Token.SEMI);
+ if (peekToken() == Token.SEMI) {
+ // no loop condition
+ cond = nf.createLeaf(Token.EMPTY);
+ } else {
+ cond = expr(false);
+ }
+
+ mustMatchToken(Token.SEMI, "msg.no.semi.for.cond");
+ decompiler.addToken(Token.SEMI);
+ if (peekToken() == Token.RP) {
+ incr = nf.createLeaf(Token.EMPTY);
+ } else {
+ incr = expr(false);
+ }
+ }
+
+ mustMatchToken(Token.RP, "msg.no.paren.for.ctrl");
+ decompiler.addToken(Token.RP);
+ decompiler.addEOL(Token.LC);
+ body = statement();
+ decompiler.addEOL(Token.RC);
+
+ if (incr == null) {
+ // cond could be null if 'in obj' got eaten
+ // by the init node.
+ pn = nf.createForIn(declType, loop, init, cond, body,
+ isForEach);
+ } else {
+ pn = nf.createFor(loop, init, cond, incr, body);
+ }
+ } finally {
+ exitLoop(true);
+ }
+ return pn;
+ }
+
+ case Token.TRY: {
+ consumeToken();
+ int lineno = ts.getLineno();
+
+ Node tryblock;
+ Node catchblocks = null;
+ Node finallyblock = null;
+
+ decompiler.addToken(Token.TRY);
+ if (peekToken() != Token.LC) {
+ reportError("msg.no.brace.try");
+ }
+ decompiler.addEOL(Token.LC);
+ tryblock = statement();
+ decompiler.addEOL(Token.RC);
+
+ catchblocks = nf.createLeaf(Token.BLOCK);
+
+ boolean sawDefaultCatch = false;
+ int peek = peekToken();
+ if (peek == Token.CATCH) {
+ while (matchToken(Token.CATCH)) {
+ if (sawDefaultCatch) {
+ reportError("msg.catch.unreachable");
+ }
+ decompiler.addToken(Token.CATCH);
+ mustMatchToken(Token.LP, "msg.no.paren.catch");
+ decompiler.addToken(Token.LP);
+
+ mustMatchToken(Token.NAME, "msg.bad.catchcond");
+ String varName = ts.getString();
+ decompiler.addName(varName);
+
+ Node catchCond = null;
+ if (matchToken(Token.IF)) {
+ decompiler.addToken(Token.IF);
+ catchCond = expr(false);
+ } else {
+ sawDefaultCatch = true;
+ }
+
+ mustMatchToken(Token.RP, "msg.bad.catchcond");
+ decompiler.addToken(Token.RP);
+ mustMatchToken(Token.LC, "msg.no.brace.catchblock");
+ decompiler.addEOL(Token.LC);
+
+ nf.addChildToBack(catchblocks,
+ nf.createCatch(varName, catchCond,
+ statements(null),
+ ts.getLineno()));
+
+ mustMatchToken(Token.RC, "msg.no.brace.after.body");
+ decompiler.addEOL(Token.RC);
+ }
+ } else if (peek != Token.FINALLY) {
+ mustMatchToken(Token.FINALLY, "msg.try.no.catchfinally");
+ }
+
+ if (matchToken(Token.FINALLY)) {
+ decompiler.addToken(Token.FINALLY);
+ decompiler.addEOL(Token.LC);
+ finallyblock = statement();
+ decompiler.addEOL(Token.RC);
+ }
+
+ pn = nf.createTryCatchFinally(tryblock, catchblocks,
+ finallyblock, lineno);
+
+ return pn;
+ }
+
+ case Token.THROW: {
+ consumeToken();
+ if (peekTokenOrEOL() == Token.EOL) {
+ // ECMAScript does not allow new lines before throw expression,
+ // see bug 256617
+ reportError("msg.bad.throw.eol");
+ }
+
+ int lineno = ts.getLineno();
+ decompiler.addToken(Token.THROW);
+ pn = nf.createThrow(expr(false), lineno);
+ break;
+ }
+
+ case Token.BREAK: {
+ consumeToken();
+ int lineno = ts.getLineno();
+
+ decompiler.addToken(Token.BREAK);
+
+ // matchJumpLabelName only matches if there is one
+ Node breakStatement = matchJumpLabelName();
+ if (breakStatement == null) {
+ if (loopAndSwitchSet == null || loopAndSwitchSet.size() == 0) {
+ reportError("msg.bad.break");
+ return null;
+ }
+ breakStatement = (Node)loopAndSwitchSet.peek();
+ }
+ pn = nf.createBreak(breakStatement, lineno);
+ break;
+ }
+
+ case Token.CONTINUE: {
+ consumeToken();
+ int lineno = ts.getLineno();
+
+ decompiler.addToken(Token.CONTINUE);
+
+ Node loop;
+ // matchJumpLabelName only matches if there is one
+ Node label = matchJumpLabelName();
+ if (label == null) {
+ if (loopSet == null || loopSet.size() == 0) {
+ reportError("msg.continue.outside");
+ return null;
+ }
+ loop = (Node)loopSet.peek();
+ } else {
+ loop = nf.getLabelLoop(label);
+ if (loop == null) {
+ reportError("msg.continue.nonloop");
+ return null;
+ }
+ }
+ pn = nf.createContinue(loop, lineno);
+ break;
+ }
+
+ case Token.WITH: {
+ consumeToken();
+
+ decompiler.addToken(Token.WITH);
+ int lineno = ts.getLineno();
+ mustMatchToken(Token.LP, "msg.no.paren.with");
+ decompiler.addToken(Token.LP);
+ Node obj = expr(false);
+ mustMatchToken(Token.RP, "msg.no.paren.after.with");
+ decompiler.addToken(Token.RP);
+ decompiler.addEOL(Token.LC);
+
+ ++nestingOfWith;
+ Node body;
+ try {
+ body = statement();
+ } finally {
+ --nestingOfWith;
+ }
+
+ decompiler.addEOL(Token.RC);
+
+ pn = nf.createWith(obj, body, lineno);
+ return pn;
+ }
+
+ case Token.CONST:
+ case Token.VAR: {
+ consumeToken();
+ decompiler.addToken(tt);
+ pn = variables(false, tt);
+ break;
+ }
+
+ case Token.LET: {
+ consumeToken();
+ decompiler.addToken(Token.LET);
+ if (peekToken() == Token.LP) {
+ return let(true);
+ } else {
+ pn = variables(false, tt);
+ if (peekToken() == Token.SEMI)
+ break;
+ return pn;
+ }
+ }
+
+ case Token.RETURN:
+ case Token.YIELD: {
+ pn = returnOrYield(tt, false);
+ break;
+ }
+
+ case Token.DEBUGGER:
+ consumeToken();
+ decompiler.addToken(Token.DEBUGGER);
+ pn = nf.createDebugger(ts.getLineno());
+ break;
+
+ case Token.LC:
+ consumeToken();
+ if (statementLabel != null) {
+ decompiler.addToken(Token.LC);
+ }
+ Node scope = nf.createScopeNode(Token.BLOCK, ts.getLineno());
+ pushScope(scope);
+ try {
+ statements(scope);
+ mustMatchToken(Token.RC, "msg.no.brace.block");
+ if (statementLabel != null) {
+ decompiler.addEOL(Token.RC);
+ }
+ return scope;
+ } finally {
+ popScope();
+ }
+
+ case Token.ERROR:
+ // Fall thru, to have a node for error recovery to work on
+ case Token.SEMI:
+ consumeToken();
+ pn = nf.createLeaf(Token.EMPTY);
+ return pn;
+
+ case Token.FUNCTION: {
+ consumeToken();
+ pn = function(FunctionNode.FUNCTION_EXPRESSION_STATEMENT);
+ return pn;
+ }
+
+ case Token.DEFAULT :
+ consumeToken();
+ mustHaveXML();
+
+ decompiler.addToken(Token.DEFAULT);
+ int nsLine = ts.getLineno();
+
+ if (!(matchToken(Token.NAME)
+ && ts.getString().equals("xml")))
+ {
+ reportError("msg.bad.namespace");
+ }
+ decompiler.addName(" xml");
+
+ if (!(matchToken(Token.NAME)
+ && ts.getString().equals("namespace")))
+ {
+ reportError("msg.bad.namespace");
+ }
+ decompiler.addName(" namespace");
+
+ if (!matchToken(Token.ASSIGN)) {
+ reportError("msg.bad.namespace");
+ }
+ decompiler.addToken(Token.ASSIGN);
+
+ Node expr = expr(false);
+ pn = nf.createDefaultNamespace(expr, nsLine);
+ break;
+
+ case Token.NAME: {
+ int lineno = ts.getLineno();
+ String name = ts.getString();
+ setCheckForLabel();
+ pn = expr(false);
+ if (pn.getType() != Token.LABEL) {
+ pn = nf.createExprStatement(pn, lineno);
+ } else {
+ // Parsed the label: push back token should be
+ // colon that primaryExpr left untouched.
+ if (peekToken() != Token.COLON) Kit.codeBug();
+ consumeToken();
+ // depend on decompiling lookahead to guess that that
+ // last name was a label.
+ decompiler.addName(name);
+ decompiler.addEOL(Token.COLON);
+
+ if (labelSet == null) {
+ labelSet = new HashMap<String,Node>();
+ } else if (labelSet.containsKey(name)) {
+ reportError("msg.dup.label");
+ }
+
+ boolean firstLabel;
+ if (statementLabel == null) {
+ firstLabel = true;
+ statementLabel = pn;
+ } else {
+ // Discard multiple label nodes and use only
+ // the first: it allows to simplify IRFactory
+ firstLabel = false;
+ }
+ labelSet.put(name, statementLabel);
+ try {
+ pn = statementHelper(statementLabel);
+ } finally {
+ labelSet.remove(name);
+ }
+ if (firstLabel) {
+ pn = nf.createLabeledStatement(statementLabel, pn);
+ }
+ return pn;
+ }
+ break;
+ }
+
+ default: {
+ int lineno = ts.getLineno();
+ pn = expr(false);
+ pn = nf.createExprStatement(pn, lineno);
+ break;
+ }
+ }
+
+ int ttFlagged = peekFlaggedToken();
+ switch (ttFlagged & CLEAR_TI_MASK) {
+ case Token.SEMI:
+ // Consume ';' as a part of expression
+ consumeToken();
+ break;
+ case Token.ERROR:
+ case Token.EOF:
+ case Token.RC:
+ // Autoinsert ;
+ break;
+ default:
+ if ((ttFlagged & TI_AFTER_EOL) == 0) {
+ // Report error if no EOL or autoinsert ; otherwise
+ reportError("msg.no.semi.stmt");
+ }
+ break;
+ }
+ decompiler.addEOL(Token.SEMI);
+
+ return pn;
+ }
+
+ /**
+ * Returns whether or not the bits in the mask have changed to all set.
+ * @param before bits before change
+ * @param after bits after change
+ * @param mask mask for bits
+ * @return true if all the bits in the mask are set in "after" but not
+ * "before"
+ */
+ private static final boolean nowAllSet(int before, int after, int mask)
+ {
+ return ((before & mask) != mask) && ((after & mask) == mask);
+ }
+
+ private Node returnOrYield(int tt, boolean exprContext)
+ throws IOException, ParserException
+ {
+ if (!insideFunction()) {
+ reportError(tt == Token.RETURN ? "msg.bad.return"
+ : "msg.bad.yield");
+ }
+ consumeToken();
+ decompiler.addToken(tt);
+ int lineno = ts.getLineno();
+
+ Node e;
+ /* This is ugly, but we don't want to require a semicolon. */
+ switch (peekTokenOrEOL()) {
+ case Token.SEMI:
+ case Token.RC:
+ case Token.EOF:
+ case Token.EOL:
+ case Token.ERROR:
+ case Token.RB:
+ case Token.RP:
+ case Token.YIELD:
+ e = null;
+ break;
+ default:
+ e = expr(false);
+ break;
+ }
+
+ int before = endFlags;
+ Node ret;
+
+ if (tt == Token.RETURN) {
+ if (e == null ) {
+ endFlags |= Node.END_RETURNS;
+ } else {
+ endFlags |= Node.END_RETURNS_VALUE;
+ }
+ ret = nf.createReturn(e, lineno);
+
+ // see if we need a strict mode warning
+ if (nowAllSet(before, endFlags,
+ Node.END_RETURNS|Node.END_RETURNS_VALUE))
+ {
+ addStrictWarning("msg.return.inconsistent", "");
+ }
+ } else {
+ endFlags |= Node.END_YIELDS;
+ ret = nf.createYield(e, lineno);
+ if (!exprContext)
+ ret = new Node(Token.EXPR_VOID, ret, lineno);
+ }
+
+ // see if we are mixing yields and value returns.
+ if (nowAllSet(before, endFlags,
+ Node.END_YIELDS|Node.END_RETURNS_VALUE))
+ {
+ String name = ((FunctionNode)currentScriptOrFn).getFunctionName();
+ if (name.length() == 0)
+ addError("msg.anon.generator.returns", "");
+ else
+ addError("msg.generator.returns", name);
+ }
+
+ return ret;
+ }
+
+ /**
+ * Parse a 'var' or 'const' statement, or a 'var' init list in a for
+ * statement.
+ * @param inFor true if we are currently in the midst of the init
+ * clause of a for.
+ * @param declType A token value: either VAR, CONST, or LET depending on
+ * context.
+ * @return The parsed statement
+ * @throws IOException
+ * @throws ParserException
+ */
+ private Node variables(boolean inFor, int declType)
+ throws IOException, ParserException
+ {
+ Node result = nf.createVariables(declType, ts.getLineno());
+ boolean first = true;
+ for (;;) {
+ Node destructuring = null;
+ String s = null;
+ int tt = peekToken();
+ if (tt == Token.LB || tt == Token.LC) {
+ // Destructuring assignment, e.g., var [a,b] = ...
+ destructuring = primaryExpr();
+ } else {
+ // Simple variable name
+ mustMatchToken(Token.NAME, "msg.bad.var");
+ s = ts.getString();
+
+ if (!first)
+ decompiler.addToken(Token.COMMA);
+ first = false;
+
+ decompiler.addName(s);
+ defineSymbol(declType, inFor, s);
+ }
+
+ Node init = null;
+ if (matchToken(Token.ASSIGN)) {
+ decompiler.addToken(Token.ASSIGN);
+ init = assignExpr(inFor);
+ }
+
+ if (destructuring != null) {
+ if (init == null) {
+ if (!inFor)
+ reportError("msg.destruct.assign.no.init");
+ nf.addChildToBack(result, destructuring);
+ } else {
+ nf.addChildToBack(result,
+ nf.createDestructuringAssignment(declType,
+ destructuring, init));
+ }
+ } else {
+ Node name = nf.createName(s);
+ if (init != null)
+ nf.addChildToBack(name, init);
+ nf.addChildToBack(result, name);
+ }
+
+ if (!matchToken(Token.COMMA))
+ break;
+ }
+ return result;
+ }
+
+
+ private Node let(boolean isStatement)
+ throws IOException, ParserException
+ {
+ mustMatchToken(Token.LP, "msg.no.paren.after.let");
+ decompiler.addToken(Token.LP);
+ Node result = nf.createScopeNode(Token.LET, ts.getLineno());
+ pushScope(result);
+ try {
+ Node vars = variables(false, Token.LET);
+ nf.addChildToBack(result, vars);
+ mustMatchToken(Token.RP, "msg.no.paren.let");
+ decompiler.addToken(Token.RP);
+ if (isStatement && peekToken() == Token.LC) {
+ // let statement
+ consumeToken();
+ decompiler.addEOL(Token.LC);
+ nf.addChildToBack(result, statements(null));
+ mustMatchToken(Token.RC, "msg.no.curly.let");
+ decompiler.addToken(Token.RC);
+ } else {
+ // let expression
+ result.setType(Token.LETEXPR);
+ nf.addChildToBack(result, expr(false));
+ if (isStatement) {
+ // let expression in statement context
+ result = nf.createExprStatement(result, ts.getLineno());
+ }
+ }
+ } finally {
+ popScope();
+ }
+ return result;
+ }
+
+ void defineSymbol(int declType, boolean ignoreNotInBlock, String name) {
+ Node.Scope definingScope = currentScope.getDefiningScope(name);
+ Node.Scope.Symbol symbol = definingScope != null
+ ? definingScope.getSymbol(name)
+ : null;
+ boolean error = false;
+ if (symbol != null && (symbol.declType == Token.CONST ||
+ declType == Token.CONST))
+ {
+ error = true;
+ } else {
+ switch (declType) {
+ case Token.LET:
+ if (symbol != null && definingScope == currentScope) {
+ error = symbol.declType == Token.LET;
+ }
+ int currentScopeType = currentScope.getType();
+ if (!ignoreNotInBlock &&
+ ((currentScopeType == Token.LOOP) ||
+ (currentScopeType == Token.IF)))
+ {
+ addError("msg.let.decl.not.in.block");
+ }
+ currentScope.putSymbol(name,
+ new Node.Scope.Symbol(declType, name));
+ break;
+
+ case Token.VAR:
+ case Token.CONST:
+ case Token.FUNCTION:
+ if (symbol != null) {
+ if (symbol.declType == Token.VAR)
+ addStrictWarning("msg.var.redecl", name);
+ else if (symbol.declType == Token.LP) {
+ addStrictWarning("msg.var.hides.arg", name);
+ }
+ } else {
+ currentScriptOrFn.putSymbol(name,
+ new Node.Scope.Symbol(declType, name));
+ }
+ break;
+
+ case Token.LP:
+ if (symbol != null) {
+ // must be duplicate parameter. Second parameter hides the
+ // first, so go ahead and add the second pararameter
+ addWarning("msg.dup.parms", name);
+ }
+ currentScriptOrFn.putSymbol(name,
+ new Node.Scope.Symbol(declType, name));
+ break;
+
+ default:
+ throw Kit.codeBug();
+ }
+ }
+ if (error) {
+ addError(symbol.declType == Token.CONST ? "msg.const.redecl" :
+ symbol.declType == Token.LET ? "msg.let.redecl" :
+ symbol.declType == Token.VAR ? "msg.var.redecl" :
+ symbol.declType == Token.FUNCTION ? "msg.fn.redecl" :
+ "msg.parm.redecl", name);
+ }
+ }
+
+ private Node expr(boolean inForInit)
+ throws IOException, ParserException
+ {
+ Node pn = assignExpr(inForInit);
+ while (matchToken(Token.COMMA)) {
+ decompiler.addToken(Token.COMMA);
+ if (compilerEnv.isStrictMode() && !pn.hasSideEffects())
+ addStrictWarning("msg.no.side.effects", "");
+ if (peekToken() == Token.YIELD) {
+ reportError("msg.yield.parenthesized");
+ }
+ pn = nf.createBinary(Token.COMMA, pn, assignExpr(inForInit));
+ }
+ return pn;
+ }
+
+ private Node assignExpr(boolean inForInit)
+ throws IOException, ParserException
+ {
+ int tt = peekToken();
+ if (tt == Token.YIELD) {
+ consumeToken();
+ return returnOrYield(tt, true);
+ }
+ Node pn = condExpr(inForInit);
+
+ tt = peekToken();
+ if (Token.FIRST_ASSIGN <= tt && tt <= Token.LAST_ASSIGN) {
+ consumeToken();
+ decompiler.addToken(tt);
+ pn = nf.createAssignment(tt, pn, assignExpr(inForInit));
+ }
+
+ return pn;
+ }
+
+ private Node condExpr(boolean inForInit)
+ throws IOException, ParserException
+ {
+ Node pn = orExpr(inForInit);
+
+ if (matchToken(Token.HOOK)) {
+ decompiler.addToken(Token.HOOK);
+ Node ifTrue = assignExpr(false);
+ mustMatchToken(Token.COLON, "msg.no.colon.cond");
+ decompiler.addToken(Token.COLON);
+ Node ifFalse = assignExpr(inForInit);
+ return nf.createCondExpr(pn, ifTrue, ifFalse);
+ }
+
+ return pn;
+ }
+
+ private Node orExpr(boolean inForInit)
+ throws IOException, ParserException
+ {
+ Node pn = andExpr(inForInit);
+ if (matchToken(Token.OR)) {
+ decompiler.addToken(Token.OR);
+ pn = nf.createBinary(Token.OR, pn, orExpr(inForInit));
+ }
+
+ return pn;
+ }
+
+ private Node andExpr(boolean inForInit)
+ throws IOException, ParserException
+ {
+ Node pn = bitOrExpr(inForInit);
+ if (matchToken(Token.AND)) {
+ decompiler.addToken(Token.AND);
+ pn = nf.createBinary(Token.AND, pn, andExpr(inForInit));
+ }
+
+ return pn;
+ }
+
+ private Node bitOrExpr(boolean inForInit)
+ throws IOException, ParserException
+ {
+ Node pn = bitXorExpr(inForInit);
+ while (matchToken(Token.BITOR)) {
+ decompiler.addToken(Token.BITOR);
+ pn = nf.createBinary(Token.BITOR, pn, bitXorExpr(inForInit));
+ }
+ return pn;
+ }
+
+ private Node bitXorExpr(boolean inForInit)
+ throws IOException, ParserException
+ {
+ Node pn = bitAndExpr(inForInit);
+ while (matchToken(Token.BITXOR)) {
+ decompiler.addToken(Token.BITXOR);
+ pn = nf.createBinary(Token.BITXOR, pn, bitAndExpr(inForInit));
+ }
+ return pn;
+ }
+
+ private Node bitAndExpr(boolean inForInit)
+ throws IOException, ParserException
+ {
+ Node pn = eqExpr(inForInit);
+ while (matchToken(Token.BITAND)) {
+ decompiler.addToken(Token.BITAND);
+ pn = nf.createBinary(Token.BITAND, pn, eqExpr(inForInit));
+ }
+ return pn;
+ }
+
+ private Node eqExpr(boolean inForInit)
+ throws IOException, ParserException
+ {
+ Node pn = relExpr(inForInit);
+ for (;;) {
+ int tt = peekToken();
+ switch (tt) {
+ case Token.EQ:
+ case Token.NE:
+ case Token.SHEQ:
+ case Token.SHNE:
+ consumeToken();
+ int decompilerToken = tt;
+ int parseToken = tt;
+ if (compilerEnv.getLanguageVersion() == Context.VERSION_1_2) {
+ // JavaScript 1.2 uses shallow equality for == and != .
+ // In addition, convert === and !== for decompiler into
+ // == and != since the decompiler is supposed to show
+ // canonical source and in 1.2 ===, !== are allowed
+ // only as an alias to ==, !=.
+ switch (tt) {
+ case Token.EQ:
+ parseToken = Token.SHEQ;
+ break;
+ case Token.NE:
+ parseToken = Token.SHNE;
+ break;
+ case Token.SHEQ:
+ decompilerToken = Token.EQ;
+ break;
+ case Token.SHNE:
+ decompilerToken = Token.NE;
+ break;
+ }
+ }
+ decompiler.addToken(decompilerToken);
+ pn = nf.createBinary(parseToken, pn, relExpr(inForInit));
+ continue;
+ }
+ break;
+ }
+ return pn;
+ }
+
+ private Node relExpr(boolean inForInit)
+ throws IOException, ParserException
+ {
+ Node pn = shiftExpr();
+ for (;;) {
+ int tt = peekToken();
+ switch (tt) {
+ case Token.IN:
+ if (inForInit)
+ break;
+ // fall through
+ case Token.INSTANCEOF:
+ case Token.LE:
+ case Token.LT:
+ case Token.GE:
+ case Token.GT:
+ consumeToken();
+ decompiler.addToken(tt);
+ pn = nf.createBinary(tt, pn, shiftExpr());
+ continue;
+ }
+ break;
+ }
+ return pn;
+ }
+
+ private Node shiftExpr()
+ throws IOException, ParserException
+ {
+ Node pn = addExpr();
+ for (;;) {
+ int tt = peekToken();
+ switch (tt) {
+ case Token.LSH:
+ case Token.URSH:
+ case Token.RSH:
+ consumeToken();
+ decompiler.addToken(tt);
+ pn = nf.createBinary(tt, pn, addExpr());
+ continue;
+ }
+ break;
+ }
+ return pn;
+ }
+
+ private Node addExpr()
+ throws IOException, ParserException
+ {
+ Node pn = mulExpr();
+ for (;;) {
+ int tt = peekToken();
+ if (tt == Token.ADD || tt == Token.SUB) {
+ consumeToken();
+ decompiler.addToken(tt);
+ // flushNewLines
+ pn = nf.createBinary(tt, pn, mulExpr());
+ continue;
+ }
+ break;
+ }
+
+ return pn;
+ }
+
+ private Node mulExpr()
+ throws IOException, ParserException
+ {
+ Node pn = unaryExpr();
+ for (;;) {
+ int tt = peekToken();
+ switch (tt) {
+ case Token.MUL:
+ case Token.DIV:
+ case Token.MOD:
+ consumeToken();
+ decompiler.addToken(tt);
+ pn = nf.createBinary(tt, pn, unaryExpr());
+ continue;
+ }
+ break;
+ }
+
+ return pn;
+ }
+
+ private Node unaryExpr()
+ throws IOException, ParserException
+ {
+ int tt;
+
+ tt = peekToken();
+
+ switch(tt) {
+ case Token.VOID:
+ case Token.NOT:
+ case Token.BITNOT:
+ case Token.TYPEOF:
+ consumeToken();
+ decompiler.addToken(tt);
+ return nf.createUnary(tt, unaryExpr());
+
+ case Token.ADD:
+ consumeToken();
+ // Convert to special POS token in decompiler and parse tree
+ decompiler.addToken(Token.POS);
+ return nf.createUnary(Token.POS, unaryExpr());
+
+ case Token.SUB:
+ consumeToken();
+ // Convert to special NEG token in decompiler and parse tree
+ decompiler.addToken(Token.NEG);
+ return nf.createUnary(Token.NEG, unaryExpr());
+
+ case Token.INC:
+ case Token.DEC:
+ consumeToken();
+ decompiler.addToken(tt);
+ return nf.createIncDec(tt, false, memberExpr(true));
+
+ case Token.DELPROP:
+ consumeToken();
+ decompiler.addToken(Token.DELPROP);
+ return nf.createUnary(Token.DELPROP, unaryExpr());
+
+ case Token.ERROR:
+ consumeToken();
+ break;
+
+ // XML stream encountered in expression.
+ case Token.LT:
+ if (compilerEnv.isXmlAvailable()) {
+ consumeToken();
+ Node pn = xmlInitializer();
+ return memberExprTail(true, pn);
+ }
+ // Fall thru to the default handling of RELOP
+
+ default:
+ Node pn = memberExpr(true);
+
+ // Don't look across a newline boundary for a postfix incop.
+ tt = peekTokenOrEOL();
+ if (tt == Token.INC || tt == Token.DEC) {
+ consumeToken();
+ decompiler.addToken(tt);
+ return nf.createIncDec(tt, true, pn);
+ }
+ return pn;
+ }
+ return nf.createName("error"); // Only reached on error.Try to continue.
+
+ }
+
+ private Node xmlInitializer() throws IOException
+ {
+ int tt = ts.getFirstXMLToken();
+ if (tt != Token.XML && tt != Token.XMLEND) {
+ reportError("msg.syntax");
+ return null;
+ }
+
+ /* Make a NEW node to append to. */
+ Node pnXML = nf.createLeaf(Token.NEW);
+
+ String xml = ts.getString();
+ boolean fAnonymous = xml.trim().startsWith("<>");
+
+ Node pn = nf.createName(fAnonymous ? "XMLList" : "XML");
+ nf.addChildToBack(pnXML, pn);
+
+ pn = null;
+ Node expr;
+ for (;;tt = ts.getNextXMLToken()) {
+ switch (tt) {
+ case Token.XML:
+ xml = ts.getString();
+ decompiler.addName(xml);
+ mustMatchToken(Token.LC, "msg.syntax");
+ decompiler.addToken(Token.LC);
+ expr = (peekToken() == Token.RC)
+ ? nf.createString("")
+ : expr(false);
+ mustMatchToken(Token.RC, "msg.syntax");
+ decompiler.addToken(Token.RC);
+ if (pn == null) {
+ pn = nf.createString(xml);
+ } else {
+ pn = nf.createBinary(Token.ADD, pn, nf.createString(xml));
+ }
+ if (ts.isXMLAttribute()) {
+ /* Need to put the result in double quotes */
+ expr = nf.createUnary(Token.ESCXMLATTR, expr);
+ Node prepend = nf.createBinary(Token.ADD,
+ nf.createString("\""),
+ expr);
+ expr = nf.createBinary(Token.ADD,
+ prepend,
+ nf.createString("\""));
+ } else {
+ expr = nf.createUnary(Token.ESCXMLTEXT, expr);
+ }
+ pn = nf.createBinary(Token.ADD, pn, expr);
+ break;
+ case Token.XMLEND:
+ xml = ts.getString();
+ decompiler.addName(xml);
+ if (pn == null) {
+ pn = nf.createString(xml);
+ } else {
+ pn = nf.createBinary(Token.ADD, pn, nf.createString(xml));
+ }
+
+ nf.addChildToBack(pnXML, pn);
+ return pnXML;
+ default:
+ reportError("msg.syntax");
+ return null;
+ }
+ }
+ }
+
+ private void argumentList(Node listNode)
+ throws IOException, ParserException
+ {
+ boolean matched;
+ matched = matchToken(Token.RP);
+ if (!matched) {
+ boolean first = true;
+ do {
+ if (!first)
+ decompiler.addToken(Token.COMMA);
+ first = false;
+ if (peekToken() == Token.YIELD) {
+ reportError("msg.yield.parenthesized");
+ }
+ nf.addChildToBack(listNode, assignExpr(false));
+ } while (matchToken(Token.COMMA));
+
+ mustMatchToken(Token.RP, "msg.no.paren.arg");
+ }
+ decompiler.addToken(Token.RP);
+ }
+
+ private Node memberExpr(boolean allowCallSyntax)
+ throws IOException, ParserException
+ {
+ int tt;
+
+ Node pn;
+
+ /* Check for new expressions. */
+ tt = peekToken();
+ if (tt == Token.NEW) {
+ /* Eat the NEW token. */
+ consumeToken();
+ decompiler.addToken(Token.NEW);
+
+ /* Make a NEW node to append to. */
+ pn = nf.createCallOrNew(Token.NEW, memberExpr(false));
+
+ if (matchToken(Token.LP)) {
+ decompiler.addToken(Token.LP);
+ /* Add the arguments to pn, if any are supplied. */
+ argumentList(pn);
+ }
+
+ /* XXX there's a check in the C source against
+ * "too many constructor arguments" - how many
+ * do we claim to support?
+ */
+
+ /* Experimental syntax: allow an object literal to follow a new expression,
+ * which will mean a kind of anonymous class built with the JavaAdapter.
+ * the object literal will be passed as an additional argument to the constructor.
+ */
+ tt = peekToken();
+ if (tt == Token.LC) {
+ nf.addChildToBack(pn, primaryExpr());
+ }
+ } else {
+ pn = primaryExpr();
+ }
+
+ return memberExprTail(allowCallSyntax, pn);
+ }
+
+ private Node memberExprTail(boolean allowCallSyntax, Node pn)
+ throws IOException, ParserException
+ {
+ tailLoop:
+ for (;;) {
+ int tt = peekToken();
+ switch (tt) {
+
+ case Token.DOT:
+ case Token.DOTDOT:
+ {
+ int memberTypeFlags;
+ String s;
+
+ consumeToken();
+ decompiler.addToken(tt);
+ memberTypeFlags = 0;
+ if (tt == Token.DOTDOT) {
+ mustHaveXML();
+ memberTypeFlags = Node.DESCENDANTS_FLAG;
+ }
+ if (!compilerEnv.isXmlAvailable()) {
+ mustMatchToken(Token.NAME, "msg.no.name.after.dot");
+ s = ts.getString();
+ decompiler.addName(s);
+ pn = nf.createPropertyGet(pn, null, s, memberTypeFlags);
+ break;
+ }
+
+ tt = nextToken();
+ switch (tt) {
+
+ // needed for generator.throw();
+ case Token.THROW:
+ decompiler.addName("throw");
+ pn = propertyName(pn, "throw", memberTypeFlags);
+ break;
+
+ // handles: name, ns::name, ns::*, ns::[expr]
+ case Token.NAME:
+ s = ts.getString();
+ decompiler.addName(s);
+ pn = propertyName(pn, s, memberTypeFlags);
+ break;
+
+ // handles: *, *::name, *::*, *::[expr]
+ case Token.MUL:
+ decompiler.addName("*");
+ pn = propertyName(pn, "*", memberTypeFlags);
+ break;
+
+ // handles: '@attr', '@ns::attr', '@ns::*', '@ns::*',
+ // '@::attr', '@::*', '@*', '@*::attr', '@*::*'
+ case Token.XMLATTR:
+ decompiler.addToken(Token.XMLATTR);
+ pn = attributeAccess(pn, memberTypeFlags);
+ break;
+
+ default:
+ reportError("msg.no.name.after.dot");
+ }
+ }
+ break;
+
+ case Token.DOTQUERY:
+ consumeToken();
+ mustHaveXML();
+ decompiler.addToken(Token.DOTQUERY);
+ pn = nf.createDotQuery(pn, expr(false), ts.getLineno());
+ mustMatchToken(Token.RP, "msg.no.paren");
+ decompiler.addToken(Token.RP);
+ break;
+
+ case Token.LB:
+ consumeToken();
+ decompiler.addToken(Token.LB);
+ pn = nf.createElementGet(pn, null, expr(false), 0);
+ mustMatchToken(Token.RB, "msg.no.bracket.index");
+ decompiler.addToken(Token.RB);
+ break;
+
+ case Token.LP:
+ if (!allowCallSyntax) {
+ break tailLoop;
+ }
+ consumeToken();
+ decompiler.addToken(Token.LP);
+ pn = nf.createCallOrNew(Token.CALL, pn);
+ /* Add the arguments to pn, if any are supplied. */
+ argumentList(pn);
+ break;
+
+ default:
+ break tailLoop;
+ }
+ }
+ return pn;
+ }
+
+ /*
+ * Xml attribute expression:
+ * '@attr', '@ns::attr', '@ns::*', '@ns::*', '@*', '@*::attr', '@*::*'
+ */
+ private Node attributeAccess(Node pn, int memberTypeFlags)
+ throws IOException
+ {
+ memberTypeFlags |= Node.ATTRIBUTE_FLAG;
+ int tt = nextToken();
+
+ switch (tt) {
+ // handles: @name, @ns::name, @ns::*, @ns::[expr]
+ case Token.NAME:
+ {
+ String s = ts.getString();
+ decompiler.addName(s);
+ pn = propertyName(pn, s, memberTypeFlags);
+ }
+ break;
+
+ // handles: @*, @*::name, @*::*, @*::[expr]
+ case Token.MUL:
+ decompiler.addName("*");
+ pn = propertyName(pn, "*", memberTypeFlags);
+ break;
+
+ // handles @[expr]
+ case Token.LB:
+ decompiler.addToken(Token.LB);
+ pn = nf.createElementGet(pn, null, expr(false), memberTypeFlags);
+ mustMatchToken(Token.RB, "msg.no.bracket.index");
+ decompiler.addToken(Token.RB);
+ break;
+
+ default:
+ reportError("msg.no.name.after.xmlAttr");
+ pn = nf.createPropertyGet(pn, null, "?", memberTypeFlags);
+ break;
+ }
+
+ return pn;
+ }
+
+ /**
+ * Check if :: follows name in which case it becomes qualified name
+ */
+ private Node propertyName(Node pn, String name, int memberTypeFlags)
+ throws IOException, ParserException
+ {
+ String namespace = null;
+ if (matchToken(Token.COLONCOLON)) {
+ decompiler.addToken(Token.COLONCOLON);
+ namespace = name;
+
+ int tt = nextToken();
+ switch (tt) {
+ // handles name::name
+ case Token.NAME:
+ name = ts.getString();
+ decompiler.addName(name);
+ break;
+
+ // handles name::*
+ case Token.MUL:
+ decompiler.addName("*");
+ name = "*";
+ break;
+
+ // handles name::[expr]
+ case Token.LB:
+ decompiler.addToken(Token.LB);
+ pn = nf.createElementGet(pn, namespace, expr(false),
+ memberTypeFlags);
+ mustMatchToken(Token.RB, "msg.no.bracket.index");
+ decompiler.addToken(Token.RB);
+ return pn;
+
+ default:
+ reportError("msg.no.name.after.coloncolon");
+ name = "?";
+ }
+ }
+
+ pn = nf.createPropertyGet(pn, namespace, name, memberTypeFlags);
+ return pn;
+ }
+
+ private Node arrayComprehension(String arrayName, Node expr)
+ throws IOException, ParserException
+ {
+ if (nextToken() != Token.FOR)
+ throw Kit.codeBug(); // shouldn't be here if next token isn't 'for'
+ decompiler.addName(" "); // space after array literal expr
+ decompiler.addToken(Token.FOR);
+ boolean isForEach = false;
+ if (matchToken(Token.NAME)) {
+ decompiler.addName(ts.getString());
+ if (ts.getString().equals("each")) {
+ isForEach = true;
+ } else {
+ reportError("msg.no.paren.for");
+ }
+ }
+ mustMatchToken(Token.LP, "msg.no.paren.for");
+ decompiler.addToken(Token.LP);
+ String name;
+ int tt = peekToken();
+ if (tt == Token.LB || tt == Token.LC) {
+ // handle destructuring assignment
+ name = currentScriptOrFn.getNextTempName();
+ defineSymbol(Token.LP, false, name);
+ expr = nf.createBinary(Token.COMMA,
+ nf.createAssignment(Token.ASSIGN, primaryExpr(),
+ nf.createName(name)),
+ expr);
+ } else if (tt == Token.NAME) {
+ consumeToken();
+ name = ts.getString();
+ decompiler.addName(name);
+ } else {
+ reportError("msg.bad.var");
+ return nf.createNumber(0);
+ }
+
+ Node init = nf.createName(name);
+ // Define as a let since we want the scope of the variable to
+ // be restricted to the array comprehension
+ defineSymbol(Token.LET, false, name);
+
+ mustMatchToken(Token.IN, "msg.in.after.for.name");
+ decompiler.addToken(Token.IN);
+ Node iterator = expr(false);
+ mustMatchToken(Token.RP, "msg.no.paren.for.ctrl");
+ decompiler.addToken(Token.RP);
+
+ Node body;
+ tt = peekToken();
+ if (tt == Token.FOR) {
+ body = arrayComprehension(arrayName, expr);
+ } else {
+ Node call = nf.createCallOrNew(Token.CALL,
+ nf.createPropertyGet(nf.createName(arrayName), null,
+ "push", 0));
+ call.addChildToBack(expr);
+ body = new Node(Token.EXPR_VOID, call, ts.getLineno());
+ if (tt == Token.IF) {
+ consumeToken();
+ decompiler.addToken(Token.IF);
+ int lineno = ts.getLineno();
+ Node cond = condition();
+ body = nf.createIf(cond, body, null, lineno);
+ }
+ mustMatchToken(Token.RB, "msg.no.bracket.arg");
+ decompiler.addToken(Token.RB);
+ }
+
+ Node loop = enterLoop(null, true);
+ try {
+ return nf.createForIn(Token.LET, loop, init, iterator, body,
+ isForEach);
+ } finally {
+ exitLoop(false);
+ }
+ }
+
+ private Node primaryExpr()
+ throws IOException, ParserException
+ {
+ Node pn;
+
+ int ttFlagged = nextFlaggedToken();
+ int tt = ttFlagged & CLEAR_TI_MASK;
+
+ switch(tt) {
+
+ case Token.FUNCTION:
+ return function(FunctionNode.FUNCTION_EXPRESSION);
+
+ case Token.LB: {
+ ObjArray elems = new ObjArray();
+ int skipCount = 0;
+ int destructuringLen = 0;
+ decompiler.addToken(Token.LB);
+ boolean after_lb_or_comma = true;
+ for (;;) {
+ tt = peekToken();
+
+ if (tt == Token.COMMA) {
+ consumeToken();
+ decompiler.addToken(Token.COMMA);
+ if (!after_lb_or_comma) {
+ after_lb_or_comma = true;
+ } else {
+ elems.add(null);
+ ++skipCount;
+ }
+ } else if (tt == Token.RB) {
+ consumeToken();
+ decompiler.addToken(Token.RB);
+ // for ([a,] in obj) is legal, but for ([a] in obj) is
+ // not since we have both key and value supplied. The
+ // trick is that [a,] and [a] are equivalent in other
+ // array literal contexts. So we calculate a special
+ // length value just for destructuring assignment.
+ destructuringLen = elems.size() +
+ (after_lb_or_comma ? 1 : 0);
+ break;
+ } else if (skipCount == 0 && elems.size() == 1 &&
+ tt == Token.FOR)
+ {
+ Node scopeNode = nf.createScopeNode(Token.ARRAYCOMP,
+ ts.getLineno());
+ String tempName = currentScriptOrFn.getNextTempName();
+ pushScope(scopeNode);
+ try {
+ defineSymbol(Token.LET, false, tempName);
+ Node expr = (Node) elems.get(0);
+ Node block = nf.createBlock(ts.getLineno());
+ Node init = new Node(Token.EXPR_VOID,
+ nf.createAssignment(Token.ASSIGN,
+ nf.createName(tempName),
+ nf.createCallOrNew(Token.NEW,
+ nf.createName("Array"))), ts.getLineno());
+ block.addChildToBack(init);
+ block.addChildToBack(arrayComprehension(tempName,
+ expr));
+ scopeNode.addChildToBack(block);
+ scopeNode.addChildToBack(nf.createName(tempName));
+ return scopeNode;
+ } finally {
+ popScope();
+ }
+ } else {
+ if (!after_lb_or_comma) {
+ reportError("msg.no.bracket.arg");
+ }
+ elems.add(assignExpr(false));
+ after_lb_or_comma = false;
+ }
+ }
+ return nf.createArrayLiteral(elems, skipCount, destructuringLen);
+ }
+
+ case Token.LC: {
+ ObjArray elems = new ObjArray();
+ decompiler.addToken(Token.LC);
+ if (!matchToken(Token.RC)) {
+
+ boolean first = true;
+ commaloop:
+ do {
+ Object property;
+
+ if (!first)
+ decompiler.addToken(Token.COMMA);
+ else
+ first = false;
+
+ tt = peekToken();
+ switch(tt) {
+ case Token.NAME:
+ case Token.STRING:
+ consumeToken();
+ // map NAMEs to STRINGs in object literal context
+ // but tell the decompiler the proper type
+ String s = ts.getString();
+ if (tt == Token.NAME) {
+ if (s.equals("get") &&
+ peekToken() == Token.NAME) {
+ decompiler.addToken(Token.GET);
+ consumeToken();
+ s = ts.getString();
+ decompiler.addName(s);
+ property = ScriptRuntime.getIndexObject(s);
+ if (!getterSetterProperty(elems, property,
+ true))
+ break commaloop;
+ break;
+ } else if (s.equals("set") &&
+ peekToken() == Token.NAME) {
+ decompiler.addToken(Token.SET);
+ consumeToken();
+ s = ts.getString();
+ decompiler.addName(s);
+ property = ScriptRuntime.getIndexObject(s);
+ if (!getterSetterProperty(elems, property,
+ false))
+ break commaloop;
+ break;
+ }
+ decompiler.addName(s);
+ } else {
+ decompiler.addString(s);
+ }
+ property = ScriptRuntime.getIndexObject(s);
+ plainProperty(elems, property);
+ break;
+
+ case Token.NUMBER:
+ consumeToken();
+ double n = ts.getNumber();
+ decompiler.addNumber(n);
+ property = ScriptRuntime.getIndexObject(n);
+ plainProperty(elems, property);
+ break;
+
+ case Token.RC:
+ // trailing comma is OK.
+ break commaloop;
+ default:
+ reportError("msg.bad.prop");
+ break commaloop;
+ }
+ } while (matchToken(Token.COMMA));
+
+ mustMatchToken(Token.RC, "msg.no.brace.prop");
+ }
+ decompiler.addToken(Token.RC);
+ return nf.createObjectLiteral(elems);
+ }
+
+ case Token.LET:
+ decompiler.addToken(Token.LET);
+ return let(false);
+
+ case Token.LP:
+
+ /* Brendan's IR-jsparse.c makes a new node tagged with
+ * TOK_LP here... I'm not sure I understand why. Isn't
+ * the grouping already implicit in the structure of the
+ * parse tree? also TOK_LP is already overloaded (I
+ * think) in the C IR as 'function call.' */
+ decompiler.addToken(Token.LP);
+ pn = expr(false);
+ pn.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE);
+ decompiler.addToken(Token.RP);
+ mustMatchToken(Token.RP, "msg.no.paren");
+ return pn;
+
+ case Token.XMLATTR:
+ mustHaveXML();
+ decompiler.addToken(Token.XMLATTR);
+ pn = attributeAccess(null, 0);
+ return pn;
+
+ case Token.NAME: {
+ String name = ts.getString();
+ if ((ttFlagged & TI_CHECK_LABEL) != 0) {
+ if (peekToken() == Token.COLON) {
+ // Do not consume colon, it is used as unwind indicator
+ // to return to statementHelper.
+ // XXX Better way?
+ return nf.createLabel(ts.getLineno());
+ }
+ }
+
+ decompiler.addName(name);
+ if (compilerEnv.isXmlAvailable()) {
+ pn = propertyName(null, name, 0);
+ } else {
+ pn = nf.createName(name);
+ }
+ return pn;
+ }
+
+ case Token.NUMBER: {
+ double n = ts.getNumber();
+ decompiler.addNumber(n);
+ return nf.createNumber(n);
+ }
+
+ case Token.STRING: {
+ String s = ts.getString();
+ decompiler.addString(s);
+ return nf.createString(s);
+ }
+
+ case Token.DIV:
+ case Token.ASSIGN_DIV: {
+ // Got / or /= which should be treated as regexp in fact
+ ts.readRegExp(tt);
+ String flags = ts.regExpFlags;
+ ts.regExpFlags = null;
+ String re = ts.getString();
+ decompiler.addRegexp(re, flags);
+ int index = currentScriptOrFn.addRegexp(re, flags);
+ return nf.createRegExp(index);
+ }
+
+ case Token.NULL:
+ case Token.THIS:
+ case Token.FALSE:
+ case Token.TRUE:
+ decompiler.addToken(tt);
+ return nf.createLeaf(tt);
+
+ case Token.RESERVED:
+ reportError("msg.reserved.id");
+ break;
+
+ case Token.ERROR:
+ /* the scanner or one of its subroutines reported the error. */
+ break;
+
+ case Token.EOF:
+ reportError("msg.unexpected.eof");
+ break;
+
+ default:
+ reportError("msg.syntax");
+ break;
+ }
+ return null; // should never reach here
+ }
+
+ private void plainProperty(ObjArray elems, Object property)
+ throws IOException {
+ mustMatchToken(Token.COLON, "msg.no.colon.prop");
+
+ // OBJLIT is used as ':' in object literal for
+ // decompilation to solve spacing ambiguity.
+ decompiler.addToken(Token.OBJECTLIT);
+ elems.add(property);
+ elems.add(assignExpr(false));
+ }
+
+ private boolean getterSetterProperty(ObjArray elems, Object property,
+ boolean isGetter) throws IOException {
+ Node f = function(FunctionNode.FUNCTION_EXPRESSION);
+ if (f.getType() != Token.FUNCTION) {
+ reportError("msg.bad.prop");
+ return false;
+ }
+ int fnIndex = f.getExistingIntProp(Node.FUNCTION_PROP);
+ FunctionNode fn = currentScriptOrFn.getFunctionNode(fnIndex);
+ if (fn.getFunctionName().length() != 0) {
+ reportError("msg.bad.prop");
+ return false;
+ }
+ elems.add(property);
+ if (isGetter) {
+ elems.add(nf.createUnary(Token.GET, f));
+ } else {
+ elems.add(nf.createUnary(Token.SET, f));
+ }
+ return true;
+ }
+}
diff --git a/src/org/mozilla/javascript/PolicySecurityController.java b/src/org/mozilla/javascript/PolicySecurityController.java
new file mode 100644
index 0000000..49cb124
--- /dev/null
+++ b/src/org/mozilla/javascript/PolicySecurityController.java
@@ -0,0 +1,223 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.lang.ref.SoftReference;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.SecureClassLoader;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.mozilla.classfile.ByteCode;
+import org.mozilla.classfile.ClassFileWriter;
+
+/**
+ * A security controller relying on Java {@link Policy} in effect. When you use
+ * this security controller, your securityDomain objects must be instances of
+ * {@link CodeSource} representing the location from where you load your
+ * scripts. Any Java policy "grant" statements matching the URL and certificate
+ * in code sources will apply to the scripts. If you specify any certificates
+ * within your {@link CodeSource} objects, it is your responsibility to verify
+ * (or not) that the script source files are signed in whatever
+ * implementation-specific way you're using.
+ * @author Attila Szegedi
+ */
+public class PolicySecurityController extends SecurityController
+{
+ private static final byte[] secureCallerImplBytecode = loadBytecode();
+
+ // We're storing a CodeSource -> (ClassLoader -> SecureRenderer), since we
+ // need to have one renderer per class loader. We're using weak hash maps
+ // and soft references all the way, since we don't want to interfere with
+ // cleanup of either CodeSource or ClassLoader objects.
+ private static final Map<CodeSource,Map<ClassLoader,SoftReference<SecureCaller>>>
+ callers =
+ new WeakHashMap<CodeSource,Map<ClassLoader,SoftReference<SecureCaller>>>();
+
+ @Override
+ public Class<?> getStaticSecurityDomainClassInternal() {
+ return CodeSource.class;
+ }
+
+ private static class Loader extends SecureClassLoader
+ implements GeneratedClassLoader
+ {
+ private final CodeSource codeSource;
+
+ Loader(ClassLoader parent, CodeSource codeSource)
+ {
+ super(parent);
+ this.codeSource = codeSource;
+ }
+
+ public Class<?> defineClass(String name, byte[] data)
+ {
+ return defineClass(name, data, 0, data.length, codeSource);
+ }
+
+ public void linkClass(Class<?> cl)
+ {
+ resolveClass(cl);
+ }
+ }
+
+ @Override
+ public GeneratedClassLoader createClassLoader(final ClassLoader parent,
+ final Object securityDomain)
+ {
+ return (Loader)AccessController.doPrivileged(
+ new PrivilegedAction<Object>()
+ {
+ public Object run()
+ {
+ return new Loader(parent, (CodeSource)securityDomain);
+ }
+ });
+ }
+
+ @Override
+ public Object getDynamicSecurityDomain(Object securityDomain)
+ {
+ // No separate notion of dynamic security domain - just return what was
+ // passed in.
+ return securityDomain;
+ }
+
+ @Override
+ public Object callWithDomain(final Object securityDomain, final Context cx,
+ Callable callable, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ // Run in doPrivileged as we might be checked for "getClassLoader"
+ // runtime permission
+ final ClassLoader classLoader = (ClassLoader)AccessController.doPrivileged(
+ new PrivilegedAction<Object>() {
+ public Object run() {
+ return cx.getApplicationClassLoader();
+ }
+ });
+ final CodeSource codeSource = (CodeSource)securityDomain;
+ Map<ClassLoader,SoftReference<SecureCaller>> classLoaderMap;
+ synchronized (callers) {
+ classLoaderMap = callers.get(codeSource);
+ if(classLoaderMap == null) {
+ classLoaderMap = new WeakHashMap<ClassLoader,SoftReference<SecureCaller>>();
+ callers.put(codeSource, classLoaderMap);
+ }
+ }
+ SecureCaller caller;
+ synchronized (classLoaderMap) {
+ SoftReference<SecureCaller> ref = classLoaderMap.get(classLoader);
+ if (ref != null) {
+ caller = ref.get();
+ } else {
+ caller = null;
+ }
+ if (caller == null)
+ {
+ try
+ {
+ // Run in doPrivileged as we'll be checked for
+ // "createClassLoader" runtime permission
+ caller = (SecureCaller)AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Object>()
+ {
+ public Object run() throws Exception
+ {
+ Loader loader = new Loader(classLoader,
+ codeSource);
+ Class<?> c = loader.defineClass(
+ SecureCaller.class.getName() + "Impl",
+ secureCallerImplBytecode);
+ return c.newInstance();
+ }
+ });
+ classLoaderMap.put(classLoader, new SoftReference<SecureCaller>(caller));
+ }
+ catch(PrivilegedActionException ex)
+ {
+ throw new UndeclaredThrowableException(ex.getCause());
+ }
+ }
+ }
+ return caller.call(callable, cx, scope, thisObj, args);
+ }
+
+ public abstract static class SecureCaller
+ {
+ public abstract Object call(Callable callable, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args);
+ }
+
+
+ private static byte[] loadBytecode()
+ {
+ String secureCallerClassName = SecureCaller.class.getName();
+ ClassFileWriter cfw = new ClassFileWriter(
+ secureCallerClassName + "Impl", secureCallerClassName,
+ "<generated>");
+ cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC);
+ cfw.addALoad(0);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, secureCallerClassName,
+ "<init>", "()V");
+ cfw.add(ByteCode.RETURN);
+ cfw.stopMethod((short)1);
+ String callableCallSig =
+ "Lorg/mozilla/javascript/Context;" +
+ "Lorg/mozilla/javascript/Scriptable;" +
+ "Lorg/mozilla/javascript/Scriptable;" +
+ "[Ljava/lang/Object;)Ljava/lang/Object;";
+
+ cfw.startMethod("call",
+ "(Lorg/mozilla/javascript/Callable;" + callableCallSig,
+ (short)(ClassFileWriter.ACC_PUBLIC
+ | ClassFileWriter.ACC_FINAL));
+ for(int i = 1; i < 6; ++i) {
+ cfw.addALoad(i);
+ }
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/Callable", "call",
+ "(" + callableCallSig);
+ cfw.add(ByteCode.ARETURN);
+ cfw.stopMethod((short)6);
+ return cfw.toByteArray();
+ }
+}
\ No newline at end of file
diff --git a/src/org/mozilla/javascript/Ref.java b/src/org/mozilla/javascript/Ref.java
new file mode 100644
index 0000000..1e237bc
--- /dev/null
+++ b/src/org/mozilla/javascript/Ref.java
@@ -0,0 +1,64 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov, igor at fastmail.fm
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.Serializable;
+
+/**
+ * Generic notion of reference object that know how to query/modify the
+ * target objects based on some property/index.
+ */
+public abstract class Ref implements Serializable
+{
+ public boolean has(Context cx)
+ {
+ return true;
+ }
+
+ public abstract Object get(Context cx);
+
+ public abstract Object set(Context cx, Object value);
+
+ public boolean delete(Context cx)
+ {
+ return false;
+ }
+
+}
+
diff --git a/src/org/mozilla/javascript/RefCallable.java b/src/org/mozilla/javascript/RefCallable.java
new file mode 100644
index 0000000..6d4b61c
--- /dev/null
+++ b/src/org/mozilla/javascript/RefCallable.java
@@ -0,0 +1,59 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov, igor at mir2.org
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * Object that can allows assignments to the result of function calls.
+ */
+public interface RefCallable extends Callable
+{
+ /**
+ * Perform function call in reference context.
+ * The args array reference should not be stored in any object that is
+ * can be GC-reachable after this method returns. If this is necessary,
+ * for example, to implement {@link Ref} methods, then store args.clone(),
+ * not args array itself.
+ *
+ * @param cx the current Context for this thread
+ * @param thisObj the JavaScript <code>this</code> object
+ * @param args the array of arguments
+ */
+ public Ref refCall(Context cx, Scriptable thisObj, Object[] args);
+}
+
diff --git a/src/org/mozilla/javascript/RegExpProxy.java b/src/org/mozilla/javascript/RegExpProxy.java
new file mode 100644
index 0000000..ac29c6e
--- /dev/null
+++ b/src/org/mozilla/javascript/RegExpProxy.java
@@ -0,0 +1,71 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * A proxy for the regexp package, so that the regexp package can be
+ * loaded optionally.
+ *
+ * @author Norris Boyd
+ */
+public interface RegExpProxy
+{
+ // Types of regexp actions
+
+ public static final int RA_MATCH = 1;
+ public static final int RA_REPLACE = 2;
+ public static final int RA_SEARCH = 3;
+
+ public boolean isRegExp(Scriptable obj);
+
+ public Object compileRegExp(Context cx, String source, String flags);
+
+ public Scriptable wrapRegExp(Context cx, Scriptable scope,
+ Object compiled);
+
+ public Object action(Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args,
+ int actionType);
+
+ public int find_split(Context cx, Scriptable scope, String target,
+ String separator, Scriptable re,
+ int[] ip, int[] matchlen,
+ boolean[] matched, String[][] parensp);
+}
diff --git a/src/org/mozilla/javascript/RhinoException.java b/src/org/mozilla/javascript/RhinoException.java
new file mode 100644
index 0000000..f4fa524
--- /dev/null
+++ b/src/org/mozilla/javascript/RhinoException.java
@@ -0,0 +1,308 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+package org.mozilla.javascript;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * The class of exceptions thrown by the JavaScript engine.
+ */
+public abstract class RhinoException extends RuntimeException
+{
+ RhinoException()
+ {
+ Evaluator e = Context.createInterpreter();
+ if (e != null)
+ e.captureStackInfo(this);
+ }
+
+ RhinoException(String details)
+ {
+ super(details);
+ Evaluator e = Context.createInterpreter();
+ if (e != null)
+ e.captureStackInfo(this);
+ }
+
+ @Override
+ public final String getMessage()
+ {
+ String details = details();
+ if (sourceName == null || lineNumber <= 0) {
+ return details;
+ }
+ StringBuffer buf = new StringBuffer(details);
+ buf.append(" (");
+ if (sourceName != null) {
+ buf.append(sourceName);
+ }
+ if (lineNumber > 0) {
+ buf.append('#');
+ buf.append(lineNumber);
+ }
+ buf.append(')');
+ return buf.toString();
+ }
+
+ public String details()
+ {
+ return super.getMessage();
+ }
+
+ /**
+ * Get the uri of the script source containing the error, or null
+ * if that information is not available.
+ */
+ public final String sourceName()
+ {
+ return sourceName;
+ }
+
+ /**
+ * Initialize the uri of the script source containing the error.
+ *
+ * @param sourceName the uri of the script source responsible for the error.
+ * It should not be <tt>null</tt>.
+ *
+ * @throws IllegalStateException if the method is called more then once.
+ */
+ public final void initSourceName(String sourceName)
+ {
+ if (sourceName == null) throw new IllegalArgumentException();
+ if (this.sourceName != null) throw new IllegalStateException();
+ this.sourceName = sourceName;
+ }
+
+ /**
+ * Returns the line number of the statement causing the error,
+ * or zero if not available.
+ */
+ public final int lineNumber()
+ {
+ return lineNumber;
+ }
+
+ /**
+ * Initialize the line number of the script statement causing the error.
+ *
+ * @param lineNumber the line number in the script source.
+ * It should be positive number.
+ *
+ * @throws IllegalStateException if the method is called more then once.
+ */
+ public final void initLineNumber(int lineNumber)
+ {
+ if (lineNumber <= 0) throw new IllegalArgumentException(String.valueOf(lineNumber));
+ if (this.lineNumber > 0) throw new IllegalStateException();
+ this.lineNumber = lineNumber;
+ }
+
+ /**
+ * The column number of the location of the error, or zero if unknown.
+ */
+ public final int columnNumber()
+ {
+ return columnNumber;
+ }
+
+ /**
+ * Initialize the column number of the script statement causing the error.
+ *
+ * @param columnNumber the column number in the script source.
+ * It should be positive number.
+ *
+ * @throws IllegalStateException if the method is called more then once.
+ */
+ public final void initColumnNumber(int columnNumber)
+ {
+ if (columnNumber <= 0) throw new IllegalArgumentException(String.valueOf(columnNumber));
+ if (this.columnNumber > 0) throw new IllegalStateException();
+ this.columnNumber = columnNumber;
+ }
+
+ /**
+ * The source text of the line causing the error, or null if unknown.
+ */
+ public final String lineSource()
+ {
+ return lineSource;
+ }
+
+ /**
+ * Initialize the text of the source line containing the error.
+ *
+ * @param lineSource the text of the source line responsible for the error.
+ * It should not be <tt>null</tt>.
+ *
+ * @throws IllegalStateException if the method is called more then once.
+ */
+ public final void initLineSource(String lineSource)
+ {
+ if (lineSource == null) throw new IllegalArgumentException();
+ if (this.lineSource != null) throw new IllegalStateException();
+ this.lineSource = lineSource;
+ }
+
+ final void recordErrorOrigin(String sourceName, int lineNumber,
+ String lineSource, int columnNumber)
+ {
+ // XXX: for compatibility allow for now -1 to mean 0
+ if (lineNumber == -1) {
+ lineNumber = 0;
+ }
+
+ if (sourceName != null) {
+ initSourceName(sourceName);
+ }
+ if (lineNumber != 0) {
+ initLineNumber(lineNumber);
+ }
+ if (lineSource != null) {
+ initLineSource(lineSource);
+ }
+ if (columnNumber != 0) {
+ initColumnNumber(columnNumber);
+ }
+ }
+
+ private String generateStackTrace()
+ {
+ // Get stable reference to work properly with concurrent access
+ CharArrayWriter writer = new CharArrayWriter();
+ super.printStackTrace(new PrintWriter(writer));
+ String origStackTrace = writer.toString();
+ Evaluator e = Context.createInterpreter();
+ if (e != null)
+ return e.getPatchedStack(this, origStackTrace);
+ return null;
+ }
+
+ /**
+ * Get a string representing the script stack of this exception.
+ * If optimization is enabled, this corresponds to all java stack elements
+ * with a source name ending with ".js".
+ * @return a script stack dump
+ * @since 1.6R6
+ */
+ public String getScriptStackTrace()
+ {
+ return getScriptStackTrace(new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return name.endsWith(".js");
+ }
+ });
+ }
+
+ /**
+ * Get a string representing the script stack of this exception.
+ * If optimization is enabled, this corresponds to all java stack elements
+ * with a source name matching the <code>filter</code>.
+ * @param filter the file name filter to determine whether a file is a
+ * script file
+ * @return a script stack dump
+ * @since 1.6R6
+ */
+ public String getScriptStackTrace(FilenameFilter filter)
+ {
+ List<String> interpreterStack = null;
+ Evaluator interpreter = Context.createInterpreter();
+ if (interpreter != null) {
+ interpreterStack = interpreter.getScriptStack(this);
+ }
+ int interpreterStackIndex = 0;
+ StringBuffer buffer = new StringBuffer();
+ String lineSeparator = SecurityUtilities.getSystemProperty("line.separator");
+ StackTraceElement[] stack = getStackTrace();
+ for (int i = 0; i < stack.length; i++) {
+ StackTraceElement e = stack[i];
+ String name = e.getFileName();
+ if (e.getLineNumber() > -1 && name != null &&
+ filter.accept(null, name))
+ {
+ buffer.append("\tat ");
+ buffer.append(e.getFileName());
+ buffer.append(':');
+ buffer.append(e.getLineNumber());
+ buffer.append(lineSeparator);
+ } else if (interpreterStack != null &&
+ interpreterStack.size() > interpreterStackIndex &&
+ "org.mozilla.javascript.Interpreter".equals(e.getClassName()) &&
+ "interpretLoop".equals(e.getMethodName()))
+ {
+ buffer.append(interpreterStack.get(interpreterStackIndex++));
+ }
+ }
+ return buffer.toString();
+ }
+
+ @Override
+ public void printStackTrace(PrintWriter s)
+ {
+ if (interpreterStackInfo == null) {
+ super.printStackTrace(s);
+ } else {
+ s.print(generateStackTrace());
+ }
+ }
+
+ @Override
+ public void printStackTrace(PrintStream s)
+ {
+ if (interpreterStackInfo == null) {
+ super.printStackTrace(s);
+ } else {
+ s.print(generateStackTrace());
+ }
+ }
+
+ private String sourceName;
+ private int lineNumber;
+ private String lineSource;
+ private int columnNumber;
+
+ Object interpreterStackInfo;
+ int[] interpreterLineData;
+}
diff --git a/src/org/mozilla/javascript/Script.java b/src/org/mozilla/javascript/Script.java
new file mode 100644
index 0000000..4721ead
--- /dev/null
+++ b/src/org/mozilla/javascript/Script.java
@@ -0,0 +1,73 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * All compiled scripts implement this interface.
+ * <p>
+ * This class encapsulates script execution relative to an
+ * object scope.
+ * @since 1.3
+ * @author Norris Boyd
+ */
+
+public interface Script {
+
+ /**
+ * Execute the script.
+ * <p>
+ * The script is executed in a particular runtime Context, which
+ * must be associated with the current thread.
+ * The script is executed relative to a scope--definitions and
+ * uses of global top-level variables and functions will access
+ * properties of the scope object. For compliant ECMA
+ * programs, the scope must be an object that has been initialized
+ * as a global object using <code>Context.initStandardObjects</code>.
+ * <p>
+ *
+ * @param cx the Context associated with the current thread
+ * @param scope the scope to execute relative to
+ * @return the result of executing the script
+ * @see org.mozilla.javascript.Context#initStandardObjects()
+ */
+ public Object exec(Context cx, Scriptable scope);
+
+}
diff --git a/src/org/mozilla/javascript/ScriptOrFnNode.java b/src/org/mozilla/javascript/ScriptOrFnNode.java
new file mode 100644
index 0000000..ba08fb4
--- /dev/null
+++ b/src/org/mozilla/javascript/ScriptOrFnNode.java
@@ -0,0 +1,230 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ * Bob Jervis
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.util.ArrayList;
+
+public class ScriptOrFnNode extends Node.Scope {
+
+ public ScriptOrFnNode(int nodeType) {
+ super(nodeType);
+ symbols = new ArrayList<Symbol>(4);
+ setParent(null);
+ }
+
+ public final String getSourceName() { return sourceName; }
+
+ public final void setSourceName(String sourceName) {
+ this.sourceName = sourceName;
+ }
+
+ public final int getEncodedSourceStart() { return encodedSourceStart; }
+
+ public final int getEncodedSourceEnd() { return encodedSourceEnd; }
+
+ public final void setEncodedSourceBounds(int start, int end) {
+ this.encodedSourceStart = start;
+ this.encodedSourceEnd = end;
+ }
+
+ public final int getBaseLineno() { return this.lineno; }
+
+ public final void setBaseLineno(int lineno) {
+ // One time action
+ if (lineno < 0 || this.lineno >= 0) Kit.codeBug();
+ this.lineno = lineno;
+ }
+
+ public final int getEndLineno() { return endLineno; }
+
+ public final void setEndLineno(int lineno) {
+ // One time action
+ if (lineno < 0 || endLineno >= 0) Kit.codeBug();
+ endLineno = lineno;
+ }
+
+ public final int getFunctionCount() {
+ if (functions == null) { return 0; }
+ return functions.size();
+ }
+
+ public final FunctionNode getFunctionNode(int i) {
+ return (FunctionNode)functions.get(i);
+ }
+
+ public final int addFunction(FunctionNode fnNode) {
+ if (fnNode == null) Kit.codeBug();
+ if (functions == null) { functions = new ObjArray(); }
+ functions.add(fnNode);
+ return functions.size() - 1;
+ }
+
+ public final int getRegexpCount() {
+ if (regexps == null) { return 0; }
+ return regexps.size() / 2;
+ }
+
+ public final String getRegexpString(int index) {
+ return (String)regexps.get(index * 2);
+ }
+
+ public final String getRegexpFlags(int index) {
+ return (String)regexps.get(index * 2 + 1);
+ }
+
+ public final int addRegexp(String string, String flags) {
+ if (string == null) Kit.codeBug();
+ if (regexps == null) { regexps = new ObjArray(); }
+ regexps.add(string);
+ regexps.add(flags);
+ return regexps.size() / 2 - 1;
+ }
+
+ public int getIndexForNameNode(Node nameNode) {
+ if (variableNames == null) throw Kit.codeBug();
+ Node.Scope node = nameNode.getScope();
+ Symbol symbol = node == null ? null
+ : node.getSymbol(nameNode.getString());
+ if (symbol == null)
+ return -1;
+ return symbol.index;
+ }
+
+ public final String getParamOrVarName(int index) {
+ if (variableNames == null) throw Kit.codeBug();
+ return variableNames[index];
+ }
+
+ public final int getParamCount() {
+ return paramCount;
+ }
+
+ public final int getParamAndVarCount() {
+ if (variableNames == null) throw Kit.codeBug();
+ return symbols.size();
+ }
+
+ public final String[] getParamAndVarNames() {
+ if (variableNames == null) throw Kit.codeBug();
+ return variableNames;
+ }
+
+ public final boolean[] getParamAndVarConst() {
+ if (variableNames == null) throw Kit.codeBug();
+ return isConsts;
+ }
+
+ void addSymbol(Symbol symbol) {
+ if (variableNames != null) throw Kit.codeBug();
+ if (symbol.declType == Token.LP) {
+ paramCount++;
+ }
+ symbols.add(symbol);
+ }
+
+ /**
+ * Assign every symbol a unique integer index. Generate arrays of variable
+ * names and constness that can be indexed by those indices.
+ *
+ * @param flattenAllTables if true, flatten all symbol tables, included
+ * nested block scope symbol tables. If false, just flatten the script's
+ * or function's symbol table.
+ */
+ void flattenSymbolTable(boolean flattenAllTables) {
+ if (!flattenAllTables) {
+ ArrayList<Symbol> newSymbols = new ArrayList<Symbol>();
+ if (this.symbolTable != null) {
+ // Just replace "symbols" with the symbols in this object's
+ // symbol table. Can't just work from symbolTable map since
+ // we need to retain duplicate parameters.
+ for (int i=0; i < symbols.size(); i++) {
+ Symbol symbol = symbols.get(i);
+ if (symbol.containingTable == this) {
+ newSymbols.add(symbol);
+ }
+ }
+ }
+ symbols = newSymbols;
+ }
+ variableNames = new String[symbols.size()];
+ isConsts = new boolean[symbols.size()];
+ for (int i=0; i < symbols.size(); i++) {
+ Symbol symbol = symbols.get(i);
+ variableNames[i] = symbol.name;
+ isConsts[i] = symbol.declType == Token.CONST;
+ symbol.index = i;
+ }
+ }
+
+ public final Object getCompilerData()
+ {
+ return compilerData;
+ }
+
+ public final void setCompilerData(Object data)
+ {
+ if (data == null) throw new IllegalArgumentException();
+ // Can only call once
+ if (compilerData != null) throw new IllegalStateException();
+ compilerData = data;
+ }
+
+ public String getNextTempName()
+ {
+ return "$" + tempNumber++;
+ }
+
+ private int encodedSourceStart;
+ private int encodedSourceEnd;
+ private String sourceName;
+ private int endLineno = -1;
+
+ private ObjArray functions;
+ private ObjArray regexps;
+
+ private ArrayList<Symbol> symbols;
+ private int paramCount = 0;
+ private String[] variableNames;
+ private boolean[] isConsts;
+
+ private Object compilerData;
+ private int tempNumber = 0;
+}
diff --git a/src/org/mozilla/javascript/ScriptRuntime.java b/src/org/mozilla/javascript/ScriptRuntime.java
new file mode 100644
index 0000000..6bc0f69
--- /dev/null
+++ b/src/org/mozilla/javascript/ScriptRuntime.java
@@ -0,0 +1,3925 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Patrick Beard
+ * Norris Boyd
+ * Igor Bukanov
+ * Mike Harm
+ * Ethan Hugg
+ * Bob Jervis
+ * Roger Lawrence
+ * Terry Lucas
+ * Frank Mitchell
+ * Milen Nankov
+ * Hannes Wallnoefer
+ * Andrew Wason
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.Serializable;
+import java.lang.reflect.*;
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import org.mozilla.javascript.xml.XMLObject;
+import org.mozilla.javascript.xml.XMLLib;
+
+/**
+ * This is the class that implements the runtime.
+ *
+ * @author Norris Boyd
+ */
+
+public class ScriptRuntime {
+
+ /**
+ * No instances should be created.
+ */
+ protected ScriptRuntime() {
+ }
+
+ static class NoSuchMethodShim implements Callable {
+ String methodName;
+ Callable noSuchMethodMethod;
+
+ NoSuchMethodShim(Callable noSuchMethodMethod, String methodName)
+ {
+ this.noSuchMethodMethod = noSuchMethodMethod;
+ this.methodName = methodName;
+ }
+ /**
+ * Perform the call.
+ *
+ * @param cx the current Context for this thread
+ * @param scope the scope to use to resolve properties.
+ * @param thisObj the JavaScript <code>this</code> object
+ * @param args the array of arguments
+ * @return the result of the call
+ */
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ Object[] nestedArgs = new Object[2];
+
+ nestedArgs[0] = methodName;
+ nestedArgs[1] = newArrayLiteral(args, null, cx, scope);
+ return noSuchMethodMethod.call(cx, scope, thisObj, nestedArgs);
+ }
+
+ }
+ /*
+ * There's such a huge space (and some time) waste for the Foo.class
+ * syntax: the compiler sticks in a test of a static field in the
+ * enclosing class for null and the code for creating the class value.
+ * It has to do this since the reference has to get pushed off until
+ * execution time (i.e. can't force an early load), but for the
+ * 'standard' classes - especially those in java.lang, we can trust
+ * that they won't cause problems by being loaded early.
+ */
+
+ public final static Class<?>
+ BooleanClass = Kit.classOrNull("java.lang.Boolean"),
+ ByteClass = Kit.classOrNull("java.lang.Byte"),
+ CharacterClass = Kit.classOrNull("java.lang.Character"),
+ ClassClass = Kit.classOrNull("java.lang.Class"),
+ DoubleClass = Kit.classOrNull("java.lang.Double"),
+ FloatClass = Kit.classOrNull("java.lang.Float"),
+ IntegerClass = Kit.classOrNull("java.lang.Integer"),
+ LongClass = Kit.classOrNull("java.lang.Long"),
+ NumberClass = Kit.classOrNull("java.lang.Number"),
+ ObjectClass = Kit.classOrNull("java.lang.Object"),
+ ShortClass = Kit.classOrNull("java.lang.Short"),
+ StringClass = Kit.classOrNull("java.lang.String"),
+ DateClass = Kit.classOrNull("java.util.Date");
+
+ public final static Class<?>
+ ContextClass
+ = Kit.classOrNull("org.mozilla.javascript.Context"),
+ ContextFactoryClass
+ = Kit.classOrNull("org.mozilla.javascript.ContextFactory"),
+ FunctionClass
+ = Kit.classOrNull("org.mozilla.javascript.Function"),
+ ScriptableObjectClass
+ = Kit.classOrNull("org.mozilla.javascript.ScriptableObject");
+ public static final Class<Scriptable> ScriptableClass =
+ Scriptable.class;
+
+
+ private static final String[] lazilyNames = {
+ "RegExp", "org.mozilla.javascript.regexp.NativeRegExp",
+ "Packages", "org.mozilla.javascript.NativeJavaTopPackage",
+ "java", "org.mozilla.javascript.NativeJavaTopPackage",
+ "javax", "org.mozilla.javascript.NativeJavaTopPackage",
+ "org", "org.mozilla.javascript.NativeJavaTopPackage",
+ "com", "org.mozilla.javascript.NativeJavaTopPackage",
+ "edu", "org.mozilla.javascript.NativeJavaTopPackage",
+ "net", "org.mozilla.javascript.NativeJavaTopPackage",
+ "getClass", "org.mozilla.javascript.NativeJavaTopPackage",
+ "JavaAdapter", "org.mozilla.javascript.JavaAdapter",
+ "JavaImporter", "org.mozilla.javascript.ImporterTopLevel",
+ "Continuation", "org.mozilla.javascript.NativeContinuation",
+ // TODO Grotesque hack using literal string (xml) just to minimize
+ // changes for now
+ "XML", "(xml)",
+ "XMLList", "(xml)",
+ "Namespace", "(xml)",
+ "QName", "(xml)",
+ };
+
+ private static final Object LIBRARY_SCOPE_KEY = "LIBRARY_SCOPE";
+
+ public static boolean isRhinoRuntimeType(Class<?> cl)
+ {
+ if (cl.isPrimitive()) {
+ return (cl != Character.TYPE);
+ } else {
+ return (cl == StringClass || cl == BooleanClass
+ || NumberClass.isAssignableFrom(cl)
+ || ScriptableClass.isAssignableFrom(cl));
+ }
+ }
+
+ public static ScriptableObject initStandardObjects(Context cx,
+ ScriptableObject scope,
+ boolean sealed)
+ {
+ if (scope == null) {
+ scope = new NativeObject();
+ }
+ scope.associateValue(LIBRARY_SCOPE_KEY, scope);
+ (new ClassCache()).associate(scope);
+
+ BaseFunction.init(scope, sealed);
+ NativeObject.init(scope, sealed);
+
+ Scriptable objectProto = ScriptableObject.getObjectPrototype(scope);
+
+ // Function.prototype.__proto__ should be Object.prototype
+ Scriptable functionProto = ScriptableObject.getFunctionPrototype(scope);
+ functionProto.setPrototype(objectProto);
+
+ // Set the prototype of the object passed in if need be
+ if (scope.getPrototype() == null)
+ scope.setPrototype(objectProto);
+
+ // must precede NativeGlobal since it's needed therein
+ NativeError.init(scope, sealed);
+ NativeGlobal.init(cx, scope, sealed);
+
+ NativeArray.init(scope, sealed);
+ if (cx.getOptimizationLevel() > 0) {
+ // When optimizing, attempt to fulfill all requests for new Array(N)
+ // with a higher threshold before switching to a sparse
+ // representation
+ NativeArray.setMaximumInitialCapacity(200000);
+ }
+ NativeString.init(scope, sealed);
+ NativeBoolean.init(scope, sealed);
+ NativeNumber.init(scope, sealed);
+ NativeDate.init(scope, sealed);
+ NativeMath.init(scope, sealed);
+
+ NativeWith.init(scope, sealed);
+ NativeCall.init(scope, sealed);
+ NativeScript.init(scope, sealed);
+
+ NativeIterator.init(scope, sealed); // Also initializes NativeGenerator
+
+ boolean withXml = cx.hasFeature(Context.FEATURE_E4X) &&
+ cx.getE4xImplementationFactory() != null;
+
+ for (int i = 0; i != lazilyNames.length; i += 2) {
+ String topProperty = lazilyNames[i];
+ String className = lazilyNames[i + 1];
+ if (!withXml && className.equals("(xml)")) {
+ continue;
+ } else if (withXml && className.equals("(xml)")) {
+ className = cx.getE4xImplementationFactory().
+ getImplementationClassName();
+ }
+ new LazilyLoadedCtor(scope, topProperty, className, sealed);
+ }
+
+ return scope;
+ }
+
+ public static ScriptableObject getLibraryScopeOrNull(Scriptable scope)
+ {
+ ScriptableObject libScope;
+ libScope = (ScriptableObject)ScriptableObject.
+ getTopScopeValue(scope, LIBRARY_SCOPE_KEY);
+ return libScope;
+ }
+
+ // It is public so NativeRegExp can access it.
+ public static boolean isJSLineTerminator(int c)
+ {
+ // Optimization for faster check for eol character:
+ // they do not have 0xDFD0 bits set
+ if ((c & 0xDFD0) != 0) {
+ return false;
+ }
+ return c == '\n' || c == '\r' || c == 0x2028 || c == 0x2029;
+ }
+
+ public static Boolean wrapBoolean(boolean b)
+ {
+ return b ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ public static Integer wrapInt(int i)
+ {
+ return new Integer(i);
+ }
+
+ public static Number wrapNumber(double x)
+ {
+ if (x != x) {
+ return ScriptRuntime.NaNobj;
+ }
+ return new Double(x);
+ }
+
+ /**
+ * Convert the value to a boolean.
+ *
+ * See ECMA 9.2.
+ */
+ public static boolean toBoolean(Object val)
+ {
+ for (;;) {
+ if (val instanceof Boolean)
+ return ((Boolean) val).booleanValue();
+ if (val == null || val == Undefined.instance)
+ return false;
+ if (val instanceof String)
+ return ((String) val).length() != 0;
+ if (val instanceof Number) {
+ double d = ((Number) val).doubleValue();
+ return (d == d && d != 0.0);
+ }
+ if (val instanceof Scriptable) {
+ if (val instanceof ScriptableObject &&
+ ((ScriptableObject) val).avoidObjectDetection())
+ {
+ return false;
+ }
+ if (Context.getContext().isVersionECMA1()) {
+ // pure ECMA
+ return true;
+ }
+ // ECMA extension
+ val = ((Scriptable) val).getDefaultValue(BooleanClass);
+ if (val instanceof Scriptable)
+ throw errorWithClassName("msg.primitive.expected", val);
+ continue;
+ }
+ warnAboutNonJSObject(val);
+ return true;
+ }
+ }
+
+ /**
+ * Convert the value to a number.
+ *
+ * See ECMA 9.3.
+ */
+ public static double toNumber(Object val)
+ {
+ for (;;) {
+ if (val instanceof Number)
+ return ((Number) val).doubleValue();
+ if (val == null)
+ return +0.0;
+ if (val == Undefined.instance)
+ return NaN;
+ if (val instanceof String)
+ return toNumber((String) val);
+ if (val instanceof Boolean)
+ return ((Boolean) val).booleanValue() ? 1 : +0.0;
+ if (val instanceof Scriptable) {
+ val = ((Scriptable) val).getDefaultValue(NumberClass);
+ if (val instanceof Scriptable)
+ throw errorWithClassName("msg.primitive.expected", val);
+ continue;
+ }
+ warnAboutNonJSObject(val);
+ return NaN;
+ }
+ }
+
+ public static double toNumber(Object[] args, int index) {
+ return (index < args.length) ? toNumber(args[index]) : NaN;
+ }
+
+ // Can not use Double.NaN defined as 0.0d / 0.0 as under the Microsoft VM,
+ // versions 2.01 and 3.0P1, that causes some uses (returns at least) of
+ // Double.NaN to be converted to 1.0.
+ // So we use ScriptRuntime.NaN instead of Double.NaN.
+ public static final double
+ NaN = Double.longBitsToDouble(0x7ff8000000000000L);
+
+ // A similar problem exists for negative zero.
+ public static final double
+ negativeZero = Double.longBitsToDouble(0x8000000000000000L);
+
+ public static final Double NaNobj = new Double(NaN);
+
+ /*
+ * Helper function for toNumber, parseInt, and TokenStream.getToken.
+ */
+ static double stringToNumber(String s, int start, int radix) {
+ char digitMax = '9';
+ char lowerCaseBound = 'a';
+ char upperCaseBound = 'A';
+ int len = s.length();
+ if (radix < 10) {
+ digitMax = (char) ('0' + radix - 1);
+ }
+ if (radix > 10) {
+ lowerCaseBound = (char) ('a' + radix - 10);
+ upperCaseBound = (char) ('A' + radix - 10);
+ }
+ int end;
+ double sum = 0.0;
+ for (end=start; end < len; end++) {
+ char c = s.charAt(end);
+ int newDigit;
+ if ('0' <= c && c <= digitMax)
+ newDigit = c - '0';
+ else if ('a' <= c && c < lowerCaseBound)
+ newDigit = c - 'a' + 10;
+ else if ('A' <= c && c < upperCaseBound)
+ newDigit = c - 'A' + 10;
+ else
+ break;
+ sum = sum*radix + newDigit;
+ }
+ if (start == end) {
+ return NaN;
+ }
+ if (sum >= 9007199254740992.0) {
+ if (radix == 10) {
+ /* If we're accumulating a decimal number and the number
+ * is >= 2^53, then the result from the repeated multiply-add
+ * above may be inaccurate. Call Java to get the correct
+ * answer.
+ */
+ try {
+ return Double.valueOf(s.substring(start, end)).doubleValue();
+ } catch (NumberFormatException nfe) {
+ return NaN;
+ }
+ } else if (radix == 2 || radix == 4 || radix == 8 ||
+ radix == 16 || radix == 32)
+ {
+ /* The number may also be inaccurate for one of these bases.
+ * This happens if the addition in value*radix + digit causes
+ * a round-down to an even least significant mantissa bit
+ * when the first dropped bit is a one. If any of the
+ * following digits in the number (which haven't been added
+ * in yet) are nonzero then the correct action would have
+ * been to round up instead of down. An example of this
+ * occurs when reading the number 0x1000000000000081, which
+ * rounds to 0x1000000000000000 instead of 0x1000000000000100.
+ */
+ int bitShiftInChar = 1;
+ int digit = 0;
+
+ final int SKIP_LEADING_ZEROS = 0;
+ final int FIRST_EXACT_53_BITS = 1;
+ final int AFTER_BIT_53 = 2;
+ final int ZEROS_AFTER_54 = 3;
+ final int MIXED_AFTER_54 = 4;
+
+ int state = SKIP_LEADING_ZEROS;
+ int exactBitsLimit = 53;
+ double factor = 0.0;
+ boolean bit53 = false;
+ // bit54 is the 54th bit (the first dropped from the mantissa)
+ boolean bit54 = false;
+
+ for (;;) {
+ if (bitShiftInChar == 1) {
+ if (start == end)
+ break;
+ digit = s.charAt(start++);
+ if ('0' <= digit && digit <= '9')
+ digit -= '0';
+ else if ('a' <= digit && digit <= 'z')
+ digit -= 'a' - 10;
+ else
+ digit -= 'A' - 10;
+ bitShiftInChar = radix;
+ }
+ bitShiftInChar >>= 1;
+ boolean bit = (digit & bitShiftInChar) != 0;
+
+ switch (state) {
+ case SKIP_LEADING_ZEROS:
+ if (bit) {
+ --exactBitsLimit;
+ sum = 1.0;
+ state = FIRST_EXACT_53_BITS;
+ }
+ break;
+ case FIRST_EXACT_53_BITS:
+ sum *= 2.0;
+ if (bit)
+ sum += 1.0;
+ --exactBitsLimit;
+ if (exactBitsLimit == 0) {
+ bit53 = bit;
+ state = AFTER_BIT_53;
+ }
+ break;
+ case AFTER_BIT_53:
+ bit54 = bit;
+ factor = 2.0;
+ state = ZEROS_AFTER_54;
+ break;
+ case ZEROS_AFTER_54:
+ if (bit) {
+ state = MIXED_AFTER_54;
+ }
+ // fallthrough
+ case MIXED_AFTER_54:
+ factor *= 2;
+ break;
+ }
+ }
+ switch (state) {
+ case SKIP_LEADING_ZEROS:
+ sum = 0.0;
+ break;
+ case FIRST_EXACT_53_BITS:
+ case AFTER_BIT_53:
+ // do nothing
+ break;
+ case ZEROS_AFTER_54:
+ // x1.1 -> x1 + 1 (round up)
+ // x0.1 -> x0 (round down)
+ if (bit54 & bit53)
+ sum += 1.0;
+ sum *= factor;
+ break;
+ case MIXED_AFTER_54:
+ // x.100...1.. -> x + 1 (round up)
+ // x.0anything -> x (round down)
+ if (bit54)
+ sum += 1.0;
+ sum *= factor;
+ break;
+ }
+ }
+ /* We don't worry about inaccurate numbers for any other base. */
+ }
+ return sum;
+ }
+
+
+ /**
+ * ToNumber applied to the String type
+ *
+ * See ECMA 9.3.1
+ */
+ public static double toNumber(String s) {
+ int len = s.length();
+ int start = 0;
+ char startChar;
+ for (;;) {
+ if (start == len) {
+ // Empty or contains only whitespace
+ return +0.0;
+ }
+ startChar = s.charAt(start);
+ if (!Character.isWhitespace(startChar))
+ break;
+ start++;
+ }
+
+ if (startChar == '0') {
+ if (start + 2 < len) {
+ int c1 = s.charAt(start + 1);
+ if (c1 == 'x' || c1 == 'X') {
+ // A hexadecimal number
+ return stringToNumber(s, start + 2, 16);
+ }
+ }
+ } else if (startChar == '+' || startChar == '-') {
+ if (start + 3 < len && s.charAt(start + 1) == '0') {
+ int c2 = s.charAt(start + 2);
+ if (c2 == 'x' || c2 == 'X') {
+ // A hexadecimal number with sign
+ double val = stringToNumber(s, start + 3, 16);
+ return startChar == '-' ? -val : val;
+ }
+ }
+ }
+
+ int end = len - 1;
+ char endChar;
+ while (Character.isWhitespace(endChar = s.charAt(end)))
+ end--;
+ if (endChar == 'y') {
+ // check for "Infinity"
+ if (startChar == '+' || startChar == '-')
+ start++;
+ if (start + 7 == end && s.regionMatches(start, "Infinity", 0, 8))
+ return startChar == '-'
+ ? Double.NEGATIVE_INFINITY
+ : Double.POSITIVE_INFINITY;
+ return NaN;
+ }
+ // A non-hexadecimal, non-infinity number:
+ // just try a normal floating point conversion
+ String sub = s.substring(start, end+1);
+ if (MSJVM_BUG_WORKAROUNDS) {
+ // The MS JVM will accept non-conformant strings
+ // rather than throwing a NumberFormatException
+ // as it should.
+ for (int i=sub.length()-1; i >= 0; i--) {
+ char c = sub.charAt(i);
+ if (('0' <= c && c <= '9') || c == '.' ||
+ c == 'e' || c == 'E' ||
+ c == '+' || c == '-')
+ continue;
+ return NaN;
+ }
+ }
+ try {
+ return Double.valueOf(sub).doubleValue();
+ } catch (NumberFormatException ex) {
+ return NaN;
+ }
+ }
+
+ /**
+ * Helper function for builtin objects that use the varargs form.
+ * ECMA function formal arguments are undefined if not supplied;
+ * this function pads the argument array out to the expected
+ * length, if necessary.
+ */
+ public static Object[] padArguments(Object[] args, int count) {
+ if (count < args.length)
+ return args;
+
+ int i;
+ Object[] result = new Object[count];
+ for (i = 0; i < args.length; i++) {
+ result[i] = args[i];
+ }
+
+ for (; i < count; i++) {
+ result[i] = Undefined.instance;
+ }
+
+ return result;
+ }
+
+ /* Work around Microsoft Java VM bugs. */
+ private final static boolean MSJVM_BUG_WORKAROUNDS = true;
+
+ public static String escapeString(String s)
+ {
+ return escapeString(s, '"');
+ }
+
+ /**
+ * For escaping strings printed by object and array literals; not quite
+ * the same as 'escape.'
+ */
+ public static String escapeString(String s, char escapeQuote)
+ {
+ if (!(escapeQuote == '"' || escapeQuote == '\'')) Kit.codeBug();
+ StringBuffer sb = null;
+
+ for(int i = 0, L = s.length(); i != L; ++i) {
+ int c = s.charAt(i);
+
+ if (' ' <= c && c <= '~' && c != escapeQuote && c != '\\') {
+ // an ordinary print character (like C isprint()) and not "
+ // or \ .
+ if (sb != null) {
+ sb.append((char)c);
+ }
+ continue;
+ }
+ if (sb == null) {
+ sb = new StringBuffer(L + 3);
+ sb.append(s);
+ sb.setLength(i);
+ }
+
+ int escape = -1;
+ switch (c) {
+ case '\b': escape = 'b'; break;
+ case '\f': escape = 'f'; break;
+ case '\n': escape = 'n'; break;
+ case '\r': escape = 'r'; break;
+ case '\t': escape = 't'; break;
+ case 0xb: escape = 'v'; break; // Java lacks \v.
+ case ' ': escape = ' '; break;
+ case '\\': escape = '\\'; break;
+ }
+ if (escape >= 0) {
+ // an \escaped sort of character
+ sb.append('\\');
+ sb.append((char)escape);
+ } else if (c == escapeQuote) {
+ sb.append('\\');
+ sb.append(escapeQuote);
+ } else {
+ int hexSize;
+ if (c < 256) {
+ // 2-digit hex
+ sb.append("\\x");
+ hexSize = 2;
+ } else {
+ // Unicode.
+ sb.append("\\u");
+ hexSize = 4;
+ }
+ // append hexadecimal form of c left-padded with 0
+ for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4) {
+ int digit = 0xf & (c >> shift);
+ int hc = (digit < 10) ? '0' + digit : 'a' - 10 + digit;
+ sb.append((char)hc);
+ }
+ }
+ }
+ return (sb == null) ? s : sb.toString();
+ }
+
+ static boolean isValidIdentifierName(String s)
+ {
+ int L = s.length();
+ if (L == 0)
+ return false;
+ if (!Character.isJavaIdentifierStart(s.charAt(0)))
+ return false;
+ for (int i = 1; i != L; ++i) {
+ if (!Character.isJavaIdentifierPart(s.charAt(i)))
+ return false;
+ }
+ return !TokenStream.isKeyword(s);
+ }
+
+ /**
+ * Convert the value to a string.
+ *
+ * See ECMA 9.8.
+ */
+ public static String toString(Object val) {
+ for (;;) {
+ if (val == null) {
+ return "null";
+ }
+ if (val == Undefined.instance) {
+ return "undefined";
+ }
+ if (val instanceof String) {
+ return (String)val;
+ }
+ if (val instanceof Number) {
+ // XXX should we just teach NativeNumber.stringValue()
+ // about Numbers?
+ return numberToString(((Number)val).doubleValue(), 10);
+ }
+ if (val instanceof Scriptable) {
+ val = ((Scriptable) val).getDefaultValue(StringClass);
+ if (val instanceof Scriptable) {
+ throw errorWithClassName("msg.primitive.expected", val);
+ }
+ continue;
+ }
+ return val.toString();
+ }
+ }
+
+ static String defaultObjectToString(Scriptable obj)
+ {
+ return "[object " + obj.getClassName() + ']';
+ }
+
+ public static String toString(Object[] args, int index)
+ {
+ return (index < args.length) ? toString(args[index]) : "undefined";
+ }
+
+ /**
+ * Optimized version of toString(Object) for numbers.
+ */
+ public static String toString(double val) {
+ return numberToString(val, 10);
+ }
+
+ public static String numberToString(double d, int base) {
+ if (d != d)
+ return "NaN";
+ if (d == Double.POSITIVE_INFINITY)
+ return "Infinity";
+ if (d == Double.NEGATIVE_INFINITY)
+ return "-Infinity";
+ if (d == 0.0)
+ return "0";
+
+ if ((base < 2) || (base > 36)) {
+ throw Context.reportRuntimeError1(
+ "msg.bad.radix", Integer.toString(base));
+ }
+
+ if (base != 10) {
+ return DToA.JS_dtobasestr(base, d);
+ } else {
+ StringBuffer result = new StringBuffer();
+ DToA.JS_dtostr(result, DToA.DTOSTR_STANDARD, 0, d);
+ return result.toString();
+ }
+
+ }
+
+ static String uneval(Context cx, Scriptable scope, Object value)
+ {
+ if (value == null) {
+ return "null";
+ }
+ if (value == Undefined.instance) {
+ return "undefined";
+ }
+ if (value instanceof String) {
+ String escaped = escapeString((String)value);
+ StringBuffer sb = new StringBuffer(escaped.length() + 2);
+ sb.append('\"');
+ sb.append(escaped);
+ sb.append('\"');
+ return sb.toString();
+ }
+ if (value instanceof Number) {
+ double d = ((Number)value).doubleValue();
+ if (d == 0 && 1 / d < 0) {
+ return "-0";
+ }
+ return toString(d);
+ }
+ if (value instanceof Boolean) {
+ return toString(value);
+ }
+ if (value instanceof Scriptable) {
+ Scriptable obj = (Scriptable)value;
+ // Wrapped Java objects won't have "toSource" and will report
+ // errors for get()s of nonexistent name, so use has() first
+ if (ScriptableObject.hasProperty(obj, "toSource")) {
+ Object v = ScriptableObject.getProperty(obj, "toSource");
+ if (v instanceof Function) {
+ Function f = (Function)v;
+ return toString(f.call(cx, scope, obj, emptyArgs));
+ }
+ }
+ return toString(value);
+ }
+ warnAboutNonJSObject(value);
+ return value.toString();
+ }
+
+ static String defaultObjectToSource(Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ boolean toplevel, iterating;
+ if (cx.iterating == null) {
+ toplevel = true;
+ iterating = false;
+ cx.iterating = new ObjToIntMap(31);
+ } else {
+ toplevel = false;
+ iterating = cx.iterating.has(thisObj);
+ }
+
+ StringBuffer result = new StringBuffer(128);
+ if (toplevel) {
+ result.append("(");
+ }
+ result.append('{');
+
+ // Make sure cx.iterating is set to null when done
+ // so we don't leak memory
+ try {
+ if (!iterating) {
+ cx.iterating.intern(thisObj); // stop recursion.
+ Object[] ids = thisObj.getIds();
+ for (int i=0; i < ids.length; i++) {
+ Object id = ids[i];
+ Object value;
+ if (id instanceof Integer) {
+ int intId = ((Integer)id).intValue();
+ value = thisObj.get(intId, thisObj);
+ if (value == Scriptable.NOT_FOUND)
+ continue; // a property has been removed
+ if (i > 0)
+ result.append(", ");
+ result.append(intId);
+ } else {
+ String strId = (String)id;
+ value = thisObj.get(strId, thisObj);
+ if (value == Scriptable.NOT_FOUND)
+ continue; // a property has been removed
+ if (i > 0)
+ result.append(", ");
+ if (ScriptRuntime.isValidIdentifierName(strId)) {
+ result.append(strId);
+ } else {
+ result.append('\'');
+ result.append(
+ ScriptRuntime.escapeString(strId, '\''));
+ result.append('\'');
+ }
+ }
+ result.append(':');
+ result.append(ScriptRuntime.uneval(cx, scope, value));
+ }
+ }
+ } finally {
+ if (toplevel) {
+ cx.iterating = null;
+ }
+ }
+
+ result.append('}');
+ if (toplevel) {
+ result.append(')');
+ }
+ return result.toString();
+ }
+
+ public static Scriptable toObject(Scriptable scope, Object val)
+ {
+ if (val instanceof Scriptable) {
+ return (Scriptable)val;
+ }
+ return toObject(Context.getContext(), scope, val);
+ }
+
+ /**
+ * Warning: this doesn't allow to resolve primitive prototype properly when many top scopes are involved
+ */
+ public static Scriptable toObjectOrNull(Context cx, Object obj)
+ {
+ if (obj instanceof Scriptable) {
+ return (Scriptable)obj;
+ } else if (obj != null && obj != Undefined.instance) {
+ return toObject(cx, getTopCallScope(cx), obj);
+ }
+ return null;
+ }
+
+ /**
+ * @param scope the scope that should be used to resolve primitive prototype
+ */
+ public static Scriptable toObjectOrNull(Context cx, Object obj,
+ final Scriptable scope)
+ {
+ if (obj instanceof Scriptable) {
+ return (Scriptable)obj;
+ } else if (obj != null && obj != Undefined.instance) {
+ return toObject(cx, scope, obj);
+ }
+ return null;
+ }
+
+ /**
+ * @deprecated Use {@link #toObject(Scriptable, Object)} instead.
+ */
+ public static Scriptable toObject(Scriptable scope, Object val,
+ Class<?> staticClass)
+ {
+ if (val instanceof Scriptable) {
+ return (Scriptable)val;
+ }
+ return toObject(Context.getContext(), scope, val);
+ }
+
+ /**
+ * Convert the value to an object.
+ *
+ * See ECMA 9.9.
+ */
+ public static Scriptable toObject(Context cx, Scriptable scope, Object val)
+ {
+ if (val instanceof Scriptable) {
+ return (Scriptable) val;
+ }
+ if (val == null) {
+ throw typeError0("msg.null.to.object");
+ }
+ if (val == Undefined.instance) {
+ throw typeError0("msg.undef.to.object");
+ }
+ String className = val instanceof String ? "String" :
+ val instanceof Number ? "Number" :
+ val instanceof Boolean ? "Boolean" :
+ null;
+ if (className != null) {
+ Object[] args = { val };
+ scope = ScriptableObject.getTopLevelScope(scope);
+ return newObject(cx, scope, className, args);
+ }
+
+ // Extension: Wrap as a LiveConnect object.
+ Object wrapped = cx.getWrapFactory().wrap(cx, scope, val, null);
+ if (wrapped instanceof Scriptable)
+ return (Scriptable) wrapped;
+ throw errorWithClassName("msg.invalid.type", val);
+ }
+
+ /**
+ * @deprecated Use {@link #toObject(Context, Scriptable, Object)} instead.
+ */
+ public static Scriptable toObject(Context cx, Scriptable scope, Object val,
+ Class<?> staticClass)
+ {
+ return toObject(cx, scope, val);
+ }
+
+ /**
+ * @deprecated The method is only present for compatibility.
+ */
+ public static Object call(Context cx, Object fun, Object thisArg,
+ Object[] args, Scriptable scope)
+ {
+ if (!(fun instanceof Function)) {
+ throw notFunctionError(toString(fun));
+ }
+ Function function = (Function)fun;
+ Scriptable thisObj = toObjectOrNull(cx, thisArg);
+ if (thisObj == null) {
+ throw undefCallError(thisObj, "function");
+ }
+ return function.call(cx, scope, thisObj, args);
+ }
+
+ public static Scriptable newObject(Context cx, Scriptable scope,
+ String constructorName, Object[] args)
+ {
+ scope = ScriptableObject.getTopLevelScope(scope);
+ Function ctor = getExistingCtor(cx, scope, constructorName);
+ if (args == null) { args = ScriptRuntime.emptyArgs; }
+ return ctor.construct(cx, scope, args);
+ }
+
+ /**
+ *
+ * See ECMA 9.4.
+ */
+ public static double toInteger(Object val) {
+ return toInteger(toNumber(val));
+ }
+
+ // convenience method
+ public static double toInteger(double d) {
+ // if it's NaN
+ if (d != d)
+ return +0.0;
+
+ if (d == 0.0 ||
+ d == Double.POSITIVE_INFINITY ||
+ d == Double.NEGATIVE_INFINITY)
+ return d;
+
+ if (d > 0.0)
+ return Math.floor(d);
+ else
+ return Math.ceil(d);
+ }
+
+ public static double toInteger(Object[] args, int index) {
+ return (index < args.length) ? toInteger(args[index]) : +0.0;
+ }
+
+ /**
+ *
+ * See ECMA 9.5.
+ */
+ public static int toInt32(Object val)
+ {
+ // short circuit for common integer values
+ if (val instanceof Integer)
+ return ((Integer)val).intValue();
+
+ return toInt32(toNumber(val));
+ }
+
+ public static int toInt32(Object[] args, int index) {
+ return (index < args.length) ? toInt32(args[index]) : 0;
+ }
+
+ public static int toInt32(double d) {
+ int id = (int)d;
+ if (id == d) {
+ // This covers -0.0 as well
+ return id;
+ }
+
+ if (d != d
+ || d == Double.POSITIVE_INFINITY
+ || d == Double.NEGATIVE_INFINITY)
+ {
+ return 0;
+ }
+
+ d = (d >= 0) ? Math.floor(d) : Math.ceil(d);
+
+ double two32 = 4294967296.0;
+ d = Math.IEEEremainder(d, two32);
+ // (double)(long)d == d should hold here
+
+ long l = (long)d;
+ // returning (int)d does not work as d can be outside int range
+ // but the result must always be 32 lower bits of l
+ return (int)l;
+ }
+
+ /**
+ * See ECMA 9.6.
+ * @return long value representing 32 bits unsigned integer
+ */
+ public static long toUint32(double d) {
+ long l = (long)d;
+ if (l == d) {
+ // This covers -0.0 as well
+ return l & 0xffffffffL;
+ }
+
+ if (d != d
+ || d == Double.POSITIVE_INFINITY
+ || d == Double.NEGATIVE_INFINITY)
+ {
+ return 0;
+ }
+
+ d = (d >= 0) ? Math.floor(d) : Math.ceil(d);
+
+ // 0x100000000 gives me a numeric overflow...
+ double two32 = 4294967296.0;
+ l = (long)Math.IEEEremainder(d, two32);
+
+ return l & 0xffffffffL;
+ }
+
+ public static long toUint32(Object val) {
+ return toUint32(toNumber(val));
+ }
+
+ /**
+ *
+ * See ECMA 9.7.
+ */
+ public static char toUint16(Object val) {
+ double d = toNumber(val);
+
+ int i = (int)d;
+ if (i == d) {
+ return (char)i;
+ }
+
+ if (d != d
+ || d == Double.POSITIVE_INFINITY
+ || d == Double.NEGATIVE_INFINITY)
+ {
+ return 0;
+ }
+
+ d = (d >= 0) ? Math.floor(d) : Math.ceil(d);
+
+ int int16 = 0x10000;
+ i = (int)Math.IEEEremainder(d, int16);
+
+ return (char)i;
+ }
+
+ // XXX: this is until setDefaultNamespace will learn how to store NS
+ // properly and separates namespace form Scriptable.get etc.
+ private static final String DEFAULT_NS_TAG = "__default_namespace__";
+
+ public static Object setDefaultNamespace(Object namespace, Context cx)
+ {
+ Scriptable scope = cx.currentActivationCall;
+ if (scope == null) {
+ scope = getTopCallScope(cx);
+ }
+
+ XMLLib xmlLib = currentXMLLib(cx);
+ Object ns = xmlLib.toDefaultXmlNamespace(cx, namespace);
+
+ // XXX : this should be in separated namesapce from Scriptable.get/put
+ if (!scope.has(DEFAULT_NS_TAG, scope)) {
+ // XXX: this is racy of cause
+ ScriptableObject.defineProperty(scope, DEFAULT_NS_TAG, ns,
+ ScriptableObject.PERMANENT
+ | ScriptableObject.DONTENUM);
+ } else {
+ scope.put(DEFAULT_NS_TAG, scope, ns);
+ }
+
+ return Undefined.instance;
+ }
+
+ public static Object searchDefaultNamespace(Context cx)
+ {
+ Scriptable scope = cx.currentActivationCall;
+ if (scope == null) {
+ scope = getTopCallScope(cx);
+ }
+ Object nsObject;
+ for (;;) {
+ Scriptable parent = scope.getParentScope();
+ if (parent == null) {
+ nsObject = ScriptableObject.getProperty(scope, DEFAULT_NS_TAG);
+ if (nsObject == Scriptable.NOT_FOUND) {
+ return null;
+ }
+ break;
+ }
+ nsObject = scope.get(DEFAULT_NS_TAG, scope);
+ if (nsObject != Scriptable.NOT_FOUND) {
+ break;
+ }
+ scope = parent;
+ }
+ return nsObject;
+ }
+
+ public static Object getTopLevelProp(Scriptable scope, String id) {
+ scope = ScriptableObject.getTopLevelScope(scope);
+ return ScriptableObject.getProperty(scope, id);
+ }
+
+ static Function getExistingCtor(Context cx, Scriptable scope,
+ String constructorName)
+ {
+ Object ctorVal = ScriptableObject.getProperty(scope, constructorName);
+ if (ctorVal instanceof Function) {
+ return (Function)ctorVal;
+ }
+ if (ctorVal == Scriptable.NOT_FOUND) {
+ throw Context.reportRuntimeError1(
+ "msg.ctor.not.found", constructorName);
+ } else {
+ throw Context.reportRuntimeError1(
+ "msg.not.ctor", constructorName);
+ }
+ }
+
+ /**
+ * Return -1L if str is not an index or the index value as lower 32
+ * bits of the result.
+ */
+ private static long indexFromString(String str)
+ {
+ // The length of the decimal string representation of
+ // Integer.MAX_VALUE, 2147483647
+ final int MAX_VALUE_LENGTH = 10;
+
+ int len = str.length();
+ if (len > 0) {
+ int i = 0;
+ boolean negate = false;
+ int c = str.charAt(0);
+ if (c == '-') {
+ if (len > 1) {
+ c = str.charAt(1);
+ i = 1;
+ negate = true;
+ }
+ }
+ c -= '0';
+ if (0 <= c && c <= 9
+ && len <= (negate ? MAX_VALUE_LENGTH + 1 : MAX_VALUE_LENGTH))
+ {
+ // Use negative numbers to accumulate index to handle
+ // Integer.MIN_VALUE that is greater by 1 in absolute value
+ // then Integer.MAX_VALUE
+ int index = -c;
+ int oldIndex = 0;
+ i++;
+ if (index != 0) {
+ // Note that 00, 01, 000 etc. are not indexes
+ while (i != len && 0 <= (c = str.charAt(i) - '0') && c <= 9)
+ {
+ oldIndex = index;
+ index = 10 * index - c;
+ i++;
+ }
+ }
+ // Make sure all characters were consumed and that it couldn't
+ // have overflowed.
+ if (i == len &&
+ (oldIndex > (Integer.MIN_VALUE / 10) ||
+ (oldIndex == (Integer.MIN_VALUE / 10) &&
+ c <= (negate ? -(Integer.MIN_VALUE % 10)
+ : (Integer.MAX_VALUE % 10)))))
+ {
+ return 0xFFFFFFFFL & (negate ? index : -index);
+ }
+ }
+ }
+ return -1L;
+ }
+
+ /**
+ * If str is a decimal presentation of Uint32 value, return it as long.
+ * Othewise return -1L;
+ */
+ public static long testUint32String(String str)
+ {
+ // The length of the decimal string representation of
+ // UINT32_MAX_VALUE, 4294967296
+ final int MAX_VALUE_LENGTH = 10;
+
+ int len = str.length();
+ if (1 <= len && len <= MAX_VALUE_LENGTH) {
+ int c = str.charAt(0);
+ c -= '0';
+ if (c == 0) {
+ // Note that 00,01 etc. are not valid Uint32 presentations
+ return (len == 1) ? 0L : -1L;
+ }
+ if (1 <= c && c <= 9) {
+ long v = c;
+ for (int i = 1; i != len; ++i) {
+ c = str.charAt(i) - '0';
+ if (!(0 <= c && c <= 9)) {
+ return -1;
+ }
+ v = 10 * v + c;
+ }
+ // Check for overflow
+ if ((v >>> 32) == 0) {
+ return v;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * If s represents index, then return index value wrapped as Integer
+ * and othewise return s.
+ */
+ static Object getIndexObject(String s)
+ {
+ long indexTest = indexFromString(s);
+ if (indexTest >= 0) {
+ return new Integer((int)indexTest);
+ }
+ return s;
+ }
+
+ /**
+ * If d is exact int value, return its value wrapped as Integer
+ * and othewise return d converted to String.
+ */
+ static Object getIndexObject(double d)
+ {
+ int i = (int)d;
+ if (i == d) {
+ return new Integer(i);
+ }
+ return toString(d);
+ }
+
+ /**
+ * If toString(id) is a decimal presentation of int32 value, then id
+ * is index. In this case return null and make the index available
+ * as ScriptRuntime.lastIndexResult(cx). Otherwise return toString(id).
+ */
+ static String toStringIdOrIndex(Context cx, Object id)
+ {
+ if (id instanceof Number) {
+ double d = ((Number)id).doubleValue();
+ int index = (int)d;
+ if (index == d) {
+ storeIndexResult(cx, index);
+ return null;
+ }
+ return toString(id);
+ } else {
+ String s;
+ if (id instanceof String) {
+ s = (String)id;
+ } else {
+ s = toString(id);
+ }
+ long indexTest = indexFromString(s);
+ if (indexTest >= 0) {
+ storeIndexResult(cx, (int)indexTest);
+ return null;
+ }
+ return s;
+ }
+ }
+
+ /**
+ * Call obj.[[Get]](id)
+ */
+ public static Object getObjectElem(Object obj, Object elem, Context cx)
+ {
+ return getObjectElem(obj, elem, cx, getTopCallScope(cx));
+ }
+
+ /**
+ * Call obj.[[Get]](id)
+ */
+ public static Object getObjectElem(Object obj, Object elem, Context cx, final Scriptable scope)
+ {
+ Scriptable sobj = toObjectOrNull(cx, obj, scope);
+ if (sobj == null) {
+ throw undefReadError(obj, elem);
+ }
+ return getObjectElem(sobj, elem, cx);
+ }
+
+ public static Object getObjectElem(Scriptable obj, Object elem,
+ Context cx)
+ {
+ if (obj instanceof XMLObject) {
+ XMLObject xmlObject = (XMLObject)obj;
+ return xmlObject.ecmaGet(cx, elem);
+ }
+
+ Object result;
+
+ String s = toStringIdOrIndex(cx, elem);
+ if (s == null) {
+ int index = lastIndexResult(cx);
+ result = ScriptableObject.getProperty(obj, index);
+ } else {
+ result = ScriptableObject.getProperty(obj, s);
+ }
+
+ if (result == Scriptable.NOT_FOUND) {
+ result = Undefined.instance;
+ }
+
+ return result;
+ }
+
+ /**
+ * Version of getObjectElem when elem is a valid JS identifier name.
+ */
+ public static Object getObjectProp(Object obj, String property,
+ Context cx)
+ {
+ Scriptable sobj = toObjectOrNull(cx, obj);
+ if (sobj == null) {
+ throw undefReadError(obj, property);
+ }
+ return getObjectProp(sobj, property, cx);
+ }
+
+ /**
+ * @param scope the scope that should be used to resolve primitive prototype
+ */
+ public static Object getObjectProp(Object obj, String property,
+ Context cx, final Scriptable scope)
+ {
+ Scriptable sobj = toObjectOrNull(cx, obj, scope);
+ if (sobj == null) {
+ throw undefReadError(obj, property);
+ }
+ return getObjectProp(sobj, property, cx);
+ }
+
+ public static Object getObjectProp(Scriptable obj, String property,
+ Context cx)
+ {
+ if (obj instanceof XMLObject) {
+ // TODO: Change XMLObject to just use Scriptable interface
+ // to avoid paying cost of instanceof check on *every property
+ // lookup* !
+ XMLObject xmlObject = (XMLObject)obj;
+ return xmlObject.ecmaGet(cx, property);
+ }
+
+ Object result = ScriptableObject.getProperty(obj, property);
+ if (result == Scriptable.NOT_FOUND) {
+ if (cx.hasFeature(Context.FEATURE_STRICT_MODE)) {
+ Context.reportWarning(ScriptRuntime.getMessage1(
+ "msg.ref.undefined.prop", property));
+ }
+ result = Undefined.instance;
+ }
+
+ return result;
+ }
+
+ public static Object getObjectPropNoWarn(Object obj, String property,
+ Context cx)
+ {
+ Scriptable sobj = toObjectOrNull(cx, obj);
+ if (sobj == null) {
+ throw undefReadError(obj, property);
+ }
+ if (obj instanceof XMLObject) {
+ // TODO: fix as mentioned in note in method above
+ getObjectProp(sobj, property, cx);
+ }
+ Object result = ScriptableObject.getProperty(sobj, property);
+ if (result == Scriptable.NOT_FOUND) {
+ return Undefined.instance;
+ }
+ return result;
+ }
+
+ /*
+ * A cheaper and less general version of the above for well-known argument
+ * types.
+ */
+ public static Object getObjectIndex(Object obj, double dblIndex,
+ Context cx)
+ {
+ Scriptable sobj = toObjectOrNull(cx, obj);
+ if (sobj == null) {
+ throw undefReadError(obj, toString(dblIndex));
+ }
+
+ int index = (int)dblIndex;
+ if (index == dblIndex) {
+ return getObjectIndex(sobj, index, cx);
+ } else {
+ String s = toString(dblIndex);
+ return getObjectProp(sobj, s, cx);
+ }
+ }
+
+ public static Object getObjectIndex(Scriptable obj, int index,
+ Context cx)
+ {
+ if (obj instanceof XMLObject) {
+ XMLObject xmlObject = (XMLObject)obj;
+ return xmlObject.ecmaGet(cx, new Integer(index));
+ }
+
+ Object result = ScriptableObject.getProperty(obj, index);
+ if (result == Scriptable.NOT_FOUND) {
+ result = Undefined.instance;
+ }
+
+ return result;
+ }
+
+ /*
+ * Call obj.[[Put]](id, value)
+ */
+ public static Object setObjectElem(Object obj, Object elem, Object value,
+ Context cx)
+ {
+ Scriptable sobj = toObjectOrNull(cx, obj);
+ if (sobj == null) {
+ throw undefWriteError(obj, elem, value);
+ }
+ return setObjectElem(sobj, elem, value, cx);
+ }
+
+ public static Object setObjectElem(Scriptable obj, Object elem,
+ Object value, Context cx)
+ {
+ if (obj instanceof XMLObject) {
+ XMLObject xmlObject = (XMLObject)obj;
+ xmlObject.ecmaPut(cx, elem, value);
+ return value;
+ }
+
+ String s = toStringIdOrIndex(cx, elem);
+ if (s == null) {
+ int index = lastIndexResult(cx);
+ ScriptableObject.putProperty(obj, index, value);
+ } else {
+ ScriptableObject.putProperty(obj, s, value);
+ }
+
+ return value;
+ }
+
+ /**
+ * Version of setObjectElem when elem is a valid JS identifier name.
+ */
+ public static Object setObjectProp(Object obj, String property,
+ Object value, Context cx)
+ {
+ Scriptable sobj = toObjectOrNull(cx, obj);
+ if (sobj == null) {
+ throw undefWriteError(obj, property, value);
+ }
+ return setObjectProp(sobj, property, value, cx);
+ }
+
+ public static Object setObjectProp(Scriptable obj, String property,
+ Object value, Context cx)
+ {
+ if (obj instanceof XMLObject) {
+ XMLObject xmlObject = (XMLObject)obj;
+ xmlObject.ecmaPut(cx, property, value);
+ } else {
+ ScriptableObject.putProperty(obj, property, value);
+ }
+ return value;
+ }
+
+ /*
+ * A cheaper and less general version of the above for well-known argument
+ * types.
+ */
+ public static Object setObjectIndex(Object obj, double dblIndex,
+ Object value, Context cx)
+ {
+ Scriptable sobj = toObjectOrNull(cx, obj);
+ if (sobj == null) {
+ throw undefWriteError(obj, String.valueOf(dblIndex), value);
+ }
+
+ int index = (int)dblIndex;
+ if (index == dblIndex) {
+ return setObjectIndex(sobj, index, value, cx);
+ } else {
+ String s = toString(dblIndex);
+ return setObjectProp(sobj, s, value, cx);
+ }
+ }
+
+ public static Object setObjectIndex(Scriptable obj, int index, Object value,
+ Context cx)
+ {
+ if (obj instanceof XMLObject) {
+ XMLObject xmlObject = (XMLObject)obj;
+ xmlObject.ecmaPut(cx, new Integer(index), value);
+ } else {
+ ScriptableObject.putProperty(obj, index, value);
+ }
+ return value;
+ }
+
+ public static boolean deleteObjectElem(Scriptable target, Object elem,
+ Context cx)
+ {
+ boolean result;
+ if (target instanceof XMLObject) {
+ XMLObject xmlObject = (XMLObject)target;
+ result = xmlObject.ecmaDelete(cx, elem);
+ } else {
+ String s = toStringIdOrIndex(cx, elem);
+ if (s == null) {
+ int index = lastIndexResult(cx);
+ result = ScriptableObject.deleteProperty(target, index);
+ } else {
+ result = ScriptableObject.deleteProperty(target, s);
+ }
+ }
+ return result;
+ }
+
+ public static boolean hasObjectElem(Scriptable target, Object elem,
+ Context cx)
+ {
+ boolean result;
+
+ if (target instanceof XMLObject) {
+ XMLObject xmlObject = (XMLObject)target;
+ result = xmlObject.ecmaHas(cx, elem);
+ } else {
+ String s = toStringIdOrIndex(cx, elem);
+ if (s == null) {
+ int index = lastIndexResult(cx);
+ result = ScriptableObject.hasProperty(target, index);
+ } else {
+ result = ScriptableObject.hasProperty(target, s);
+ }
+ }
+
+ return result;
+ }
+
+ public static Object refGet(Ref ref, Context cx)
+ {
+ return ref.get(cx);
+ }
+
+ public static Object refSet(Ref ref, Object value, Context cx)
+ {
+ return ref.set(cx, value);
+ }
+
+ public static Object refDel(Ref ref, Context cx)
+ {
+ return wrapBoolean(ref.delete(cx));
+ }
+
+ static boolean isSpecialProperty(String s)
+ {
+ return s.equals("__proto__") || s.equals("__parent__");
+ }
+
+ public static Ref specialRef(Object obj, String specialProperty,
+ Context cx)
+ {
+ return SpecialRef.createSpecial(cx, obj, specialProperty);
+ }
+
+ /**
+ * The delete operator
+ *
+ * See ECMA 11.4.1
+ *
+ * In ECMA 0.19, the description of the delete operator (11.4.1)
+ * assumes that the [[Delete]] method returns a value. However,
+ * the definition of the [[Delete]] operator (8.6.2.5) does not
+ * define a return value. Here we assume that the [[Delete]]
+ * method doesn't return a value.
+ */
+ public static Object delete(Object obj, Object id, Context cx)
+ {
+ Scriptable sobj = toObjectOrNull(cx, obj);
+ if (sobj == null) {
+ String idStr = (id == null) ? "null" : id.toString();
+ throw typeError2("msg.undef.prop.delete", toString(obj), idStr);
+ }
+ boolean result = deleteObjectElem(sobj, id, cx);
+ return wrapBoolean(result);
+ }
+
+ /**
+ * Looks up a name in the scope chain and returns its value.
+ */
+ public static Object name(Context cx, Scriptable scope, String name)
+ {
+ Scriptable parent = scope.getParentScope();
+ if (parent == null) {
+ Object result = topScopeName(cx, scope, name);
+ if (result == Scriptable.NOT_FOUND) {
+ throw notFoundError(scope, name);
+ }
+ return result;
+ }
+
+ return nameOrFunction(cx, scope, parent, name, false);
+ }
+
+ private static Object nameOrFunction(Context cx, Scriptable scope,
+ Scriptable parentScope, String name,
+ boolean asFunctionCall)
+ {
+ Object result;
+ Scriptable thisObj = scope; // It is used only if asFunctionCall==true.
+
+ XMLObject firstXMLObject = null;
+ for (;;) {
+ if (scope instanceof NativeWith) {
+ Scriptable withObj = scope.getPrototype();
+ if (withObj instanceof XMLObject) {
+ XMLObject xmlObj = (XMLObject)withObj;
+ if (xmlObj.ecmaHas(cx, name)) {
+ // function this should be the target object of with
+ thisObj = xmlObj;
+ result = xmlObj.ecmaGet(cx, name);
+ break;
+ }
+ if (firstXMLObject == null) {
+ firstXMLObject = xmlObj;
+ }
+ } else {
+ result = ScriptableObject.getProperty(withObj, name);
+ if (result != Scriptable.NOT_FOUND) {
+ // function this should be the target object of with
+ thisObj = withObj;
+ break;
+ }
+ }
+ } else if (scope instanceof NativeCall) {
+ // NativeCall does not prototype chain and Scriptable.get
+ // can be called directly.
+ result = scope.get(name, scope);
+ if (result != Scriptable.NOT_FOUND) {
+ if (asFunctionCall) {
+ // ECMA 262 requires that this for nested funtions
+ // should be top scope
+ thisObj = ScriptableObject.
+ getTopLevelScope(parentScope);
+ }
+ break;
+ }
+ } else {
+ // Can happen if Rhino embedding decided that nested
+ // scopes are useful for what ever reasons.
+ result = ScriptableObject.getProperty(scope, name);
+ if (result != Scriptable.NOT_FOUND) {
+ thisObj = scope;
+ break;
+ }
+ }
+ scope = parentScope;
+ parentScope = parentScope.getParentScope();
+ if (parentScope == null) {
+ result = topScopeName(cx, scope, name);
+ if (result == Scriptable.NOT_FOUND) {
+ if (firstXMLObject == null || asFunctionCall) {
+ throw notFoundError(scope, name);
+ }
+ // The name was not found, but we did find an XML
+ // object in the scope chain and we are looking for name,
+ // not function. The result should be an empty XMLList
+ // in name context.
+ result = firstXMLObject.ecmaGet(cx, name);
+ }
+ // For top scope thisObj for functions is always scope itself.
+ thisObj = scope;
+ break;
+ }
+ }
+
+ if (asFunctionCall) {
+ if (!(result instanceof Callable)) {
+ throw notFunctionError(result, name);
+ }
+ storeScriptable(cx, thisObj);
+ }
+
+ return result;
+ }
+
+ private static Object topScopeName(Context cx, Scriptable scope,
+ String name)
+ {
+ if (cx.useDynamicScope) {
+ scope = checkDynamicScope(cx.topCallScope, scope);
+ }
+ return ScriptableObject.getProperty(scope, name);
+ }
+
+
+ /**
+ * Returns the object in the scope chain that has a given property.
+ *
+ * The order of evaluation of an assignment expression involves
+ * evaluating the lhs to a reference, evaluating the rhs, and then
+ * modifying the reference with the rhs value. This method is used
+ * to 'bind' the given name to an object containing that property
+ * so that the side effects of evaluating the rhs do not affect
+ * which property is modified.
+ * Typically used in conjunction with setName.
+ *
+ * See ECMA 10.1.4
+ */
+ public static Scriptable bind(Context cx, Scriptable scope, String id)
+ {
+ Scriptable firstXMLObject = null;
+ Scriptable parent = scope.getParentScope();
+ childScopesChecks: if (parent != null) {
+ // Check for possibly nested "with" scopes first
+ while (scope instanceof NativeWith) {
+ Scriptable withObj = scope.getPrototype();
+ if (withObj instanceof XMLObject) {
+ XMLObject xmlObject = (XMLObject)withObj;
+ if (xmlObject.ecmaHas(cx, id)) {
+ return xmlObject;
+ }
+ if (firstXMLObject == null) {
+ firstXMLObject = xmlObject;
+ }
+ } else {
+ if (ScriptableObject.hasProperty(withObj, id)) {
+ return withObj;
+ }
+ }
+ scope = parent;
+ parent = parent.getParentScope();
+ if (parent == null) {
+ break childScopesChecks;
+ }
+ }
+ for (;;) {
+ if (ScriptableObject.hasProperty(scope, id)) {
+ return scope;
+ }
+ scope = parent;
+ parent = parent.getParentScope();
+ if (parent == null) {
+ break childScopesChecks;
+ }
+ }
+ }
+ // scope here is top scope
+ if (cx.useDynamicScope) {
+ scope = checkDynamicScope(cx.topCallScope, scope);
+ }
+ if (ScriptableObject.hasProperty(scope, id)) {
+ return scope;
+ }
+ // Nothing was found, but since XML objects always bind
+ // return one if found
+ return firstXMLObject;
+ }
+
+ public static Object setName(Scriptable bound, Object value,
+ Context cx, Scriptable scope, String id)
+ {
+ if (bound != null) {
+ if (bound instanceof XMLObject) {
+ XMLObject xmlObject = (XMLObject)bound;
+ xmlObject.ecmaPut(cx, id, value);
+ } else {
+ ScriptableObject.putProperty(bound, id, value);
+ }
+ } else {
+ // "newname = 7;", where 'newname' has not yet
+ // been defined, creates a new property in the
+ // top scope unless strict mode is specified.
+ if (cx.hasFeature(Context.FEATURE_STRICT_MODE) ||
+ cx.hasFeature(Context.FEATURE_STRICT_VARS))
+ {
+ Context.reportWarning(
+ ScriptRuntime.getMessage1("msg.assn.create.strict", id));
+ }
+ // Find the top scope by walking up the scope chain.
+ bound = ScriptableObject.getTopLevelScope(scope);
+ if (cx.useDynamicScope) {
+ bound = checkDynamicScope(cx.topCallScope, bound);
+ }
+ bound.put(id, bound, value);
+ }
+ return value;
+ }
+
+ public static Object setConst(Scriptable bound, Object value,
+ Context cx, String id)
+ {
+ if (bound instanceof XMLObject) {
+ XMLObject xmlObject = (XMLObject)bound;
+ xmlObject.ecmaPut(cx, id, value);
+ } else {
+ ScriptableObject.putConstProperty(bound, id, value);
+ }
+ return value;
+ }
+
+ /**
+ * This is the enumeration needed by the for..in statement.
+ *
+ * See ECMA 12.6.3.
+ *
+ * IdEnumeration maintains a ObjToIntMap to make sure a given
+ * id is enumerated only once across multiple objects in a
+ * prototype chain.
+ *
+ * XXX - ECMA delete doesn't hide properties in the prototype,
+ * but js/ref does. This means that the js/ref for..in can
+ * avoid maintaining a hash table and instead perform lookups
+ * to see if a given property has already been enumerated.
+ *
+ */
+ private static class IdEnumeration implements Serializable
+ {
+ private static final long serialVersionUID = 1L;
+ Scriptable obj;
+ Object[] ids;
+ int index;
+ ObjToIntMap used;
+ Object currentId;
+ int enumType; /* one of ENUM_INIT_KEYS, ENUM_INIT_VALUES,
+ ENUM_INIT_ARRAY */
+
+ // if true, integer ids will be returned as numbers rather than strings
+ boolean enumNumbers;
+
+ Scriptable iterator;
+ }
+
+ public static Scriptable toIterator(Context cx, Scriptable scope,
+ Scriptable obj, boolean keyOnly)
+ {
+ if (ScriptableObject.hasProperty(obj,
+ NativeIterator.ITERATOR_PROPERTY_NAME))
+ {
+ Object v = ScriptableObject.getProperty(obj,
+ NativeIterator.ITERATOR_PROPERTY_NAME);
+ if (!(v instanceof Callable)) {
+ throw typeError0("msg.invalid.iterator");
+ }
+ Callable f = (Callable) v;
+ Object[] args = new Object[] { keyOnly ? Boolean.TRUE
+ : Boolean.FALSE };
+ v = f.call(cx, scope, obj, args);
+ if (!(v instanceof Scriptable)) {
+ throw typeError0("msg.iterator.primitive");
+ }
+ return (Scriptable) v;
+ }
+ return null;
+ }
+
+ // for backwards compatibility with generated class files
+ public static Object enumInit(Object value, Context cx, boolean enumValues)
+ {
+ return enumInit(value, cx, enumValues ? ENUMERATE_VALUES
+ : ENUMERATE_KEYS);
+ }
+
+ public static final int ENUMERATE_KEYS = 0;
+ public static final int ENUMERATE_VALUES = 1;
+ public static final int ENUMERATE_ARRAY = 2;
+ public static final int ENUMERATE_KEYS_NO_ITERATOR = 3;
+ public static final int ENUMERATE_VALUES_NO_ITERATOR = 4;
+ public static final int ENUMERATE_ARRAY_NO_ITERATOR = 5;
+
+ public static Object enumInit(Object value, Context cx, int enumType)
+ {
+ IdEnumeration x = new IdEnumeration();
+ x.obj = toObjectOrNull(cx, value);
+ if (x.obj == null) {
+ // null or undefined do not cause errors but rather lead to empty
+ // "for in" loop
+ return x;
+ }
+ x.enumType = enumType;
+ x.iterator = null;
+ if (enumType != ENUMERATE_KEYS_NO_ITERATOR &&
+ enumType != ENUMERATE_VALUES_NO_ITERATOR &&
+ enumType != ENUMERATE_ARRAY_NO_ITERATOR)
+ {
+ x.iterator = toIterator(cx, x.obj.getParentScope(), x.obj, true);
+ }
+ if (x.iterator == null) {
+ // enumInit should read all initial ids before returning
+ // or "for (a.i in a)" would wrongly enumerate i in a as well
+ enumChangeObject(x);
+ }
+
+ return x;
+ }
+
+ public static void setEnumNumbers(Object enumObj, boolean enumNumbers) {
+ ((IdEnumeration)enumObj).enumNumbers = enumNumbers;
+ }
+
+ public static Boolean enumNext(Object enumObj)
+ {
+ IdEnumeration x = (IdEnumeration)enumObj;
+ if (x.iterator != null) {
+ Object v = ScriptableObject.getProperty(x.iterator, "next");
+ if (!(v instanceof Callable))
+ return Boolean.FALSE;
+ Callable f = (Callable) v;
+ Context cx = Context.getContext();
+ try {
+ x.currentId = f.call(cx, x.iterator.getParentScope(),
+ x.iterator, emptyArgs);
+ return Boolean.TRUE;
+ } catch (JavaScriptException e) {
+ if (e.getValue() instanceof NativeIterator.StopIteration) {
+ return Boolean.FALSE;
+ }
+ throw e;
+ }
+ }
+ for (;;) {
+ if (x.obj == null) {
+ return Boolean.FALSE;
+ }
+ if (x.index == x.ids.length) {
+ x.obj = x.obj.getPrototype();
+ enumChangeObject(x);
+ continue;
+ }
+ Object id = x.ids[x.index++];
+ if (x.used != null && x.used.has(id)) {
+ continue;
+ }
+ if (id instanceof String) {
+ String strId = (String)id;
+ if (!x.obj.has(strId, x.obj))
+ continue; // must have been deleted
+ x.currentId = strId;
+ } else {
+ int intId = ((Number)id).intValue();
+ if (!x.obj.has(intId, x.obj))
+ continue; // must have been deleted
+ x.currentId = x.enumNumbers ? (Object) (new Integer(intId))
+ : String.valueOf(intId);
+ }
+ return Boolean.TRUE;
+ }
+ }
+
+ public static Object enumId(Object enumObj, Context cx)
+ {
+ IdEnumeration x = (IdEnumeration)enumObj;
+ if (x.iterator != null) {
+ return x.currentId;
+ }
+ switch (x.enumType) {
+ case ENUMERATE_KEYS:
+ case ENUMERATE_KEYS_NO_ITERATOR:
+ return x.currentId;
+ case ENUMERATE_VALUES:
+ case ENUMERATE_VALUES_NO_ITERATOR:
+ return enumValue(enumObj, cx);
+ case ENUMERATE_ARRAY:
+ case ENUMERATE_ARRAY_NO_ITERATOR:
+ Object[] elements = { x.currentId, enumValue(enumObj, cx) };
+ return cx.newArray(x.obj, elements);
+ default:
+ throw Kit.codeBug();
+ }
+ }
+
+ public static Object enumValue(Object enumObj, Context cx) {
+ IdEnumeration x = (IdEnumeration)enumObj;
+
+ Object result;
+
+ String s = toStringIdOrIndex(cx, x.currentId);
+ if (s == null) {
+ int index = lastIndexResult(cx);
+ result = x.obj.get(index, x.obj);
+ } else {
+ result = x.obj.get(s, x.obj);
+ }
+
+ return result;
+ }
+
+ private static void enumChangeObject(IdEnumeration x)
+ {
+ Object[] ids = null;
+ while (x.obj != null) {
+ ids = x.obj.getIds();
+ if (ids.length != 0) {
+ break;
+ }
+ x.obj = x.obj.getPrototype();
+ }
+ if (x.obj != null && x.ids != null) {
+ Object[] previous = x.ids;
+ int L = previous.length;
+ if (x.used == null) {
+ x.used = new ObjToIntMap(L);
+ }
+ for (int i = 0; i != L; ++i) {
+ x.used.intern(previous[i]);
+ }
+ }
+ x.ids = ids;
+ x.index = 0;
+ }
+
+ /**
+ * Prepare for calling name(...): return function corresponding to
+ * name and make current top scope available
+ * as ScriptRuntime.lastStoredScriptable() for consumption as thisObj.
+ * The caller must call ScriptRuntime.lastStoredScriptable() immediately
+ * after calling this method.
+ */
+ public static Callable getNameFunctionAndThis(String name,
+ Context cx,
+ Scriptable scope)
+ {
+ Scriptable parent = scope.getParentScope();
+ if (parent == null) {
+ Object result = topScopeName(cx, scope, name);
+ if (!(result instanceof Callable)) {
+ if (result == Scriptable.NOT_FOUND) {
+ throw notFoundError(scope, name);
+ } else {
+ throw notFunctionError(result, name);
+ }
+ }
+ // Top scope is not NativeWith or NativeCall => thisObj == scope
+ Scriptable thisObj = scope;
+ storeScriptable(cx, thisObj);
+ return (Callable)result;
+ }
+
+ // name will call storeScriptable(cx, thisObj);
+ return (Callable)nameOrFunction(cx, scope, parent, name, true);
+ }
+
+ /**
+ * Prepare for calling obj[id](...): return function corresponding to
+ * obj[id] and make obj properly converted to Scriptable available
+ * as ScriptRuntime.lastStoredScriptable() for consumption as thisObj.
+ * The caller must call ScriptRuntime.lastStoredScriptable() immediately
+ * after calling this method.
+ */
+ public static Callable getElemFunctionAndThis(Object obj,
+ Object elem,
+ Context cx)
+ {
+ String s = toStringIdOrIndex(cx, elem);
+ if (s != null) {
+ return getPropFunctionAndThis(obj, s, cx);
+ }
+ int index = lastIndexResult(cx);
+
+ Scriptable thisObj = toObjectOrNull(cx, obj);
+ if (thisObj == null) {
+ throw undefCallError(obj, String.valueOf(index));
+ }
+
+ Object value;
+ for (;;) {
+ // Ignore XML lookup as required by ECMA 357, 11.2.2.1
+ value = ScriptableObject.getProperty(thisObj, index);
+ if (value != Scriptable.NOT_FOUND) {
+ break;
+ }
+ if (!(thisObj instanceof XMLObject)) {
+ break;
+ }
+ XMLObject xmlObject = (XMLObject)thisObj;
+ Scriptable extra = xmlObject.getExtraMethodSource(cx);
+ if (extra == null) {
+ break;
+ }
+ thisObj = extra;
+ }
+ if (!(value instanceof Callable)) {
+ throw notFunctionError(value, elem);
+ }
+
+ storeScriptable(cx, thisObj);
+ return (Callable)value;
+ }
+
+ /**
+ * Prepare for calling obj.property(...): return function corresponding to
+ * obj.property and make obj properly converted to Scriptable available
+ * as ScriptRuntime.lastStoredScriptable() for consumption as thisObj.
+ * The caller must call ScriptRuntime.lastStoredScriptable() immediately
+ * after calling this method.
+ * Warning: this doesn't allow to resolve primitive prototype properly when
+ * many top scopes are involved.
+ */
+ public static Callable getPropFunctionAndThis(Object obj,
+ String property,
+ Context cx)
+ {
+ Scriptable thisObj = toObjectOrNull(cx, obj);
+ return getPropFunctionAndThisHelper(obj, property, cx, thisObj);
+ }
+
+ /**
+ * Prepare for calling obj.property(...): return function corresponding to
+ * obj.property and make obj properly converted to Scriptable available
+ * as ScriptRuntime.lastStoredScriptable() for consumption as thisObj.
+ * The caller must call ScriptRuntime.lastStoredScriptable() immediately
+ * after calling this method.
+ */
+ public static Callable getPropFunctionAndThis(Object obj,
+ String property,
+ Context cx, final Scriptable scope)
+ {
+ Scriptable thisObj = toObjectOrNull(cx, obj, scope);
+ return getPropFunctionAndThisHelper(obj, property, cx, thisObj);
+ }
+
+ private static Callable getPropFunctionAndThisHelper(Object obj,
+ String property, Context cx, Scriptable thisObj)
+ {
+ if (thisObj == null) {
+ throw undefCallError(obj, property);
+ }
+
+ Object value;
+ for (;;) {
+ // Ignore XML lookup as required by ECMA 357, 11.2.2.1
+ value = ScriptableObject.getProperty(thisObj, property);
+ if (value != Scriptable.NOT_FOUND) {
+ break;
+ }
+ if (!(thisObj instanceof XMLObject)) {
+ break;
+ }
+ XMLObject xmlObject = (XMLObject)thisObj;
+ Scriptable extra = xmlObject.getExtraMethodSource(cx);
+ if (extra == null) {
+ break;
+ }
+ thisObj = extra;
+ }
+
+ if (!(value instanceof Callable)) {
+ Object noSuchMethod = ScriptableObject.getProperty(thisObj, "__noSuchMethod__");
+ if (noSuchMethod instanceof Callable)
+ value = new NoSuchMethodShim((Callable)noSuchMethod, property);
+ else
+ throw notFunctionError(thisObj, value, property);
+ }
+
+ storeScriptable(cx, thisObj);
+ return (Callable)value;
+ }
+
+ /**
+ * Prepare for calling <expression>(...): return function corresponding to
+ * <expression> and make parent scope of the function available
+ * as ScriptRuntime.lastStoredScriptable() for consumption as thisObj.
+ * The caller must call ScriptRuntime.lastStoredScriptable() immediately
+ * after calling this method.
+ */
+ public static Callable getValueFunctionAndThis(Object value, Context cx)
+ {
+ if (!(value instanceof Callable)) {
+ throw notFunctionError(value);
+ }
+
+ Callable f = (Callable)value;
+ Scriptable thisObj = null;
+ if (f instanceof Scriptable) {
+ thisObj = ((Scriptable)f).getParentScope();
+ }
+ if (thisObj == null) {
+ if (cx.topCallScope == null) throw new IllegalStateException();
+ thisObj = cx.topCallScope;
+ }
+ if (thisObj.getParentScope() != null) {
+ if (thisObj instanceof NativeWith) {
+ // functions defined inside with should have with target
+ // as their thisObj
+ } else if (thisObj instanceof NativeCall) {
+ // nested functions should have top scope as their thisObj
+ thisObj = ScriptableObject.getTopLevelScope(thisObj);
+ }
+ }
+ storeScriptable(cx, thisObj);
+ return f;
+ }
+
+ /**
+ * Perform function call in reference context. Should always
+ * return value that can be passed to
+ * {@link #refGet(Ref, Context)} or {@link #refSet(Ref, Object, Context)}
+ * arbitrary number of times.
+ * The args array reference should not be stored in any object that is
+ * can be GC-reachable after this method returns. If this is necessary,
+ * store args.clone(), not args array itself.
+ */
+ public static Ref callRef(Callable function, Scriptable thisObj,
+ Object[] args, Context cx)
+ {
+ if (function instanceof RefCallable) {
+ RefCallable rfunction = (RefCallable)function;
+ Ref ref = rfunction.refCall(cx, thisObj, args);
+ if (ref == null) {
+ throw new IllegalStateException(rfunction.getClass().getName()+".refCall() returned null");
+ }
+ return ref;
+ }
+ // No runtime support for now
+ String msg = getMessage1("msg.no.ref.from.function",
+ toString(function));
+ throw constructError("ReferenceError", msg);
+ }
+
+ /**
+ * Operator new.
+ *
+ * See ECMA 11.2.2
+ */
+ public static Scriptable newObject(Object fun, Context cx,
+ Scriptable scope, Object[] args)
+ {
+ if (!(fun instanceof Function)) {
+ throw notFunctionError(fun);
+ }
+ Function function = (Function)fun;
+ return function.construct(cx, scope, args);
+ }
+
+ public static Object callSpecial(Context cx, Callable fun,
+ Scriptable thisObj,
+ Object[] args, Scriptable scope,
+ Scriptable callerThis, int callType,
+ String filename, int lineNumber)
+ {
+ if (callType == Node.SPECIALCALL_EVAL) {
+ if (NativeGlobal.isEvalFunction(fun)) {
+ return evalSpecial(cx, scope, callerThis, args,
+ filename, lineNumber);
+ }
+ } else if (callType == Node.SPECIALCALL_WITH) {
+ if (NativeWith.isWithFunction(fun)) {
+ throw Context.reportRuntimeError1("msg.only.from.new",
+ "With");
+ }
+ } else {
+ throw Kit.codeBug();
+ }
+
+ return fun.call(cx, scope, thisObj, args);
+ }
+
+ public static Object newSpecial(Context cx, Object fun,
+ Object[] args, Scriptable scope,
+ int callType)
+ {
+ if (callType == Node.SPECIALCALL_EVAL) {
+ if (NativeGlobal.isEvalFunction(fun)) {
+ throw typeError1("msg.not.ctor", "eval");
+ }
+ } else if (callType == Node.SPECIALCALL_WITH) {
+ if (NativeWith.isWithFunction(fun)) {
+ return NativeWith.newWithSpecial(cx, scope, args);
+ }
+ } else {
+ throw Kit.codeBug();
+ }
+
+ return newObject(fun, cx, scope, args);
+ }
+
+ /**
+ * Function.prototype.apply and Function.prototype.call
+ *
+ * See Ecma 15.3.4.[34]
+ */
+ public static Object applyOrCall(boolean isApply,
+ Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ int L = args.length;
+ Callable function = getCallable(thisObj);
+
+ Scriptable callThis = null;
+ if (L != 0) {
+ callThis = toObjectOrNull(cx, args[0]);
+ }
+ if (callThis == null) {
+ // This covers the case of args[0] == (null|undefined) as well.
+ callThis = getTopCallScope(cx);
+ }
+
+ Object[] callArgs;
+ if (isApply) {
+ // Follow Ecma 15.3.4.3
+ callArgs = L <= 1 ? ScriptRuntime.emptyArgs :
+ getApplyArguments(cx, args[1]);
+ } else {
+ // Follow Ecma 15.3.4.4
+ if (L <= 1) {
+ callArgs = ScriptRuntime.emptyArgs;
+ } else {
+ callArgs = new Object[L - 1];
+ System.arraycopy(args, 1, callArgs, 0, L - 1);
+ }
+ }
+
+ return function.call(cx, scope, callThis, callArgs);
+ }
+
+ static Object[] getApplyArguments(Context cx, Object arg1)
+ {
+ if (arg1 == null || arg1 == Undefined.instance) {
+ return ScriptRuntime.emptyArgs;
+ } else if (arg1 instanceof NativeArray || arg1 instanceof Arguments) {
+ return cx.getElements((Scriptable) arg1);
+ } else {
+ throw ScriptRuntime.typeError0("msg.arg.isnt.array");
+ }
+ }
+
+ static Callable getCallable(Scriptable thisObj)
+ {
+ Callable function;
+ if (thisObj instanceof Callable) {
+ function = (Callable)thisObj;
+ } else {
+ Object value = thisObj.getDefaultValue(ScriptRuntime.FunctionClass);
+ if (!(value instanceof Callable)) {
+ throw ScriptRuntime.notFunctionError(value, thisObj);
+ }
+ function = (Callable)value;
+ }
+ return function;
+ }
+
+ /**
+ * The eval function property of the global object.
+ *
+ * See ECMA 15.1.2.1
+ */
+ public static Object evalSpecial(Context cx, Scriptable scope,
+ Object thisArg, Object[] args,
+ String filename, int lineNumber)
+ {
+ if (args.length < 1)
+ return Undefined.instance;
+ Object x = args[0];
+ if (!(x instanceof String)) {
+ if (cx.hasFeature(Context.FEATURE_STRICT_MODE) ||
+ cx.hasFeature(Context.FEATURE_STRICT_EVAL))
+ {
+ throw Context.reportRuntimeError0("msg.eval.nonstring.strict");
+ }
+ String message = ScriptRuntime.getMessage0("msg.eval.nonstring");
+ Context.reportWarning(message);
+ return x;
+ }
+ if (filename == null) {
+ int[] linep = new int[1];
+ filename = Context.getSourcePositionFromStack(linep);
+ if (filename != null) {
+ lineNumber = linep[0];
+ } else {
+ filename = "";
+ }
+ }
+ String sourceName = ScriptRuntime.
+ makeUrlForGeneratedScript(true, filename, lineNumber);
+
+ ErrorReporter reporter;
+ reporter = DefaultErrorReporter.forEval(cx.getErrorReporter());
+
+ Evaluator evaluator = Context.createInterpreter();
+ if (evaluator == null) {
+ throw new JavaScriptException("Interpreter not present",
+ filename, lineNumber);
+ }
+
+ // Compile with explicit interpreter instance to force interpreter
+ // mode.
+ Script script = cx.compileString((String)x, evaluator,
+ reporter, sourceName, 1, null);
+ evaluator.setEvalScriptFlag(script);
+ Callable c = (Callable)script;
+ return c.call(cx, scope, (Scriptable)thisArg, ScriptRuntime.emptyArgs);
+ }
+
+ /**
+ * The typeof operator
+ */
+ public static String typeof(Object value)
+ {
+ if (value == null)
+ return "object";
+ if (value == Undefined.instance)
+ return "undefined";
+ if (value instanceof Scriptable)
+ {
+ if (value instanceof ScriptableObject &&
+ ((ScriptableObject)value).avoidObjectDetection())
+ {
+ return "undefined";
+ }
+ if (value instanceof XMLObject)
+ return "xml";
+ return (value instanceof Callable) ? "function" : "object";
+ }
+ if (value instanceof String)
+ return "string";
+ if (value instanceof Number)
+ return "number";
+ if (value instanceof Boolean)
+ return "boolean";
+ throw errorWithClassName("msg.invalid.type", value);
+ }
+
+ /**
+ * The typeof operator that correctly handles the undefined case
+ */
+ public static String typeofName(Scriptable scope, String id)
+ {
+ Context cx = Context.getContext();
+ Scriptable val = bind(cx, scope, id);
+ if (val == null)
+ return "undefined";
+ return typeof(getObjectProp(val, id, cx));
+ }
+
+ // neg:
+ // implement the '-' operator inline in the caller
+ // as "-toNumber(val)"
+
+ // not:
+ // implement the '!' operator inline in the caller
+ // as "!toBoolean(val)"
+
+ // bitnot:
+ // implement the '~' operator inline in the caller
+ // as "~toInt32(val)"
+
+ public static Object add(Object val1, Object val2, Context cx)
+ {
+ if(val1 instanceof Number && val2 instanceof Number) {
+ return wrapNumber(((Number)val1).doubleValue() +
+ ((Number)val2).doubleValue());
+ }
+ if (val1 instanceof XMLObject) {
+ Object test = ((XMLObject)val1).addValues(cx, true, val2);
+ if (test != Scriptable.NOT_FOUND) {
+ return test;
+ }
+ }
+ if (val2 instanceof XMLObject) {
+ Object test = ((XMLObject)val2).addValues(cx, false, val1);
+ if (test != Scriptable.NOT_FOUND) {
+ return test;
+ }
+ }
+ if (val1 instanceof Scriptable)
+ val1 = ((Scriptable) val1).getDefaultValue(null);
+ if (val2 instanceof Scriptable)
+ val2 = ((Scriptable) val2).getDefaultValue(null);
+ if (!(val1 instanceof String) && !(val2 instanceof String))
+ if ((val1 instanceof Number) && (val2 instanceof Number))
+ return wrapNumber(((Number)val1).doubleValue() +
+ ((Number)val2).doubleValue());
+ else
+ return wrapNumber(toNumber(val1) + toNumber(val2));
+ return toString(val1).concat(toString(val2));
+ }
+
+ public static String add(String val1, Object val2) {
+ return val1.concat(toString(val2));
+ }
+
+ public static String add(Object val1, String val2) {
+ return toString(val1).concat(val2);
+ }
+
+ /**
+ * @deprecated The method is only present for compatibility.
+ */
+ public static Object nameIncrDecr(Scriptable scopeChain, String id,
+ int incrDecrMask)
+ {
+ return nameIncrDecr(scopeChain, id, Context.getContext(), incrDecrMask);
+ }
+
+ public static Object nameIncrDecr(Scriptable scopeChain, String id,
+ Context cx, int incrDecrMask)
+ {
+ Scriptable target;
+ Object value;
+ search: {
+ do {
+ if (cx.useDynamicScope && scopeChain.getParentScope() == null) {
+ scopeChain = checkDynamicScope(cx.topCallScope, scopeChain);
+ }
+ target = scopeChain;
+ do {
+ value = target.get(id, scopeChain);
+ if (value != Scriptable.NOT_FOUND) {
+ break search;
+ }
+ target = target.getPrototype();
+ } while (target != null);
+ scopeChain = scopeChain.getParentScope();
+ } while (scopeChain != null);
+ throw notFoundError(scopeChain, id);
+ }
+ return doScriptableIncrDecr(target, id, scopeChain, value,
+ incrDecrMask);
+ }
+
+ public static Object propIncrDecr(Object obj, String id,
+ Context cx, int incrDecrMask)
+ {
+ Scriptable start = toObjectOrNull(cx, obj);
+ if (start == null) {
+ throw undefReadError(obj, id);
+ }
+
+ Scriptable target = start;
+ Object value;
+ search: {
+ do {
+ value = target.get(id, start);
+ if (value != Scriptable.NOT_FOUND) {
+ break search;
+ }
+ target = target.getPrototype();
+ } while (target != null);
+ start.put(id, start, NaNobj);
+ return NaNobj;
+ }
+ return doScriptableIncrDecr(target, id, start, value,
+ incrDecrMask);
+ }
+
+ private static Object doScriptableIncrDecr(Scriptable target,
+ String id,
+ Scriptable protoChainStart,
+ Object value,
+ int incrDecrMask)
+ {
+ boolean post = ((incrDecrMask & Node.POST_FLAG) != 0);
+ double number;
+ if (value instanceof Number) {
+ number = ((Number)value).doubleValue();
+ } else {
+ number = toNumber(value);
+ if (post) {
+ // convert result to number
+ value = wrapNumber(number);
+ }
+ }
+ if ((incrDecrMask & Node.DECR_FLAG) == 0) {
+ ++number;
+ } else {
+ --number;
+ }
+ Number result = wrapNumber(number);
+ target.put(id, protoChainStart, result);
+ if (post) {
+ return value;
+ } else {
+ return result;
+ }
+ }
+
+ public static Object elemIncrDecr(Object obj, Object index,
+ Context cx, int incrDecrMask)
+ {
+ Object value = getObjectElem(obj, index, cx);
+ boolean post = ((incrDecrMask & Node.POST_FLAG) != 0);
+ double number;
+ if (value instanceof Number) {
+ number = ((Number)value).doubleValue();
+ } else {
+ number = toNumber(value);
+ if (post) {
+ // convert result to number
+ value = wrapNumber(number);
+ }
+ }
+ if ((incrDecrMask & Node.DECR_FLAG) == 0) {
+ ++number;
+ } else {
+ --number;
+ }
+ Number result = wrapNumber(number);
+ setObjectElem(obj, index, result, cx);
+ if (post) {
+ return value;
+ } else {
+ return result;
+ }
+ }
+
+ public static Object refIncrDecr(Ref ref, Context cx, int incrDecrMask)
+ {
+ Object value = ref.get(cx);
+ boolean post = ((incrDecrMask & Node.POST_FLAG) != 0);
+ double number;
+ if (value instanceof Number) {
+ number = ((Number)value).doubleValue();
+ } else {
+ number = toNumber(value);
+ if (post) {
+ // convert result to number
+ value = wrapNumber(number);
+ }
+ }
+ if ((incrDecrMask & Node.DECR_FLAG) == 0) {
+ ++number;
+ } else {
+ --number;
+ }
+ Number result = wrapNumber(number);
+ ref.set(cx, result);
+ if (post) {
+ return value;
+ } else {
+ return result;
+ }
+ }
+
+ private static Object toPrimitive(Object val)
+ {
+ if (!(val instanceof Scriptable)) {
+ return val;
+ }
+ Scriptable s = (Scriptable)val;
+ Object result = s.getDefaultValue(null);
+ if (result instanceof Scriptable)
+ throw typeError0("msg.bad.default.value");
+ return result;
+ }
+
+ /**
+ * Equality
+ *
+ * See ECMA 11.9
+ */
+ public static boolean eq(Object x, Object y)
+ {
+ if (x == null || x == Undefined.instance) {
+ if (y == null || y == Undefined.instance) {
+ return true;
+ }
+ if (y instanceof ScriptableObject) {
+ Object test = ((ScriptableObject)y).equivalentValues(x);
+ if (test != Scriptable.NOT_FOUND) {
+ return ((Boolean)test).booleanValue();
+ }
+ }
+ return false;
+ } else if (x instanceof Number) {
+ return eqNumber(((Number)x).doubleValue(), y);
+ } else if (x instanceof String) {
+ return eqString((String)x, y);
+ } else if (x instanceof Boolean) {
+ boolean b = ((Boolean)x).booleanValue();
+ if (y instanceof Boolean) {
+ return b == ((Boolean)y).booleanValue();
+ }
+ if (y instanceof ScriptableObject) {
+ Object test = ((ScriptableObject)y).equivalentValues(x);
+ if (test != Scriptable.NOT_FOUND) {
+ return ((Boolean)test).booleanValue();
+ }
+ }
+ return eqNumber(b ? 1.0 : 0.0, y);
+ } else if (x instanceof Scriptable) {
+ if (y instanceof Scriptable) {
+ if (x == y) {
+ return true;
+ }
+ if (x instanceof ScriptableObject) {
+ Object test = ((ScriptableObject)x).equivalentValues(y);
+ if (test != Scriptable.NOT_FOUND) {
+ return ((Boolean)test).booleanValue();
+ }
+ }
+ if (y instanceof ScriptableObject) {
+ Object test = ((ScriptableObject)y).equivalentValues(x);
+ if (test != Scriptable.NOT_FOUND) {
+ return ((Boolean)test).booleanValue();
+ }
+ }
+ if (x instanceof Wrapper && y instanceof Wrapper) {
+ // See bug 413838. Effectively an extension to ECMA for
+ // the LiveConnect case.
+ Object unwrappedX = ((Wrapper)x).unwrap();
+ Object unwrappedY = ((Wrapper)y).unwrap();
+ return unwrappedX == unwrappedY ||
+ (isPrimitive(unwrappedX) &&
+ isPrimitive(unwrappedY) &&
+ eq(unwrappedX, unwrappedY));
+ }
+ return false;
+ } else if (y instanceof Boolean) {
+ if (x instanceof ScriptableObject) {
+ Object test = ((ScriptableObject)x).equivalentValues(y);
+ if (test != Scriptable.NOT_FOUND) {
+ return ((Boolean)test).booleanValue();
+ }
+ }
+ double d = ((Boolean)y).booleanValue() ? 1.0 : 0.0;
+ return eqNumber(d, x);
+ } else if (y instanceof Number) {
+ return eqNumber(((Number)y).doubleValue(), x);
+ } else if (y instanceof String) {
+ return eqString((String)y, x);
+ }
+ // covers the case when y == Undefined.instance as well
+ return false;
+ } else {
+ warnAboutNonJSObject(x);
+ return x == y;
+ }
+ }
+
+ private static boolean isPrimitive(Object obj) {
+ return (obj instanceof Number) || (obj instanceof String) ||
+ (obj instanceof Boolean);
+ }
+
+ static boolean eqNumber(double x, Object y)
+ {
+ for (;;) {
+ if (y == null || y == Undefined.instance) {
+ return false;
+ } else if (y instanceof Number) {
+ return x == ((Number)y).doubleValue();
+ } else if (y instanceof String) {
+ return x == toNumber(y);
+ } else if (y instanceof Boolean) {
+ return x == (((Boolean)y).booleanValue() ? 1.0 : +0.0);
+ } else if (y instanceof Scriptable) {
+ if (y instanceof ScriptableObject) {
+ Object xval = wrapNumber(x);
+ Object test = ((ScriptableObject)y).equivalentValues(xval);
+ if (test != Scriptable.NOT_FOUND) {
+ return ((Boolean)test).booleanValue();
+ }
+ }
+ y = toPrimitive(y);
+ } else {
+ warnAboutNonJSObject(y);
+ return false;
+ }
+ }
+ }
+
+ private static boolean eqString(String x, Object y)
+ {
+ for (;;) {
+ if (y == null || y == Undefined.instance) {
+ return false;
+ } else if (y instanceof String) {
+ return x.equals(y);
+ } else if (y instanceof Number) {
+ return toNumber(x) == ((Number)y).doubleValue();
+ } else if (y instanceof Boolean) {
+ return toNumber(x) == (((Boolean)y).booleanValue() ? 1.0 : 0.0);
+ } else if (y instanceof Scriptable) {
+ if (y instanceof ScriptableObject) {
+ Object test = ((ScriptableObject)y).equivalentValues(x);
+ if (test != Scriptable.NOT_FOUND) {
+ return ((Boolean)test).booleanValue();
+ }
+ }
+ y = toPrimitive(y);
+ continue;
+ } else {
+ warnAboutNonJSObject(y);
+ return false;
+ }
+ }
+ }
+ public static boolean shallowEq(Object x, Object y)
+ {
+ if (x == y) {
+ if (!(x instanceof Number)) {
+ return true;
+ }
+ // NaN check
+ double d = ((Number)x).doubleValue();
+ return d == d;
+ }
+ if (x == null || x == Undefined.instance) {
+ return false;
+ } else if (x instanceof Number) {
+ if (y instanceof Number) {
+ return ((Number)x).doubleValue() == ((Number)y).doubleValue();
+ }
+ } else if (x instanceof String) {
+ if (y instanceof String) {
+ return x.equals(y);
+ }
+ } else if (x instanceof Boolean) {
+ if (y instanceof Boolean) {
+ return x.equals(y);
+ }
+ } else if (x instanceof Scriptable) {
+ if (x instanceof Wrapper && y instanceof Wrapper) {
+ return ((Wrapper)x).unwrap() == ((Wrapper)y).unwrap();
+ }
+ } else {
+ warnAboutNonJSObject(x);
+ return x == y;
+ }
+ return false;
+ }
+
+ /**
+ * The instanceof operator.
+ *
+ * @return a instanceof b
+ */
+ public static boolean instanceOf(Object a, Object b, Context cx)
+ {
+ // Check RHS is an object
+ if (! (b instanceof Scriptable)) {
+ throw typeError0("msg.instanceof.not.object");
+ }
+
+ // for primitive values on LHS, return false
+ // XXX we may want to change this so that
+ // 5 instanceof Number == true
+ if (! (a instanceof Scriptable))
+ return false;
+
+ return ((Scriptable)b).hasInstance((Scriptable)a);
+ }
+
+ /**
+ * Delegates to
+ *
+ * @return true iff rhs appears in lhs' proto chain
+ */
+ public static boolean jsDelegatesTo(Scriptable lhs, Scriptable rhs) {
+ Scriptable proto = lhs.getPrototype();
+
+ while (proto != null) {
+ if (proto.equals(rhs)) return true;
+ proto = proto.getPrototype();
+ }
+
+ return false;
+ }
+
+ /**
+ * The in operator.
+ *
+ * This is a new JS 1.3 language feature. The in operator mirrors
+ * the operation of the for .. in construct, and tests whether the
+ * rhs has the property given by the lhs. It is different from the
+ * for .. in construct in that:
+ * <BR> - it doesn't perform ToObject on the right hand side
+ * <BR> - it returns true for DontEnum properties.
+ * @param a the left hand operand
+ * @param b the right hand operand
+ *
+ * @return true if property name or element number a is a property of b
+ */
+ public static boolean in(Object a, Object b, Context cx)
+ {
+ if (!(b instanceof Scriptable)) {
+ throw typeError0("msg.instanceof.not.object");
+ }
+
+ return hasObjectElem((Scriptable)b, a, cx);
+ }
+
+ public static boolean cmp_LT(Object val1, Object val2)
+ {
+ double d1, d2;
+ if (val1 instanceof Number && val2 instanceof Number) {
+ d1 = ((Number)val1).doubleValue();
+ d2 = ((Number)val2).doubleValue();
+ } else {
+ if (val1 instanceof Scriptable)
+ val1 = ((Scriptable) val1).getDefaultValue(NumberClass);
+ if (val2 instanceof Scriptable)
+ val2 = ((Scriptable) val2).getDefaultValue(NumberClass);
+ if (val1 instanceof String && val2 instanceof String) {
+ return ((String)val1).compareTo((String)val2) < 0;
+ }
+ d1 = toNumber(val1);
+ d2 = toNumber(val2);
+ }
+ return d1 < d2;
+ }
+
+ public static boolean cmp_LE(Object val1, Object val2)
+ {
+ double d1, d2;
+ if (val1 instanceof Number && val2 instanceof Number) {
+ d1 = ((Number)val1).doubleValue();
+ d2 = ((Number)val2).doubleValue();
+ } else {
+ if (val1 instanceof Scriptable)
+ val1 = ((Scriptable) val1).getDefaultValue(NumberClass);
+ if (val2 instanceof Scriptable)
+ val2 = ((Scriptable) val2).getDefaultValue(NumberClass);
+ if (val1 instanceof String && val2 instanceof String) {
+ return ((String)val1).compareTo((String)val2) <= 0;
+ }
+ d1 = toNumber(val1);
+ d2 = toNumber(val2);
+ }
+ return d1 <= d2;
+ }
+
+ // ------------------
+ // Statements
+ // ------------------
+
+ public static ScriptableObject getGlobal(Context cx) {
+ final String GLOBAL_CLASS = "org.mozilla.javascript.tools.shell.Global";
+ Class<?> globalClass = Kit.classOrNull(GLOBAL_CLASS);
+ if (globalClass != null) {
+ try {
+ Class<?>[] parm = { ScriptRuntime.ContextClass };
+ Constructor<?> globalClassCtor = globalClass.getConstructor(parm);
+ Object[] arg = { cx };
+ return (ScriptableObject) globalClassCtor.newInstance(arg);
+ } catch (Exception e) {
+ // fall through...
+ }
+ }
+ return new ImporterTopLevel(cx);
+ }
+
+ public static boolean hasTopCall(Context cx)
+ {
+ return (cx.topCallScope != null);
+ }
+
+ public static Scriptable getTopCallScope(Context cx)
+ {
+ Scriptable scope = cx.topCallScope;
+ if (scope == null) {
+ throw new IllegalStateException();
+ }
+ return scope;
+ }
+
+ public static Object doTopCall(Callable callable,
+ Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (scope == null)
+ throw new IllegalArgumentException();
+ if (cx.topCallScope != null) throw new IllegalStateException();
+
+ Object result;
+ cx.topCallScope = ScriptableObject.getTopLevelScope(scope);
+ cx.useDynamicScope = cx.hasFeature(Context.FEATURE_DYNAMIC_SCOPE);
+ ContextFactory f = cx.getFactory();
+ try {
+ result = f.doTopCall(callable, cx, scope, thisObj, args);
+ } finally {
+ cx.topCallScope = null;
+ // Cleanup cached references
+ cx.cachedXMLLib = null;
+
+ if (cx.currentActivationCall != null) {
+ // Function should always call exitActivationFunction
+ // if it creates activation record
+ throw new IllegalStateException();
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Return <tt>possibleDynamicScope</tt> if <tt>staticTopScope</tt>
+ * is present on its prototype chain and return <tt>staticTopScope</tt>
+ * otherwise.
+ * Should only be called when <tt>staticTopScope</tt> is top scope.
+ */
+ static Scriptable checkDynamicScope(Scriptable possibleDynamicScope,
+ Scriptable staticTopScope)
+ {
+ // Return cx.topCallScope if scope
+ if (possibleDynamicScope == staticTopScope) {
+ return possibleDynamicScope;
+ }
+ Scriptable proto = possibleDynamicScope;
+ for (;;) {
+ proto = proto.getPrototype();
+ if (proto == staticTopScope) {
+ return possibleDynamicScope;
+ }
+ if (proto == null) {
+ return staticTopScope;
+ }
+ }
+ }
+
+ public static void addInstructionCount(Context cx, int instructionsToAdd)
+ {
+ cx.instructionCount += instructionsToAdd;
+ if (cx.instructionCount > cx.instructionThreshold)
+ {
+ cx.observeInstructionCount(cx.instructionCount);
+ cx.instructionCount = 0;
+ }
+ }
+
+ public static void initScript(NativeFunction funObj, Scriptable thisObj,
+ Context cx, Scriptable scope,
+ boolean evalScript)
+ {
+ if (cx.topCallScope == null)
+ throw new IllegalStateException();
+
+ int varCount = funObj.getParamAndVarCount();
+ if (varCount != 0) {
+
+ Scriptable varScope = scope;
+ // Never define any variables from var statements inside with
+ // object. See bug 38590.
+ while (varScope instanceof NativeWith) {
+ varScope = varScope.getParentScope();
+ }
+
+ for (int i = varCount; i-- != 0;) {
+ String name = funObj.getParamOrVarName(i);
+ boolean isConst = funObj.getParamOrVarConst(i);
+ // Don't overwrite existing def if already defined in object
+ // or prototypes of object.
+ if (!ScriptableObject.hasProperty(scope, name)) {
+ if (!evalScript) {
+ // Global var definitions are supposed to be DONTDELETE
+ if (isConst)
+ ScriptableObject.defineConstProperty(varScope, name);
+ else
+ ScriptableObject.defineProperty(
+ varScope, name, Undefined.instance,
+ ScriptableObject.PERMANENT);
+ } else {
+ varScope.put(name, varScope, Undefined.instance);
+ }
+ } else {
+ ScriptableObject.redefineProperty(scope, name, isConst);
+ }
+ }
+ }
+ }
+
+ public static Scriptable createFunctionActivation(NativeFunction funObj,
+ Scriptable scope,
+ Object[] args)
+ {
+ return new NativeCall(funObj, scope, args);
+ }
+
+
+ public static void enterActivationFunction(Context cx,
+ Scriptable scope)
+ {
+ if (cx.topCallScope == null)
+ throw new IllegalStateException();
+ NativeCall call = (NativeCall)scope;
+ call.parentActivationCall = cx.currentActivationCall;
+ cx.currentActivationCall = call;
+ }
+
+ public static void exitActivationFunction(Context cx)
+ {
+ NativeCall call = cx.currentActivationCall;
+ cx.currentActivationCall = call.parentActivationCall;
+ call.parentActivationCall = null;
+ }
+
+ static NativeCall findFunctionActivation(Context cx, Function f)
+ {
+ NativeCall call = cx.currentActivationCall;
+ while (call != null) {
+ if (call.function == f)
+ return call;
+ call = call.parentActivationCall;
+ }
+ return null;
+ }
+
+ public static Scriptable newCatchScope(Throwable t,
+ Scriptable lastCatchScope,
+ String exceptionName,
+ Context cx, Scriptable scope)
+ {
+ Object obj;
+ boolean cacheObj;
+
+ getObj:
+ if (t instanceof JavaScriptException) {
+ cacheObj = false;
+ obj = ((JavaScriptException)t).getValue();
+ } else {
+ cacheObj = true;
+
+ // Create wrapper object unless it was associated with
+ // the previous scope object
+
+ if (lastCatchScope != null) {
+ NativeObject last = (NativeObject)lastCatchScope;
+ obj = last.getAssociatedValue(t);
+ if (obj == null) Kit.codeBug();
+ break getObj;
+ }
+
+ RhinoException re;
+ String errorName;
+ String errorMsg;
+ Throwable javaException = null;
+
+ if (t instanceof EcmaError) {
+ EcmaError ee = (EcmaError)t;
+ re = ee;
+ errorName = ee.getName();
+ errorMsg = ee.getErrorMessage();
+ } else if (t instanceof WrappedException) {
+ WrappedException we = (WrappedException)t;
+ re = we;
+ javaException = we.getWrappedException();
+ errorName = "JavaException";
+ errorMsg = javaException.getClass().getName()
+ +": "+javaException.getMessage();
+ } else if (t instanceof EvaluatorException) {
+ // Pure evaluator exception, nor WrappedException instance
+ EvaluatorException ee = (EvaluatorException)t;
+ re = ee;
+ errorName = "InternalError";
+ errorMsg = ee.getMessage();
+ } else if (cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)) {
+ // With FEATURE_ENHANCED_JAVA_ACCESS, scripts can catch
+ // all exception types
+ re = new WrappedException(t);
+ errorName = "JavaException";
+ errorMsg = t.toString();
+ } else {
+ // Script can catch only instances of JavaScriptException,
+ // EcmaError and EvaluatorException
+ throw Kit.codeBug();
+ }
+
+ String sourceUri = re.sourceName();
+ if (sourceUri == null) {
+ sourceUri = "";
+ }
+ int line = re.lineNumber();
+ Object args[];
+ if (line > 0) {
+ args = new Object[] { errorMsg, sourceUri, new Integer(line) };
+ } else {
+ args = new Object[] { errorMsg, sourceUri };
+ }
+
+ Scriptable errorObject = cx.newObject(scope, errorName, args);
+ ScriptableObject.putProperty(errorObject, "name", errorName);
+
+ if (javaException != null && isVisible(cx, javaException)) {
+ Object wrap = cx.getWrapFactory().wrap(cx, scope, javaException,
+ null);
+ ScriptableObject.defineProperty(
+ errorObject, "javaException", wrap,
+ ScriptableObject.PERMANENT | ScriptableObject.READONLY);
+ }
+ if (isVisible(cx, re)) {
+ Object wrap = cx.getWrapFactory().wrap(cx, scope, re, null);
+ ScriptableObject.defineProperty(
+ errorObject, "rhinoException", wrap,
+ ScriptableObject.PERMANENT | ScriptableObject.READONLY);
+ }
+ obj = errorObject;
+ }
+
+ NativeObject catchScopeObject = new NativeObject();
+ // See ECMA 12.4
+ catchScopeObject.defineProperty(
+ exceptionName, obj, ScriptableObject.PERMANENT);
+
+ if (isVisible(cx, t)) {
+ // Add special Rhino object __exception__ defined in the catch
+ // scope that can be used to retrieve the Java exception associated
+ // with the JavaScript exception (to get stack trace info, etc.)
+ catchScopeObject.defineProperty(
+ "__exception__", Context.javaToJS(t, scope),
+ ScriptableObject.PERMANENT|ScriptableObject.DONTENUM);
+ }
+
+ if (cacheObj) {
+ catchScopeObject.associateValue(t, obj);
+ }
+ return catchScopeObject;
+ }
+
+ private static boolean isVisible(Context cx, Object obj) {
+ ClassShutter shutter = cx.getClassShutter();
+ return shutter == null ||
+ shutter.visibleToScripts(obj.getClass().getName());
+ }
+
+ public static Scriptable enterWith(Object obj, Context cx,
+ Scriptable scope)
+ {
+ Scriptable sobj = toObjectOrNull(cx, obj);
+ if (sobj == null) {
+ throw typeError1("msg.undef.with", toString(obj));
+ }
+ if (sobj instanceof XMLObject) {
+ XMLObject xmlObject = (XMLObject)sobj;
+ return xmlObject.enterWith(scope);
+ }
+ return new NativeWith(scope, sobj);
+ }
+
+ public static Scriptable leaveWith(Scriptable scope)
+ {
+ NativeWith nw = (NativeWith)scope;
+ return nw.getParentScope();
+ }
+
+ public static Scriptable enterDotQuery(Object value, Scriptable scope)
+ {
+ if (!(value instanceof XMLObject)) {
+ throw notXmlError(value);
+ }
+ XMLObject object = (XMLObject)value;
+ return object.enterDotQuery(scope);
+ }
+
+ public static Object updateDotQuery(boolean value, Scriptable scope)
+ {
+ // Return null to continue looping
+ NativeWith nw = (NativeWith)scope;
+ return nw.updateDotQuery(value);
+ }
+
+ public static Scriptable leaveDotQuery(Scriptable scope)
+ {
+ NativeWith nw = (NativeWith)scope;
+ return nw.getParentScope();
+ }
+
+ public static void setFunctionProtoAndParent(BaseFunction fn,
+ Scriptable scope)
+ {
+ fn.setParentScope(scope);
+ fn.setPrototype(ScriptableObject.getFunctionPrototype(scope));
+ }
+
+ public static void setObjectProtoAndParent(ScriptableObject object,
+ Scriptable scope)
+ {
+ // Compared with function it always sets the scope to top scope
+ scope = ScriptableObject.getTopLevelScope(scope);
+ object.setParentScope(scope);
+ Scriptable proto
+ = ScriptableObject.getClassPrototype(scope, object.getClassName());
+ object.setPrototype(proto);
+ }
+
+ public static void initFunction(Context cx, Scriptable scope,
+ NativeFunction function, int type,
+ boolean fromEvalCode)
+ {
+ if (type == FunctionNode.FUNCTION_STATEMENT) {
+ String name = function.getFunctionName();
+ if (name != null && name.length() != 0) {
+ if (!fromEvalCode) {
+ // ECMA specifies that functions defined in global and
+ // function scope outside eval should have DONTDELETE set.
+ ScriptableObject.defineProperty
+ (scope, name, function, ScriptableObject.PERMANENT);
+ } else {
+ scope.put(name, scope, function);
+ }
+ }
+ } else if (type == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) {
+ String name = function.getFunctionName();
+ if (name != null && name.length() != 0) {
+ // Always put function expression statements into initial
+ // activation object ignoring the with statement to follow
+ // SpiderMonkey
+ while (scope instanceof NativeWith) {
+ scope = scope.getParentScope();
+ }
+ scope.put(name, scope, function);
+ }
+ } else {
+ throw Kit.codeBug();
+ }
+ }
+
+ public static Scriptable newArrayLiteral(Object[] objects,
+ int[] skipIndices,
+ Context cx, Scriptable scope)
+ {
+ final int SKIP_DENSITY = 2;
+ int count = objects.length;
+ int skipCount = 0;
+ if (skipIndices != null) {
+ skipCount = skipIndices.length;
+ }
+ int length = count + skipCount;
+ if (length > 1 && skipCount * SKIP_DENSITY < length) {
+ // If not too sparse, create whole array for constructor
+ Object[] sparse;
+ if (skipCount == 0) {
+ sparse = objects;
+ } else {
+ sparse = new Object[length];
+ int skip = 0;
+ for (int i = 0, j = 0; i != length; ++i) {
+ if (skip != skipCount && skipIndices[skip] == i) {
+ sparse[i] = Scriptable.NOT_FOUND;
+ ++skip;
+ continue;
+ }
+ sparse[i] = objects[j];
+ ++j;
+ }
+ }
+ return cx.newObject(scope, "Array", sparse);
+ }
+
+ Scriptable arrayObj = cx.newObject(scope, "Array",
+ ScriptRuntime.emptyArgs);
+ int skip = 0;
+ for (int i = 0, j = 0; i != length; ++i) {
+ if (skip != skipCount && skipIndices[skip] == i) {
+ ++skip;
+ continue;
+ }
+ ScriptableObject.putProperty(arrayObj, i, objects[j]);
+ ++j;
+ }
+ return arrayObj;
+ }
+
+ /**
+ * This method is here for backward compat with existing compiled code. It
+ * is called when an object literal is compiled. The next instance will be
+ * the version called from new code.
+ * @deprecated This method only present for compatibility.
+ */
+ public static Scriptable newObjectLiteral(Object[] propertyIds,
+ Object[] propertyValues,
+ Context cx, Scriptable scope)
+ {
+ // This will initialize to all zeros, exactly what we need for old-style
+ // getterSetters values (no getters or setters in the list)
+ int [] getterSetters = new int[propertyIds.length];
+ return newObjectLiteral(propertyIds, propertyValues, getterSetters,
+ cx, scope);
+ }
+
+ public static Scriptable newObjectLiteral(Object[] propertyIds,
+ Object[] propertyValues,
+ int [] getterSetters,
+ Context cx, Scriptable scope)
+ {
+ Scriptable object = cx.newObject(scope);
+ for (int i = 0, end = propertyIds.length; i != end; ++i) {
+ Object id = propertyIds[i];
+ int getterSetter = getterSetters[i];
+ Object value = propertyValues[i];
+ if (id instanceof String) {
+ if (getterSetter == 0)
+ ScriptableObject.putProperty(object, (String)id, value);
+ else {
+ Callable fun;
+ String definer;
+ if (getterSetter < 0) // < 0 means get foo() ...
+ definer = "__defineGetter__";
+ else
+ definer = "__defineSetter__";
+ fun = getPropFunctionAndThis(object, definer, cx);
+ // Must consume the last scriptable object in cx
+ lastStoredScriptable(cx);
+ Object[] outArgs = new Object[2];
+ outArgs[0] = id;
+ outArgs[1] = value;
+ fun.call(cx, scope, object, outArgs);
+ }
+ } else {
+ int index = ((Integer)id).intValue();
+ ScriptableObject.putProperty(object, index, value);
+ }
+ }
+ return object;
+ }
+
+ public static boolean isArrayObject(Object obj)
+ {
+ return obj instanceof NativeArray || obj instanceof Arguments;
+ }
+
+ public static Object[] getArrayElements(Scriptable object)
+ {
+ Context cx = Context.getContext();
+ long longLen = NativeArray.getLengthProperty(cx, object);
+ if (longLen > Integer.MAX_VALUE) {
+ // arrays beyond MAX_INT is not in Java in any case
+ throw new IllegalArgumentException();
+ }
+ int len = (int) longLen;
+ if (len == 0) {
+ return ScriptRuntime.emptyArgs;
+ } else {
+ Object[] result = new Object[len];
+ for (int i=0; i < len; i++) {
+ Object elem = ScriptableObject.getProperty(object, i);
+ result[i] = (elem == Scriptable.NOT_FOUND) ? Undefined.instance
+ : elem;
+ }
+ return result;
+ }
+ }
+
+ static void checkDeprecated(Context cx, String name) {
+ int version = cx.getLanguageVersion();
+ if (version >= Context.VERSION_1_4 || version == Context.VERSION_DEFAULT) {
+ String msg = getMessage1("msg.deprec.ctor", name);
+ if (version == Context.VERSION_DEFAULT)
+ Context.reportWarning(msg);
+ else
+ throw Context.reportRuntimeError(msg);
+ }
+ }
+
+ public static String getMessage0(String messageId)
+ {
+ return getMessage(messageId, null);
+ }
+
+ public static String getMessage1(String messageId, Object arg1)
+ {
+ Object[] arguments = {arg1};
+ return getMessage(messageId, arguments);
+ }
+
+ public static String getMessage2(
+ String messageId, Object arg1, Object arg2)
+ {
+ Object[] arguments = {arg1, arg2};
+ return getMessage(messageId, arguments);
+ }
+
+ public static String getMessage3(
+ String messageId, Object arg1, Object arg2, Object arg3)
+ {
+ Object[] arguments = {arg1, arg2, arg3};
+ return getMessage(messageId, arguments);
+ }
+
+ public static String getMessage4(
+ String messageId, Object arg1, Object arg2, Object arg3, Object arg4)
+ {
+ Object[] arguments = {arg1, arg2, arg3, arg4};
+ return getMessage(messageId, arguments);
+ }
+
+ /**
+ * This is an interface defining a message provider. Create your
+ * own implementation to override the default error message provider.
+ *
+ * @author Mike Harm
+ */
+ public interface MessageProvider {
+
+ /**
+ * Returns a textual message identified by the given messageId,
+ * parameterized by the given arguments.
+ *
+ * @param messageId the identifier of the message
+ * @param arguments the arguments to fill into the message
+ */
+ String getMessage(String messageId, Object[] arguments);
+ }
+
+ public static MessageProvider messageProvider = new DefaultMessageProvider();
+
+ public static String getMessage(String messageId, Object[] arguments)
+ {
+ return messageProvider.getMessage(messageId, arguments);
+ }
+
+ /* OPT there's a noticable delay for the first error! Maybe it'd
+ * make sense to use a ListResourceBundle instead of a properties
+ * file to avoid (synchronized) text parsing.
+ */
+ private static class DefaultMessageProvider implements MessageProvider {
+ public String getMessage(String messageId, Object[] arguments) {
+ final String defaultResource
+ = "org.mozilla.javascript.resources.Messages";
+
+ Context cx = Context.getCurrentContext();
+ Locale locale = cx != null ? cx.getLocale() : Locale.getDefault();
+
+ // ResourceBundle does caching.
+ ResourceBundle rb = ResourceBundle.getBundle(defaultResource, locale);
+
+ String formatString;
+ try {
+ formatString = rb.getString(messageId);
+ } catch (java.util.MissingResourceException mre) {
+ throw new RuntimeException
+ ("no message resource found for message property "+ messageId);
+ }
+
+ /*
+ * It's OK to format the string, even if 'arguments' is null;
+ * we need to format it anyway, to make double ''s collapse to
+ * single 's.
+ */
+ MessageFormat formatter = new MessageFormat(formatString);
+ return formatter.format(arguments);
+ }
+ }
+
+ public static EcmaError constructError(String error, String message)
+ {
+ int[] linep = new int[1];
+ String filename = Context.getSourcePositionFromStack(linep);
+ return constructError(error, message, filename, linep[0], null, 0);
+ }
+
+ public static EcmaError constructError(String error,
+ String message,
+ int lineNumberDelta)
+ {
+ int[] linep = new int[1];
+ String filename = Context.getSourcePositionFromStack(linep);
+ if (linep[0] != 0) {
+ linep[0] += lineNumberDelta;
+ }
+ return constructError(error, message, filename, linep[0], null, 0);
+ }
+
+ public static EcmaError constructError(String error,
+ String message,
+ String sourceName,
+ int lineNumber,
+ String lineSource,
+ int columnNumber)
+ {
+ return new EcmaError(error, message, sourceName,
+ lineNumber, lineSource, columnNumber);
+ }
+
+ public static EcmaError typeError(String message)
+ {
+ return constructError("TypeError", message);
+ }
+
+ public static EcmaError typeError0(String messageId)
+ {
+ String msg = getMessage0(messageId);
+ return typeError(msg);
+ }
+
+ public static EcmaError typeError1(String messageId, String arg1)
+ {
+ String msg = getMessage1(messageId, arg1);
+ return typeError(msg);
+ }
+
+ public static EcmaError typeError2(String messageId, String arg1,
+ String arg2)
+ {
+ String msg = getMessage2(messageId, arg1, arg2);
+ return typeError(msg);
+ }
+
+ public static EcmaError typeError3(String messageId, String arg1,
+ String arg2, String arg3)
+ {
+ String msg = getMessage3(messageId, arg1, arg2, arg3);
+ return typeError(msg);
+ }
+
+ public static RuntimeException undefReadError(Object object, Object id)
+ {
+ String idStr = (id == null) ? "null" : id.toString();
+ return typeError2("msg.undef.prop.read", toString(object), idStr);
+ }
+
+ public static RuntimeException undefCallError(Object object, Object id)
+ {
+ String idStr = (id == null) ? "null" : id.toString();
+ return typeError2("msg.undef.method.call", toString(object), idStr);
+ }
+
+ public static RuntimeException undefWriteError(Object object,
+ Object id,
+ Object value)
+ {
+ String idStr = (id == null) ? "null" : id.toString();
+ String valueStr = (value instanceof Scriptable)
+ ? value.toString() : toString(value);
+ return typeError3("msg.undef.prop.write", toString(object), idStr,
+ valueStr);
+ }
+
+ public static RuntimeException notFoundError(Scriptable object,
+ String property)
+ {
+ // XXX: use object to improve the error message
+ String msg = getMessage1("msg.is.not.defined", property);
+ throw constructError("ReferenceError", msg);
+ }
+
+ public static RuntimeException notFunctionError(Object value)
+ {
+ return notFunctionError(value, value);
+ }
+
+ public static RuntimeException notFunctionError(Object value,
+ Object messageHelper)
+ {
+ // Use value for better error reporting
+ String msg = (messageHelper == null)
+ ? "null" : messageHelper.toString();
+ if (value == Scriptable.NOT_FOUND) {
+ return typeError1("msg.function.not.found", msg);
+ }
+ return typeError2("msg.isnt.function", msg, typeof(value));
+ }
+
+ public static RuntimeException notFunctionError(Object obj, Object value,
+ String propertyName)
+ {
+ // Use obj and value for better error reporting
+ String objString = toString(obj);
+ if (value == Scriptable.NOT_FOUND) {
+ return typeError2("msg.function.not.found.in", propertyName,
+ objString);
+ }
+ return typeError3("msg.isnt.function.in", propertyName, objString,
+ typeof(value));
+ }
+
+ private static RuntimeException notXmlError(Object value)
+ {
+ throw typeError1("msg.isnt.xml.object", toString(value));
+ }
+
+ private static void warnAboutNonJSObject(Object nonJSObject)
+ {
+ String message =
+"RHINO USAGE WARNING: Missed Context.javaToJS() conversion:\n"
++"Rhino runtime detected object "+nonJSObject+" of class "+nonJSObject.getClass().getName()+" where it expected String, Number, Boolean or Scriptable instance. Please check your code for missing Context.javaToJS() call.";
+ Context.reportWarning(message);
+ // Just to be sure that it would be noticed
+ System.err.println(message);
+ }
+
+ public static RegExpProxy getRegExpProxy(Context cx)
+ {
+ return cx.getRegExpProxy();
+ }
+
+ public static void setRegExpProxy(Context cx, RegExpProxy proxy)
+ {
+ if (proxy == null) throw new IllegalArgumentException();
+ cx.regExpProxy = proxy;
+ }
+
+ public static RegExpProxy checkRegExpProxy(Context cx)
+ {
+ RegExpProxy result = getRegExpProxy(cx);
+ if (result == null) {
+ throw Context.reportRuntimeError0("msg.no.regexp");
+ }
+ return result;
+ }
+
+ private static XMLLib currentXMLLib(Context cx)
+ {
+ // Scripts should be running to access this
+ if (cx.topCallScope == null)
+ throw new IllegalStateException();
+
+ XMLLib xmlLib = cx.cachedXMLLib;
+ if (xmlLib == null) {
+ xmlLib = XMLLib.extractFromScope(cx.topCallScope);
+ if (xmlLib == null)
+ throw new IllegalStateException();
+ cx.cachedXMLLib = xmlLib;
+ }
+
+ return xmlLib;
+ }
+
+ /**
+ * Escapes the reserved characters in a value of an attribute
+ *
+ * @param value Unescaped text
+ * @return The escaped text
+ */
+ public static String escapeAttributeValue(Object value, Context cx)
+ {
+ XMLLib xmlLib = currentXMLLib(cx);
+ return xmlLib.escapeAttributeValue(value);
+ }
+
+ /**
+ * Escapes the reserved characters in a value of a text node
+ *
+ * @param value Unescaped text
+ * @return The escaped text
+ */
+ public static String escapeTextValue(Object value, Context cx)
+ {
+ XMLLib xmlLib = currentXMLLib(cx);
+ return xmlLib.escapeTextValue(value);
+ }
+
+ public static Ref memberRef(Object obj, Object elem,
+ Context cx, int memberTypeFlags)
+ {
+ if (!(obj instanceof XMLObject)) {
+ throw notXmlError(obj);
+ }
+ XMLObject xmlObject = (XMLObject)obj;
+ return xmlObject.memberRef(cx, elem, memberTypeFlags);
+ }
+
+ public static Ref memberRef(Object obj, Object namespace, Object elem,
+ Context cx, int memberTypeFlags)
+ {
+ if (!(obj instanceof XMLObject)) {
+ throw notXmlError(obj);
+ }
+ XMLObject xmlObject = (XMLObject)obj;
+ return xmlObject.memberRef(cx, namespace, elem, memberTypeFlags);
+ }
+
+ public static Ref nameRef(Object name, Context cx,
+ Scriptable scope, int memberTypeFlags)
+ {
+ XMLLib xmlLib = currentXMLLib(cx);
+ return xmlLib.nameRef(cx, name, scope, memberTypeFlags);
+ }
+
+ public static Ref nameRef(Object namespace, Object name, Context cx,
+ Scriptable scope, int memberTypeFlags)
+ {
+ XMLLib xmlLib = currentXMLLib(cx);
+ return xmlLib.nameRef(cx, namespace, name, scope, memberTypeFlags);
+ }
+
+ private static void storeIndexResult(Context cx, int index)
+ {
+ cx.scratchIndex = index;
+ }
+
+ static int lastIndexResult(Context cx)
+ {
+ return cx.scratchIndex;
+ }
+
+ public static void storeUint32Result(Context cx, long value)
+ {
+ if ((value >>> 32) != 0)
+ throw new IllegalArgumentException();
+ cx.scratchUint32 = value;
+ }
+
+ public static long lastUint32Result(Context cx)
+ {
+ long value = cx.scratchUint32;
+ if ((value >>> 32) != 0)
+ throw new IllegalStateException();
+ return value;
+ }
+
+ private static void storeScriptable(Context cx, Scriptable value)
+ {
+ // The previously stored scratchScriptable should be consumed
+ if (cx.scratchScriptable != null)
+ throw new IllegalStateException();
+ cx.scratchScriptable = value;
+ }
+
+ public static Scriptable lastStoredScriptable(Context cx)
+ {
+ Scriptable result = cx.scratchScriptable;
+ cx.scratchScriptable = null;
+ return result;
+ }
+
+ static String makeUrlForGeneratedScript
+ (boolean isEval, String masterScriptUrl, int masterScriptLine)
+ {
+ if (isEval) {
+ return masterScriptUrl+'#'+masterScriptLine+"(eval)";
+ } else {
+ return masterScriptUrl+'#'+masterScriptLine+"(Function)";
+ }
+ }
+
+ static boolean isGeneratedScript(String sourceUrl) {
+ // ALERT: this may clash with a valid URL containing (eval) or
+ // (Function)
+ return sourceUrl.indexOf("(eval)") >= 0
+ || sourceUrl.indexOf("(Function)") >= 0;
+ }
+
+ private static RuntimeException errorWithClassName(String msg, Object val)
+ {
+ return Context.reportRuntimeError1(msg, val.getClass().getName());
+ }
+
+ public static final Object[] emptyArgs = new Object[0];
+ public static final String[] emptyStrings = new String[0];
+
+}
diff --git a/src/org/mozilla/javascript/Scriptable.java b/src/org/mozilla/javascript/Scriptable.java
new file mode 100644
index 0000000..aa95d09
--- /dev/null
+++ b/src/org/mozilla/javascript/Scriptable.java
@@ -0,0 +1,342 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * This is interface that all objects in JavaScript must implement.
+ * The interface provides for the management of properties and for
+ * performing conversions.
+ * <p>
+ * Host system implementors may find it easier to extend the ScriptableObject
+ * class rather than implementing Scriptable when writing host objects.
+ * <p>
+ * There are many static methods defined in ScriptableObject that perform
+ * the multiple calls to the Scriptable interface needed in order to
+ * manipulate properties in prototype chains.
+ * <p>
+ *
+ * @see org.mozilla.javascript.ScriptableObject
+ * @author Norris Boyd
+ * @author Nick Thompson
+ * @author Brendan Eich
+ */
+
+public interface Scriptable {
+
+ /**
+ * Get the name of the set of objects implemented by this Java class.
+ * This corresponds to the [[Class]] operation in ECMA and is used
+ * by Object.prototype.toString() in ECMA.<p>
+ * See ECMA 8.6.2 and 15.2.4.2.
+ */
+ public String getClassName();
+
+ /**
+ * Value returned from <code>get</code> if the property is not
+ * found.
+ */
+ public static final Object NOT_FOUND = UniqueTag.NOT_FOUND;
+
+ /**
+ * Get a named property from the object.
+ *
+ * Looks property up in this object and returns the associated value
+ * if found. Returns NOT_FOUND if not found.
+ * Note that this method is not expected to traverse the prototype
+ * chain. This is different from the ECMA [[Get]] operation.
+ *
+ * Depending on the property selector, the runtime will call
+ * this method or the form of <code>get</code> that takes an
+ * integer:
+ * <table>
+ * <tr><th>JavaScript code</th><th>Java code</th></tr>
+ * <tr><td>a.b </td><td>a.get("b", a)</td></tr>
+ * <tr><td>a["foo"] </td><td>a.get("foo", a)</td></tr>
+ * <tr><td>a[3] </td><td>a.get(3, a)</td></tr>
+ * <tr><td>a["3"] </td><td>a.get(3, a)</td></tr>
+ * <tr><td>a[3.0] </td><td>a.get(3, a)</td></tr>
+ * <tr><td>a["3.0"] </td><td>a.get("3.0", a)</td></tr>
+ * <tr><td>a[1.1] </td><td>a.get("1.1", a)</td></tr>
+ * <tr><td>a[-4] </td><td>a.get(-4, a)</td></tr>
+ * </table>
+ * <p>
+ * The values that may be returned are limited to the following:
+ * <UL>
+ * <LI>java.lang.Boolean objects</LI>
+ * <LI>java.lang.String objects</LI>
+ * <LI>java.lang.Number objects</LI>
+ * <LI>org.mozilla.javascript.Scriptable objects</LI>
+ * <LI>null</LI>
+ * <LI>The value returned by Context.getUndefinedValue()</LI>
+ * <LI>NOT_FOUND</LI>
+ * </UL>
+ * @param name the name of the property
+ * @param start the object in which the lookup began
+ * @return the value of the property (may be null), or NOT_FOUND
+ * @see org.mozilla.javascript.Context#getUndefinedValue
+ */
+ public Object get(String name, Scriptable start);
+
+ /**
+ * Get a property from the object selected by an integral index.
+ *
+ * Identical to <code>get(String, Scriptable)</code> except that
+ * an integral index is used to select the property.
+ *
+ * @param index the numeric index for the property
+ * @param start the object in which the lookup began
+ * @return the value of the property (may be null), or NOT_FOUND
+ * @see org.mozilla.javascript.Scriptable#get(String,Scriptable)
+ */
+ public Object get(int index, Scriptable start);
+
+ /**
+ * Indicates whether or not a named property is defined in an object.
+ *
+ * Does not traverse the prototype chain.<p>
+ *
+ * The property is specified by a String name
+ * as defined for the <code>get</code> method.<p>
+ *
+ * @param name the name of the property
+ * @param start the object in which the lookup began
+ * @return true if and only if the named property is found in the object
+ * @see org.mozilla.javascript.Scriptable#get(String, Scriptable)
+ * @see org.mozilla.javascript.ScriptableObject#getProperty(Scriptable, String)
+ */
+ public boolean has(String name, Scriptable start);
+
+ /**
+ * Indicates whether or not an indexed property is defined in an object.
+ *
+ * Does not traverse the prototype chain.<p>
+ *
+ * The property is specified by an integral index
+ * as defined for the <code>get</code> method.<p>
+ *
+ * @param index the numeric index for the property
+ * @param start the object in which the lookup began
+ * @return true if and only if the indexed property is found in the object
+ * @see org.mozilla.javascript.Scriptable#get(int, Scriptable)
+ * @see org.mozilla.javascript.ScriptableObject#getProperty(Scriptable, int)
+ */
+ public boolean has(int index, Scriptable start);
+
+ /**
+ * Sets a named property in this object.
+ * <p>
+ * The property is specified by a string name
+ * as defined for <code>get</code>.
+ * <p>
+ * The possible values that may be passed in are as defined for
+ * <code>get</code>. A class that implements this method may choose
+ * to ignore calls to set certain properties, in which case those
+ * properties are effectively read-only.<p>
+ * For properties defined in a prototype chain,
+ * use <code>putProperty</code> in ScriptableObject. <p>
+ * Note that if a property <i>a</i> is defined in the prototype <i>p</i>
+ * of an object <i>o</i>, then evaluating <code>o.a = 23</code> will cause
+ * <code>set</code> to be called on the prototype <i>p</i> with
+ * <i>o</i> as the <i>start</i> parameter.
+ * To preserve JavaScript semantics, it is the Scriptable
+ * object's responsibility to modify <i>o</i>. <p>
+ * This design allows properties to be defined in prototypes and implemented
+ * in terms of getters and setters of Java values without consuming slots
+ * in each instance.<p>
+ * <p>
+ * The values that may be set are limited to the following:
+ * <UL>
+ * <LI>java.lang.Boolean objects</LI>
+ * <LI>java.lang.String objects</LI>
+ * <LI>java.lang.Number objects</LI>
+ * <LI>org.mozilla.javascript.Scriptable objects</LI>
+ * <LI>null</LI>
+ * <LI>The value returned by Context.getUndefinedValue()</LI>
+ * </UL><p>
+ * Arbitrary Java objects may be wrapped in a Scriptable by first calling
+ * <code>Context.toObject</code>. This allows the property of a JavaScript
+ * object to contain an arbitrary Java object as a value.<p>
+ * Note that <code>has</code> will be called by the runtime first before
+ * <code>set</code> is called to determine in which object the
+ * property is defined.
+ * Note that this method is not expected to traverse the prototype chain,
+ * which is different from the ECMA [[Put]] operation.
+ * @param name the name of the property
+ * @param start the object whose property is being set
+ * @param value value to set the property to
+ * @see org.mozilla.javascript.Scriptable#has(String, Scriptable)
+ * @see org.mozilla.javascript.Scriptable#get(String, Scriptable)
+ * @see org.mozilla.javascript.ScriptableObject#putProperty(Scriptable, String, Object)
+ * @see org.mozilla.javascript.Context#toObject(Object, Scriptable)
+ */
+ public void put(String name, Scriptable start, Object value);
+
+ /**
+ * Sets an indexed property in this object.
+ * <p>
+ * The property is specified by an integral index
+ * as defined for <code>get</code>.<p>
+ *
+ * Identical to <code>put(String, Scriptable, Object)</code> except that
+ * an integral index is used to select the property.
+ *
+ * @param index the numeric index for the property
+ * @param start the object whose property is being set
+ * @param value value to set the property to
+ * @see org.mozilla.javascript.Scriptable#has(int, Scriptable)
+ * @see org.mozilla.javascript.Scriptable#get(int, Scriptable)
+ * @see org.mozilla.javascript.ScriptableObject#putProperty(Scriptable, int, Object)
+ * @see org.mozilla.javascript.Context#toObject(Object, Scriptable)
+ */
+ public void put(int index, Scriptable start, Object value);
+
+ /**
+ * Removes a property from this object.
+ * This operation corresponds to the ECMA [[Delete]] except that
+ * the no result is returned. The runtime will guarantee that this
+ * method is called only if the property exists. After this method
+ * is called, the runtime will call Scriptable.has to see if the
+ * property has been removed in order to determine the boolean
+ * result of the delete operator as defined by ECMA 11.4.1.
+ * <p>
+ * A property can be made permanent by ignoring calls to remove
+ * it.<p>
+ * The property is specified by a String name
+ * as defined for <code>get</code>.
+ * <p>
+ * To delete properties defined in a prototype chain,
+ * see deleteProperty in ScriptableObject.
+ * @param name the identifier for the property
+ * @see org.mozilla.javascript.Scriptable#get(String, Scriptable)
+ * @see org.mozilla.javascript.ScriptableObject#deleteProperty(Scriptable, String)
+ */
+ public void delete(String name);
+
+ /**
+ * Removes a property from this object.
+ *
+ * The property is specified by an integral index
+ * as defined for <code>get</code>.
+ * <p>
+ * To delete properties defined in a prototype chain,
+ * see deleteProperty in ScriptableObject.
+ *
+ * Identical to <code>delete(String)</code> except that
+ * an integral index is used to select the property.
+ *
+ * @param index the numeric index for the property
+ * @see org.mozilla.javascript.Scriptable#get(int, Scriptable)
+ * @see org.mozilla.javascript.ScriptableObject#deleteProperty(Scriptable, int)
+ */
+ public void delete(int index);
+
+ /**
+ * Get the prototype of the object.
+ * @return the prototype
+ */
+ public Scriptable getPrototype();
+
+ /**
+ * Set the prototype of the object.
+ * @param prototype the prototype to set
+ */
+ public void setPrototype(Scriptable prototype);
+
+ /**
+ * Get the parent scope of the object.
+ * @return the parent scope
+ */
+ public Scriptable getParentScope();
+
+ /**
+ * Set the parent scope of the object.
+ * @param parent the parent scope to set
+ */
+ public void setParentScope(Scriptable parent);
+
+ /**
+ * Get an array of property ids.
+ *
+ * Not all property ids need be returned. Those properties
+ * whose ids are not returned are considered non-enumerable.
+ *
+ * @return an array of Objects. Each entry in the array is either
+ * a java.lang.String or a java.lang.Number
+ */
+ public Object[] getIds();
+
+ /**
+ * Get the default value of the object with a given hint.
+ * The hints are String.class for type String, Number.class for type
+ * Number, Scriptable.class for type Object, and Boolean.class for
+ * type Boolean. <p>
+ *
+ * A <code>hint</code> of null means "no hint".
+ *
+ * See ECMA 8.6.2.6.
+ *
+ * @param hint the type hint
+ * @return the default value
+ */
+ public Object getDefaultValue(Class<?> hint);
+
+ /**
+ * The instanceof operator.
+ *
+ * <p>
+ * The JavaScript code "lhs instanceof rhs" causes rhs.hasInstance(lhs) to
+ * be called.
+ *
+ * <p>
+ * The return value is implementation dependent so that embedded host objects can
+ * return an appropriate value. See the JS 1.3 language documentation for more
+ * detail.
+ *
+ * <p>This operator corresponds to the proposed EMCA [[HasInstance]] operator.
+ *
+ * @param instance The value that appeared on the LHS of the instanceof
+ * operator
+ *
+ * @return an implementation dependent value
+ */
+ public boolean hasInstance(Scriptable instance);
+}
+
diff --git a/src/org/mozilla/javascript/ScriptableObject.java b/src/org/mozilla/javascript/ScriptableObject.java
new file mode 100644
index 0000000..c45acb9
--- /dev/null
+++ b/src/org/mozilla/javascript/ScriptableObject.java
@@ -0,0 +1,2522 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Daniel Gredler
+ * Bob Jervis
+ * Roger Lawrence
+ * Cameron McCormack
+ * Steve Weiss
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.*;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.io.*;
+import org.mozilla.javascript.debug.DebuggableObject;
+
+/**
+ * This is the default implementation of the Scriptable interface. This
+ * class provides convenient default behavior that makes it easier to
+ * define host objects.
+ * <p>
+ * Various properties and methods of JavaScript objects can be conveniently
+ * defined using methods of ScriptableObject.
+ * <p>
+ * Classes extending ScriptableObject must define the getClassName method.
+ *
+ * @see org.mozilla.javascript.Scriptable
+ * @author Norris Boyd
+ */
+
+public abstract class ScriptableObject implements Scriptable, Serializable,
+ DebuggableObject,
+ ConstProperties
+{
+
+ /**
+ * The empty property attribute.
+ *
+ * Used by getAttributes() and setAttributes().
+ *
+ * @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
+ * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
+ */
+ public static final int EMPTY = 0x00;
+
+ /**
+ * Property attribute indicating assignment to this property is ignored.
+ *
+ * @see org.mozilla.javascript.ScriptableObject
+ * #put(String, Scriptable, Object)
+ * @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
+ * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
+ */
+ public static final int READONLY = 0x01;
+
+ /**
+ * Property attribute indicating property is not enumerated.
+ *
+ * Only enumerated properties will be returned by getIds().
+ *
+ * @see org.mozilla.javascript.ScriptableObject#getIds()
+ * @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
+ * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
+ */
+ public static final int DONTENUM = 0x02;
+
+ /**
+ * Property attribute indicating property cannot be deleted.
+ *
+ * @see org.mozilla.javascript.ScriptableObject#delete(String)
+ * @see org.mozilla.javascript.ScriptableObject#getAttributes(String)
+ * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int)
+ */
+ public static final int PERMANENT = 0x04;
+
+ /**
+ * Property attribute indicating that this is a const property that has not
+ * been assigned yet. The first 'const' assignment to the property will
+ * clear this bit.
+ */
+ public static final int UNINITIALIZED_CONST = 0x08;
+
+ public static final int CONST = PERMANENT|READONLY|UNINITIALIZED_CONST;
+ /**
+ * The prototype of this object.
+ */
+ private Scriptable prototypeObject;
+
+ /**
+ * The parent scope of this object.
+ */
+ private Scriptable parentScopeObject;
+
+ private static final Slot REMOVED = new Slot(null, 0, READONLY);
+
+ static {
+ REMOVED.wasDeleted = true;
+ }
+
+ private transient Slot[] slots;
+ // If count >= 0, it gives number of keys or if count < 0,
+ // it indicates sealed object where ~count gives number of keys
+ private int count;
+
+ // gateways into the definition-order linked list of slots
+ private transient Slot firstAdded;
+ private transient Slot lastAdded;
+
+ // cache; may be removed for smaller memory footprint
+ private transient Slot lastAccess = REMOVED;
+
+ private volatile Map<Object,Object> associatedValues;
+
+ private static final int SLOT_QUERY = 1;
+ private static final int SLOT_MODIFY = 2;
+ private static final int SLOT_REMOVE = 3;
+ private static final int SLOT_MODIFY_GETTER_SETTER = 4;
+ private static final int SLOT_MODIFY_CONST = 5;
+
+ private static class Slot implements Serializable
+ {
+ private static final long serialVersionUID = -6090581677123995491L;
+ String name; // This can change due to caching
+ int indexOrHash;
+ private volatile short attributes;
+ transient volatile boolean wasDeleted;
+ volatile Object value;
+ transient volatile Slot next; // next in hash table bucket
+ transient volatile Slot orderedNext; // next in linked list
+
+ Slot(String name, int indexOrHash, int attributes)
+ {
+ this.name = name;
+ this.indexOrHash = indexOrHash;
+ this.attributes = (short)attributes;
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+ if (name != null) {
+ indexOrHash = name.hashCode();
+ }
+ }
+
+ final int getAttributes()
+ {
+ return attributes;
+ }
+
+ final synchronized void setAttributes(int value)
+ {
+ checkValidAttributes(value);
+ attributes = (short)value;
+ }
+
+ final void checkNotReadonly()
+ {
+ if ((attributes & READONLY) != 0) {
+ String str = (name != null ? name
+ : Integer.toString(indexOrHash));
+ throw Context.reportRuntimeError1("msg.modify.readonly", str);
+ }
+ }
+
+ }
+
+ private static final class GetterSlot extends Slot
+ {
+ static final long serialVersionUID = -4900574849788797588L;
+
+ Object getter;
+ Object setter;
+
+ GetterSlot(String name, int indexOrHash, int attributes)
+ {
+ super(name, indexOrHash, attributes);
+ }
+ }
+
+ static void checkValidAttributes(int attributes)
+ {
+ final int mask = READONLY | DONTENUM | PERMANENT | UNINITIALIZED_CONST;
+ if ((attributes & ~mask) != 0) {
+ throw new IllegalArgumentException(String.valueOf(attributes));
+ }
+ }
+
+ public ScriptableObject()
+ {
+ }
+
+ public ScriptableObject(Scriptable scope, Scriptable prototype)
+ {
+ if (scope == null)
+ throw new IllegalArgumentException();
+
+ parentScopeObject = scope;
+ prototypeObject = prototype;
+ }
+
+ /**
+ * Return the name of the class.
+ *
+ * This is typically the same name as the constructor.
+ * Classes extending ScriptableObject must implement this abstract
+ * method.
+ */
+ public abstract String getClassName();
+
+ /**
+ * Returns true if the named property is defined.
+ *
+ * @param name the name of the property
+ * @param start the object in which the lookup began
+ * @return true if and only if the property was found in the object
+ */
+ public boolean has(String name, Scriptable start)
+ {
+ return null != getSlot(name, 0, SLOT_QUERY);
+ }
+
+ /**
+ * Returns true if the property index is defined.
+ *
+ * @param index the numeric index for the property
+ * @param start the object in which the lookup began
+ * @return true if and only if the property was found in the object
+ */
+ public boolean has(int index, Scriptable start)
+ {
+ return null != getSlot(null, index, SLOT_QUERY);
+ }
+
+ /**
+ * Returns the value of the named property or NOT_FOUND.
+ *
+ * If the property was created using defineProperty, the
+ * appropriate getter method is called.
+ *
+ * @param name the name of the property
+ * @param start the object in which the lookup began
+ * @return the value of the property (may be null), or NOT_FOUND
+ */
+ public Object get(String name, Scriptable start)
+ {
+ return getImpl(name, 0, start);
+ }
+
+ /**
+ * Returns the value of the indexed property or NOT_FOUND.
+ *
+ * @param index the numeric index for the property
+ * @param start the object in which the lookup began
+ * @return the value of the property (may be null), or NOT_FOUND
+ */
+ public Object get(int index, Scriptable start)
+ {
+ return getImpl(null, index, start);
+ }
+
+ /**
+ * Sets the value of the named property, creating it if need be.
+ *
+ * If the property was created using defineProperty, the
+ * appropriate setter method is called. <p>
+ *
+ * If the property's attributes include READONLY, no action is
+ * taken.
+ * This method will actually set the property in the start
+ * object.
+ *
+ * @param name the name of the property
+ * @param start the object whose property is being set
+ * @param value value to set the property to
+ */
+ public void put(String name, Scriptable start, Object value)
+ {
+ if (putImpl(name, 0, start, value, EMPTY))
+ return;
+
+ if (start == this) throw Kit.codeBug();
+ start.put(name, start, value);
+ }
+
+ /**
+ * Sets the value of the indexed property, creating it if need be.
+ *
+ * @param index the numeric index for the property
+ * @param start the object whose property is being set
+ * @param value value to set the property to
+ */
+ public void put(int index, Scriptable start, Object value)
+ {
+ if (putImpl(null, index, start, value, EMPTY))
+ return;
+
+ if (start == this) throw Kit.codeBug();
+ start.put(index, start, value);
+ }
+
+ /**
+ * Removes a named property from the object.
+ *
+ * If the property is not found, or it has the PERMANENT attribute,
+ * no action is taken.
+ *
+ * @param name the name of the property
+ */
+ public void delete(String name)
+ {
+ checkNotSealed(name, 0);
+ accessSlot(name, 0, SLOT_REMOVE);
+ }
+
+ /**
+ * Removes the indexed property from the object.
+ *
+ * If the property is not found, or it has the PERMANENT attribute,
+ * no action is taken.
+ *
+ * @param index the numeric index for the property
+ */
+ public void delete(int index)
+ {
+ checkNotSealed(null, index);
+ accessSlot(null, index, SLOT_REMOVE);
+ }
+
+ /**
+ * Sets the value of the named const property, creating it if need be.
+ *
+ * If the property was created using defineProperty, the
+ * appropriate setter method is called. <p>
+ *
+ * If the property's attributes include READONLY, no action is
+ * taken.
+ * This method will actually set the property in the start
+ * object.
+ *
+ * @param name the name of the property
+ * @param start the object whose property is being set
+ * @param value value to set the property to
+ */
+ public void putConst(String name, Scriptable start, Object value)
+ {
+ if (putImpl(name, 0, start, value, READONLY))
+ return;
+
+ if (start == this) throw Kit.codeBug();
+ if (start instanceof ConstProperties)
+ ((ConstProperties)start).putConst(name, start, value);
+ else
+ start.put(name, start, value);
+ }
+
+ public void defineConst(String name, Scriptable start)
+ {
+ if (putImpl(name, 0, start, Undefined.instance, UNINITIALIZED_CONST))
+ return;
+
+ if (start == this) throw Kit.codeBug();
+ if (start instanceof ConstProperties)
+ ((ConstProperties)start).defineConst(name, start);
+ }
+ /**
+ * Returns true if the named property is defined as a const on this object.
+ * @param name
+ * @return true if the named property is defined as a const, false
+ * otherwise.
+ */
+ public boolean isConst(String name)
+ {
+ Slot slot = getSlot(name, 0, SLOT_QUERY);
+ if (slot == null) {
+ return false;
+ }
+ return (slot.getAttributes() & (PERMANENT|READONLY)) ==
+ (PERMANENT|READONLY);
+
+ }
+ /**
+ * @deprecated Use {@link #getAttributes(String name)}. The engine always
+ * ignored the start argument.
+ */
+ public final int getAttributes(String name, Scriptable start)
+ {
+ return getAttributes(name);
+ }
+
+ /**
+ * @deprecated Use {@link #getAttributes(int index)}. The engine always
+ * ignored the start argument.
+ */
+ public final int getAttributes(int index, Scriptable start)
+ {
+ return getAttributes(index);
+ }
+
+ /**
+ * @deprecated Use {@link #setAttributes(String name, int attributes)}.
+ * The engine always ignored the start argument.
+ */
+ public final void setAttributes(String name, Scriptable start,
+ int attributes)
+ {
+ setAttributes(name, attributes);
+ }
+
+ /**
+ * @deprecated Use {@link #setAttributes(int index, int attributes)}.
+ * The engine always ignored the start argument.
+ */
+ public void setAttributes(int index, Scriptable start,
+ int attributes)
+ {
+ setAttributes(index, attributes);
+ }
+
+ /**
+ * Get the attributes of a named property.
+ *
+ * The property is specified by <code>name</code>
+ * as defined for <code>has</code>.<p>
+ *
+ * @param name the identifier for the property
+ * @return the bitset of attributes
+ * @exception EvaluatorException if the named property is not found
+ * @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable)
+ * @see org.mozilla.javascript.ScriptableObject#READONLY
+ * @see org.mozilla.javascript.ScriptableObject#DONTENUM
+ * @see org.mozilla.javascript.ScriptableObject#PERMANENT
+ * @see org.mozilla.javascript.ScriptableObject#EMPTY
+ */
+ public int getAttributes(String name)
+ {
+ return findAttributeSlot(name, 0, SLOT_QUERY).getAttributes();
+ }
+
+ /**
+ * Get the attributes of an indexed property.
+ *
+ * @param index the numeric index for the property
+ * @exception EvaluatorException if the named property is not found
+ * is not found
+ * @return the bitset of attributes
+ * @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable)
+ * @see org.mozilla.javascript.ScriptableObject#READONLY
+ * @see org.mozilla.javascript.ScriptableObject#DONTENUM
+ * @see org.mozilla.javascript.ScriptableObject#PERMANENT
+ * @see org.mozilla.javascript.ScriptableObject#EMPTY
+ */
+ public int getAttributes(int index)
+ {
+ return findAttributeSlot(null, index, SLOT_QUERY).getAttributes();
+ }
+
+ /**
+ * Set the attributes of a named property.
+ *
+ * The property is specified by <code>name</code>
+ * as defined for <code>has</code>.<p>
+ *
+ * The possible attributes are READONLY, DONTENUM,
+ * and PERMANENT. Combinations of attributes
+ * are expressed by the bitwise OR of attributes.
+ * EMPTY is the state of no attributes set. Any unused
+ * bits are reserved for future use.
+ *
+ * @param name the name of the property
+ * @param attributes the bitset of attributes
+ * @exception EvaluatorException if the named property is not found
+ * @see org.mozilla.javascript.Scriptable#has(String, Scriptable)
+ * @see org.mozilla.javascript.ScriptableObject#READONLY
+ * @see org.mozilla.javascript.ScriptableObject#DONTENUM
+ * @see org.mozilla.javascript.ScriptableObject#PERMANENT
+ * @see org.mozilla.javascript.ScriptableObject#EMPTY
+ */
+ public void setAttributes(String name, int attributes)
+ {
+ checkNotSealed(name, 0);
+ findAttributeSlot(name, 0, SLOT_MODIFY).setAttributes(attributes);
+ }
+
+ /**
+ * Set the attributes of an indexed property.
+ *
+ * @param index the numeric index for the property
+ * @param attributes the bitset of attributes
+ * @exception EvaluatorException if the named property is not found
+ * @see org.mozilla.javascript.Scriptable#has(String, Scriptable)
+ * @see org.mozilla.javascript.ScriptableObject#READONLY
+ * @see org.mozilla.javascript.ScriptableObject#DONTENUM
+ * @see org.mozilla.javascript.ScriptableObject#PERMANENT
+ * @see org.mozilla.javascript.ScriptableObject#EMPTY
+ */
+ public void setAttributes(int index, int attributes)
+ {
+ checkNotSealed(null, index);
+ findAttributeSlot(null, index, SLOT_MODIFY).setAttributes(attributes);
+ }
+
+ /**
+ * XXX: write docs.
+ */
+ public void setGetterOrSetter(String name, int index,
+ Callable getterOrSetter, boolean isSetter)
+ {
+ if (name != null && index != 0)
+ throw new IllegalArgumentException(name);
+
+ checkNotSealed(name, index);
+ GetterSlot gslot = (GetterSlot)getSlot(name, index,
+ SLOT_MODIFY_GETTER_SETTER);
+ gslot.checkNotReadonly();
+ if (isSetter) {
+ gslot.setter = getterOrSetter;
+ } else {
+ gslot.getter = getterOrSetter;
+ }
+ gslot.value = Undefined.instance;
+ }
+
+ /**
+ * Get the getter or setter for a given property. Used by __lookupGetter__
+ * and __lookupSetter__.
+ *
+ * @param name Name of the object. If nonnull, index must be 0.
+ * @param index Index of the object. If nonzero, name must be null.
+ * @param isSetter If true, return the setter, otherwise return the getter.
+ * @exception IllegalArgumentException if both name and index are nonnull
+ * and nonzero respectively.
+ * @return Null if the property does not exist. Otherwise returns either
+ * the getter or the setter for the property, depending on
+ * the value of isSetter (may be undefined if unset).
+ */
+ public Object getGetterOrSetter(String name, int index, boolean isSetter)
+ {
+ if (name != null && index != 0)
+ throw new IllegalArgumentException(name);
+ Slot slot = getSlot(name, index, SLOT_QUERY);
+ if (slot == null)
+ return null;
+ if (slot instanceof GetterSlot) {
+ GetterSlot gslot = (GetterSlot)slot;
+ Object result = isSetter ? gslot.setter : gslot.getter;
+ return result != null ? result : Undefined.instance;
+ } else
+ return Undefined.instance;
+ }
+
+ /**
+ * Returns whether a property is a getter or a setter
+ * @param name property name
+ * @param index property index
+ * @param setter true to check for a setter, false for a getter
+ * @return whether the property is a getter or a setter
+ */
+ protected boolean isGetterOrSetter(String name, int index, boolean setter) {
+ Slot slot = getSlot(name, index, SLOT_QUERY);
+ if (slot instanceof GetterSlot) {
+ if (setter && ((GetterSlot)slot).setter != null) return true;
+ if (!setter && ((GetterSlot)slot).getter != null) return true;
+ }
+ return false;
+ }
+
+ void addLazilyInitializedValue(String name, int index,
+ LazilyLoadedCtor init, int attributes)
+ {
+ if (name != null && index != 0)
+ throw new IllegalArgumentException(name);
+ checkNotSealed(name, index);
+ GetterSlot gslot = (GetterSlot)getSlot(name, index,
+ SLOT_MODIFY_GETTER_SETTER);
+ gslot.setAttributes(attributes);
+ gslot.getter = null;
+ gslot.setter = null;
+ gslot.value = init;
+ }
+
+ /**
+ * Returns the prototype of the object.
+ */
+ public Scriptable getPrototype()
+ {
+ return prototypeObject;
+ }
+
+ /**
+ * Sets the prototype of the object.
+ */
+ public void setPrototype(Scriptable m)
+ {
+ prototypeObject = m;
+ }
+
+ /**
+ * Returns the parent (enclosing) scope of the object.
+ */
+ public Scriptable getParentScope()
+ {
+ return parentScopeObject;
+ }
+
+ /**
+ * Sets the parent (enclosing) scope of the object.
+ */
+ public void setParentScope(Scriptable m)
+ {
+ parentScopeObject = m;
+ }
+
+ /**
+ * Returns an array of ids for the properties of the object.
+ *
+ * <p>Any properties with the attribute DONTENUM are not listed. <p>
+ *
+ * @return an array of java.lang.Objects with an entry for every
+ * listed property. Properties accessed via an integer index will
+ * have a corresponding
+ * Integer entry in the returned array. Properties accessed by
+ * a String will have a String entry in the returned array.
+ */
+ public Object[] getIds() {
+ return getIds(false);
+ }
+
+ /**
+ * Returns an array of ids for the properties of the object.
+ *
+ * <p>All properties, even those with attribute DONTENUM, are listed. <p>
+ *
+ * @return an array of java.lang.Objects with an entry for every
+ * listed property. Properties accessed via an integer index will
+ * have a corresponding
+ * Integer entry in the returned array. Properties accessed by
+ * a String will have a String entry in the returned array.
+ */
+ public Object[] getAllIds() {
+ return getIds(true);
+ }
+
+ /**
+ * Implements the [[DefaultValue]] internal method.
+ *
+ * <p>Note that the toPrimitive conversion is a no-op for
+ * every type other than Object, for which [[DefaultValue]]
+ * is called. See ECMA 9.1.<p>
+ *
+ * A <code>hint</code> of null means "no hint".
+ *
+ * @param typeHint the type hint
+ * @return the default value for the object
+ *
+ * See ECMA 8.6.2.6.
+ */
+ public Object getDefaultValue(Class<?> typeHint)
+ {
+ return getDefaultValue(this, typeHint);
+ }
+
+ public static Object getDefaultValue(Scriptable object, Class<?> typeHint)
+ {
+ Context cx = null;
+ for (int i=0; i < 2; i++) {
+ boolean tryToString;
+ if (typeHint == ScriptRuntime.StringClass) {
+ tryToString = (i == 0);
+ } else {
+ tryToString = (i == 1);
+ }
+
+ String methodName;
+ Object[] args;
+ if (tryToString) {
+ methodName = "toString";
+ args = ScriptRuntime.emptyArgs;
+ } else {
+ methodName = "valueOf";
+ args = new Object[1];
+ String hint;
+ if (typeHint == null) {
+ hint = "undefined";
+ } else if (typeHint == ScriptRuntime.StringClass) {
+ hint = "string";
+ } else if (typeHint == ScriptRuntime.ScriptableClass) {
+ hint = "object";
+ } else if (typeHint == ScriptRuntime.FunctionClass) {
+ hint = "function";
+ } else if (typeHint == ScriptRuntime.BooleanClass
+ || typeHint == Boolean.TYPE)
+ {
+ hint = "boolean";
+ } else if (typeHint == ScriptRuntime.NumberClass ||
+ typeHint == ScriptRuntime.ByteClass ||
+ typeHint == Byte.TYPE ||
+ typeHint == ScriptRuntime.ShortClass ||
+ typeHint == Short.TYPE ||
+ typeHint == ScriptRuntime.IntegerClass ||
+ typeHint == Integer.TYPE ||
+ typeHint == ScriptRuntime.FloatClass ||
+ typeHint == Float.TYPE ||
+ typeHint == ScriptRuntime.DoubleClass ||
+ typeHint == Double.TYPE)
+ {
+ hint = "number";
+ } else {
+ throw Context.reportRuntimeError1(
+ "msg.invalid.type", typeHint.toString());
+ }
+ args[0] = hint;
+ }
+ Object v = getProperty(object, methodName);
+ if (!(v instanceof Function))
+ continue;
+ Function fun = (Function) v;
+ if (cx == null)
+ cx = Context.getContext();
+ v = fun.call(cx, fun.getParentScope(), object, args);
+ if (v != null) {
+ if (!(v instanceof Scriptable)) {
+ return v;
+ }
+ if (typeHint == ScriptRuntime.ScriptableClass
+ || typeHint == ScriptRuntime.FunctionClass)
+ {
+ return v;
+ }
+ if (tryToString && v instanceof Wrapper) {
+ // Let a wrapped java.lang.String pass for a primitive
+ // string.
+ Object u = ((Wrapper)v).unwrap();
+ if (u instanceof String)
+ return u;
+ }
+ }
+ }
+ // fall through to error
+ String arg = (typeHint == null) ? "undefined" : typeHint.getName();
+ throw ScriptRuntime.typeError1("msg.default.value", arg);
+ }
+
+ /**
+ * Implements the instanceof operator.
+ *
+ * <p>This operator has been proposed to ECMA.
+ *
+ * @param instance The value that appeared on the LHS of the instanceof
+ * operator
+ * @return true if "this" appears in value's prototype chain
+ *
+ */
+ public boolean hasInstance(Scriptable instance) {
+ // Default for JS objects (other than Function) is to do prototype
+ // chasing. This will be overridden in NativeFunction and non-JS
+ // objects.
+
+ return ScriptRuntime.jsDelegatesTo(instance, this);
+ }
+
+ /**
+ * Emulate the SpiderMonkey (and Firefox) feature of allowing
+ * custom objects to avoid detection by normal "object detection"
+ * code patterns. This is used to implement document.all.
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=412247.
+ * This is an analog to JOF_DETECTING from SpiderMonkey; see
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=248549.
+ * Other than this special case, embeddings should return false.
+ * @return true if this object should avoid object detection
+ * @since 1.7R1
+ */
+ public boolean avoidObjectDetection() {
+ return false;
+ }
+
+ /**
+ * Custom <tt>==</tt> operator.
+ * Must return {@link Scriptable#NOT_FOUND} if this object does not
+ * have custom equality operator for the given value,
+ * <tt>Boolean.TRUE</tt> if this object is equivalent to <tt>value</tt>,
+ * <tt>Boolean.FALSE</tt> if this object is not equivalent to
+ * <tt>value</tt>.
+ * <p>
+ * The default implementation returns Boolean.TRUE
+ * if <tt>this == value</tt> or {@link Scriptable#NOT_FOUND} otherwise.
+ * It indicates that by default custom equality is available only if
+ * <tt>value</tt> is <tt>this</tt> in which case true is returned.
+ */
+ protected Object equivalentValues(Object value)
+ {
+ return (this == value) ? Boolean.TRUE : Scriptable.NOT_FOUND;
+ }
+
+ /**
+ * Defines JavaScript objects from a Java class that implements Scriptable.
+ *
+ * If the given class has a method
+ * <pre>
+ * static void init(Context cx, Scriptable scope, boolean sealed);</pre>
+ *
+ * or its compatibility form
+ * <pre>
+ * static void init(Scriptable scope);</pre>
+ *
+ * then it is invoked and no further initialization is done.<p>
+ *
+ * However, if no such a method is found, then the class's constructors and
+ * methods are used to initialize a class in the following manner.<p>
+ *
+ * First, the zero-parameter constructor of the class is called to
+ * create the prototype. If no such constructor exists,
+ * a {@link EvaluatorException} is thrown. <p>
+ *
+ * Next, all methods are scanned for special prefixes that indicate that they
+ * have special meaning for defining JavaScript objects.
+ * These special prefixes are
+ * <ul>
+ * <li><code>jsFunction_</code> for a JavaScript function
+ * <li><code>jsStaticFunction_</code> for a JavaScript function that
+ * is a property of the constructor
+ * <li><code>jsGet_</code> for a getter of a JavaScript property
+ * <li><code>jsSet_</code> for a setter of a JavaScript property
+ * <li><code>jsConstructor</code> for a JavaScript function that
+ * is the constructor
+ * </ul><p>
+ *
+ * If the method's name begins with "jsFunction_", a JavaScript function
+ * is created with a name formed from the rest of the Java method name
+ * following "jsFunction_". So a Java method named "jsFunction_foo" will
+ * define a JavaScript method "foo". Calling this JavaScript function
+ * will cause the Java method to be called. The parameters of the method
+ * must be of number and types as defined by the FunctionObject class.
+ * The JavaScript function is then added as a property
+ * of the prototype. <p>
+ *
+ * If the method's name begins with "jsStaticFunction_", it is handled
+ * similarly except that the resulting JavaScript function is added as a
+ * property of the constructor object. The Java method must be static.
+ *
+ * If the method's name begins with "jsGet_" or "jsSet_", the method is
+ * considered to define a property. Accesses to the defined property
+ * will result in calls to these getter and setter methods. If no
+ * setter is defined, the property is defined as READONLY.<p>
+ *
+ * If the method's name is "jsConstructor", the method is
+ * considered to define the body of the constructor. Only one
+ * method of this name may be defined. You may use the varargs forms
+ * for constructors documented in {@link FunctionObject#FunctionObject(String, Member, Scriptable)}
+ *
+ * If no method is found that can serve as constructor, a Java
+ * constructor will be selected to serve as the JavaScript
+ * constructor in the following manner. If the class has only one
+ * Java constructor, that constructor is used to define
+ * the JavaScript constructor. If the the class has two constructors,
+ * one must be the zero-argument constructor (otherwise an
+ * {@link EvaluatorException} would have already been thrown
+ * when the prototype was to be created). In this case
+ * the Java constructor with one or more parameters will be used
+ * to define the JavaScript constructor. If the class has three
+ * or more constructors, an {@link EvaluatorException}
+ * will be thrown.<p>
+ *
+ * Finally, if there is a method
+ * <pre>
+ * static void finishInit(Scriptable scope, FunctionObject constructor,
+ * Scriptable prototype)</pre>
+ *
+ * it will be called to finish any initialization. The <code>scope</code>
+ * argument will be passed, along with the newly created constructor and
+ * the newly created prototype.<p>
+ *
+ * @param scope The scope in which to define the constructor.
+ * @param clazz The Java class to use to define the JavaScript objects
+ * and properties.
+ * @exception IllegalAccessException if access is not available
+ * to a reflected class member
+ * @exception InstantiationException if unable to instantiate
+ * the named class
+ * @exception InvocationTargetException if an exception is thrown
+ * during execution of methods of the named class
+ * @see org.mozilla.javascript.Function
+ * @see org.mozilla.javascript.FunctionObject
+ * @see org.mozilla.javascript.ScriptableObject#READONLY
+ * @see org.mozilla.javascript.ScriptableObject
+ * #defineProperty(String, Class, int)
+ */
+ public static <T extends Scriptable> void defineClass(
+ Scriptable scope, Class<T> clazz)
+ throws IllegalAccessException, InstantiationException,
+ InvocationTargetException
+ {
+ defineClass(scope, clazz, false, false);
+ }
+
+ /**
+ * Defines JavaScript objects from a Java class, optionally
+ * allowing sealing.
+ *
+ * Similar to <code>defineClass(Scriptable scope, Class clazz)</code>
+ * except that sealing is allowed. An object that is sealed cannot have
+ * properties added or removed. Note that sealing is not allowed in
+ * the current ECMA/ISO language specification, but is likely for
+ * the next version.
+ *
+ * @param scope The scope in which to define the constructor.
+ * @param clazz The Java class to use to define the JavaScript objects
+ * and properties. The class must implement Scriptable.
+ * @param sealed Whether or not to create sealed standard objects that
+ * cannot be modified.
+ * @exception IllegalAccessException if access is not available
+ * to a reflected class member
+ * @exception InstantiationException if unable to instantiate
+ * the named class
+ * @exception InvocationTargetException if an exception is thrown
+ * during execution of methods of the named class
+ * @since 1.4R3
+ */
+ public static <T extends Scriptable> void defineClass(
+ Scriptable scope, Class<T> clazz, boolean sealed)
+ throws IllegalAccessException, InstantiationException,
+ InvocationTargetException
+ {
+ defineClass(scope, clazz, sealed, false);
+ }
+
+ /**
+ * Defines JavaScript objects from a Java class, optionally
+ * allowing sealing and mapping of Java inheritance to JavaScript
+ * prototype-based inheritance.
+ *
+ * Similar to <code>defineClass(Scriptable scope, Class clazz)</code>
+ * except that sealing and inheritance mapping are allowed. An object
+ * that is sealed cannot have properties added or removed. Note that
+ * sealing is not allowed in the current ECMA/ISO language specification,
+ * but is likely for the next version.
+ *
+ * @param scope The scope in which to define the constructor.
+ * @param clazz The Java class to use to define the JavaScript objects
+ * and properties. The class must implement Scriptable.
+ * @param sealed Whether or not to create sealed standard objects that
+ * cannot be modified.
+ * @param mapInheritance Whether or not to map Java inheritance to
+ * JavaScript prototype-based inheritance.
+ * @return the class name for the prototype of the specified class
+ * @exception IllegalAccessException if access is not available
+ * to a reflected class member
+ * @exception InstantiationException if unable to instantiate
+ * the named class
+ * @exception InvocationTargetException if an exception is thrown
+ * during execution of methods of the named class
+ * @since 1.6R2
+ */
+ public static <T extends Scriptable> String defineClass(
+ Scriptable scope, Class<T> clazz, boolean sealed,
+ boolean mapInheritance)
+ throws IllegalAccessException, InstantiationException,
+ InvocationTargetException
+ {
+ BaseFunction ctor = buildClassCtor(scope, clazz, sealed,
+ mapInheritance);
+ if (ctor == null)
+ return null;
+ String name = ctor.getClassPrototype().getClassName();
+ defineProperty(scope, name, ctor, ScriptableObject.DONTENUM);
+ return name;
+ }
+
+ static <T extends Scriptable> BaseFunction buildClassCtor(
+ Scriptable scope, Class<T> clazz,
+ boolean sealed,
+ boolean mapInheritance)
+ throws IllegalAccessException, InstantiationException,
+ InvocationTargetException
+ {
+ Method[] methods = FunctionObject.getMethodList(clazz);
+ for (int i=0; i < methods.length; i++) {
+ Method method = methods[i];
+ if (!method.getName().equals("init"))
+ continue;
+ Class<?>[] parmTypes = method.getParameterTypes();
+ if (parmTypes.length == 3 &&
+ parmTypes[0] == ScriptRuntime.ContextClass &&
+ parmTypes[1] == ScriptRuntime.ScriptableClass &&
+ parmTypes[2] == Boolean.TYPE &&
+ Modifier.isStatic(method.getModifiers()))
+ {
+ Object args[] = { Context.getContext(), scope,
+ sealed ? Boolean.TRUE : Boolean.FALSE };
+ method.invoke(null, args);
+ return null;
+ }
+ if (parmTypes.length == 1 &&
+ parmTypes[0] == ScriptRuntime.ScriptableClass &&
+ Modifier.isStatic(method.getModifiers()))
+ {
+ Object args[] = { scope };
+ method.invoke(null, args);
+ return null;
+ }
+
+ }
+
+ // If we got here, there isn't an "init" method with the right
+ // parameter types.
+
+ Constructor<?>[] ctors = clazz.getConstructors();
+ Constructor<?> protoCtor = null;
+ for (int i=0; i < ctors.length; i++) {
+ if (ctors[i].getParameterTypes().length == 0) {
+ protoCtor = ctors[i];
+ break;
+ }
+ }
+ if (protoCtor == null) {
+ throw Context.reportRuntimeError1(
+ "msg.zero.arg.ctor", clazz.getName());
+ }
+
+ Scriptable proto = (Scriptable) protoCtor.newInstance(ScriptRuntime.emptyArgs);
+ String className = proto.getClassName();
+
+ // Set the prototype's prototype, trying to map Java inheritance to JS
+ // prototype-based inheritance if requested to do so.
+ Scriptable superProto = null;
+ if (mapInheritance) {
+ Class<? super T> superClass = clazz.getSuperclass();
+ if (ScriptRuntime.ScriptableClass.isAssignableFrom(superClass) &&
+ !Modifier.isAbstract(superClass.getModifiers()))
+ {
+ Class<? extends Scriptable> superScriptable =
+ extendsScriptable(superClass);
+ String name = ScriptableObject.defineClass(scope,
+ superScriptable, sealed, mapInheritance);
+ if (name != null) {
+ superProto = ScriptableObject.getClassPrototype(scope, name);
+ }
+ }
+ }
+ if (superProto == null) {
+ superProto = ScriptableObject.getObjectPrototype(scope);
+ }
+ proto.setPrototype(superProto);
+
+ // Find out whether there are any methods that begin with
+ // "js". If so, then only methods that begin with special
+ // prefixes will be defined as JavaScript entities.
+ final String functionPrefix = "jsFunction_";
+ final String staticFunctionPrefix = "jsStaticFunction_";
+ final String getterPrefix = "jsGet_";
+ final String setterPrefix = "jsSet_";
+ final String ctorName = "jsConstructor";
+
+ Member ctorMember = FunctionObject.findSingleMethod(methods, ctorName);
+
+ if (ctorMember == null) {
+ if (ctors.length == 1) {
+ ctorMember = ctors[0];
+ } else if (ctors.length == 2) {
+ if (ctors[0].getParameterTypes().length == 0)
+ ctorMember = ctors[1];
+ else if (ctors[1].getParameterTypes().length == 0)
+ ctorMember = ctors[0];
+ }
+ if (ctorMember == null) {
+ throw Context.reportRuntimeError1(
+ "msg.ctor.multiple.parms", clazz.getName());
+ }
+ }
+
+ FunctionObject ctor = new FunctionObject(className, ctorMember, scope);
+ if (ctor.isVarArgsMethod()) {
+ throw Context.reportRuntimeError1
+ ("msg.varargs.ctor", ctorMember.getName());
+ }
+ ctor.initAsConstructor(scope, proto);
+
+ Method finishInit = null;
+ HashSet<String> names = new HashSet<String>(methods.length);
+ for (int i=0; i < methods.length; i++) {
+ if (methods[i] == ctorMember) {
+ continue;
+ }
+ String name = methods[i].getName();
+ if (name.equals("finishInit")) {
+ Class<?>[] parmTypes = methods[i].getParameterTypes();
+ if (parmTypes.length == 3 &&
+ parmTypes[0] == ScriptRuntime.ScriptableClass &&
+ parmTypes[1] == FunctionObject.class &&
+ parmTypes[2] == ScriptRuntime.ScriptableClass &&
+ Modifier.isStatic(methods[i].getModifiers()))
+ {
+ finishInit = methods[i];
+ continue;
+ }
+ }
+ // ignore any compiler generated methods.
+ if (name.indexOf('$') != -1)
+ continue;
+ if (name.equals(ctorName))
+ continue;
+
+ String prefix = null;
+ if (name.startsWith(functionPrefix)) {
+ prefix = functionPrefix;
+ } else if (name.startsWith(staticFunctionPrefix)) {
+ prefix = staticFunctionPrefix;
+ if (!Modifier.isStatic(methods[i].getModifiers())) {
+ throw Context.reportRuntimeError(
+ "jsStaticFunction must be used with static method.");
+ }
+ } else if (name.startsWith(getterPrefix)) {
+ prefix = getterPrefix;
+ } else {
+ // note that setterPrefix is among the unhandled names here -
+ // we deal with that when we see the getter
+ continue;
+ }
+ String propName = name.substring(prefix.length());
+ if (names.contains(propName)) {
+ throw Context.reportRuntimeError2("duplicate.defineClass.name",
+ name, propName);
+ }
+ names.add(propName);
+ name = name.substring(prefix.length());
+ if (prefix == getterPrefix) {
+ if (!(proto instanceof ScriptableObject)) {
+ throw Context.reportRuntimeError2(
+ "msg.extend.scriptable",
+ proto.getClass().toString(), name);
+ }
+ Method setter = FunctionObject.findSingleMethod(
+ methods,
+ setterPrefix + name);
+ int attr = ScriptableObject.PERMANENT |
+ ScriptableObject.DONTENUM |
+ (setter != null ? 0
+ : ScriptableObject.READONLY);
+ ((ScriptableObject) proto).defineProperty(name, null,
+ methods[i], setter,
+ attr);
+ continue;
+ }
+
+ FunctionObject f = new FunctionObject(name, methods[i], proto);
+ if (f.isVarArgsConstructor()) {
+ throw Context.reportRuntimeError1
+ ("msg.varargs.fun", ctorMember.getName());
+ }
+ Scriptable dest = prefix == staticFunctionPrefix
+ ? ctor
+ : proto;
+ defineProperty(dest, name, f, DONTENUM);
+ if (sealed) {
+ f.sealObject();
+ }
+ }
+
+ // Call user code to complete initialization if necessary.
+ if (finishInit != null) {
+ Object[] finishArgs = { scope, ctor, proto };
+ finishInit.invoke(null, finishArgs);
+ }
+
+ // Seal the object if necessary.
+ if (sealed) {
+ ctor.sealObject();
+ if (proto instanceof ScriptableObject) {
+ ((ScriptableObject) proto).sealObject();
+ }
+ }
+
+ return ctor;
+ }
+
+ @SuppressWarnings({"unchecked"})
+ private static <T extends Scriptable> Class<T> extendsScriptable(Class<?> c)
+ {
+ if (ScriptRuntime.ScriptableClass.isAssignableFrom(c))
+ return (Class<T>) c;
+ return null;
+ }
+
+ /**
+ * Define a JavaScript property.
+ *
+ * Creates the property with an initial value and sets its attributes.
+ *
+ * @param propertyName the name of the property to define.
+ * @param value the initial value of the property
+ * @param attributes the attributes of the JavaScript property
+ * @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object)
+ */
+ public void defineProperty(String propertyName, Object value,
+ int attributes)
+ {
+ checkNotSealed(propertyName, 0);
+ put(propertyName, this, value);
+ setAttributes(propertyName, attributes);
+ }
+
+ /**
+ * Utility method to add properties to arbitrary Scriptable object.
+ * If destination is instance of ScriptableObject, calls
+ * defineProperty there, otherwise calls put in destination
+ * ignoring attributes
+ */
+ public static void defineProperty(Scriptable destination,
+ String propertyName, Object value,
+ int attributes)
+ {
+ if (!(destination instanceof ScriptableObject)) {
+ destination.put(propertyName, destination, value);
+ return;
+ }
+ ScriptableObject so = (ScriptableObject)destination;
+ so.defineProperty(propertyName, value, attributes);
+ }
+
+ /**
+ * Utility method to add properties to arbitrary Scriptable object.
+ * If destination is instance of ScriptableObject, calls
+ * defineProperty there, otherwise calls put in destination
+ * ignoring attributes
+ */
+ public static void defineConstProperty(Scriptable destination,
+ String propertyName)
+ {
+ if (destination instanceof ConstProperties) {
+ ConstProperties cp = (ConstProperties)destination;
+ cp.defineConst(propertyName, destination);
+ } else
+ defineProperty(destination, propertyName, Undefined.instance, CONST);
+ }
+
+ /**
+ * Define a JavaScript property with getter and setter side effects.
+ *
+ * If the setter is not found, the attribute READONLY is added to
+ * the given attributes. <p>
+ *
+ * The getter must be a method with zero parameters, and the setter, if
+ * found, must be a method with one parameter.<p>
+ *
+ * @param propertyName the name of the property to define. This name
+ * also affects the name of the setter and getter
+ * to search for. If the propertyId is "foo", then
+ * <code>clazz</code> will be searched for "getFoo"
+ * and "setFoo" methods.
+ * @param clazz the Java class to search for the getter and setter
+ * @param attributes the attributes of the JavaScript property
+ * @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object)
+ */
+ public void defineProperty(String propertyName, Class<?> clazz,
+ int attributes)
+ {
+ int length = propertyName.length();
+ if (length == 0) throw new IllegalArgumentException();
+ char[] buf = new char[3 + length];
+ propertyName.getChars(0, length, buf, 3);
+ buf[3] = Character.toUpperCase(buf[3]);
+ buf[0] = 'g';
+ buf[1] = 'e';
+ buf[2] = 't';
+ String getterName = new String(buf);
+ buf[0] = 's';
+ String setterName = new String(buf);
+
+ Method[] methods = FunctionObject.getMethodList(clazz);
+ Method getter = FunctionObject.findSingleMethod(methods, getterName);
+ Method setter = FunctionObject.findSingleMethod(methods, setterName);
+ if (setter == null)
+ attributes |= ScriptableObject.READONLY;
+ defineProperty(propertyName, null, getter,
+ setter == null ? null : setter, attributes);
+ }
+
+ /**
+ * Define a JavaScript property.
+ *
+ * Use this method only if you wish to define getters and setters for
+ * a given property in a ScriptableObject. To create a property without
+ * special getter or setter side effects, use
+ * <code>defineProperty(String,int)</code>.
+ *
+ * If <code>setter</code> is null, the attribute READONLY is added to
+ * the given attributes.<p>
+ *
+ * Several forms of getters or setters are allowed. In all cases the
+ * type of the value parameter can be any one of the following types:
+ * Object, String, boolean, Scriptable, byte, short, int, long, float,
+ * or double. The runtime will perform appropriate conversions based
+ * upon the type of the parameter (see description in FunctionObject).
+ * The first forms are nonstatic methods of the class referred to
+ * by 'this':
+ * <pre>
+ * Object getFoo();
+ * void setFoo(SomeType value);</pre>
+ * Next are static methods that may be of any class; the object whose
+ * property is being accessed is passed in as an extra argument:
+ * <pre>
+ * static Object getFoo(Scriptable obj);
+ * static void setFoo(Scriptable obj, SomeType value);</pre>
+ * Finally, it is possible to delegate to another object entirely using
+ * the <code>delegateTo</code> parameter. In this case the methods are
+ * nonstatic methods of the class delegated to, and the object whose
+ * property is being accessed is passed in as an extra argument:
+ * <pre>
+ * Object getFoo(Scriptable obj);
+ * void setFoo(Scriptable obj, SomeType value);</pre>
+ *
+ * @param propertyName the name of the property to define.
+ * @param delegateTo an object to call the getter and setter methods on,
+ * or null, depending on the form used above.
+ * @param getter the method to invoke to get the value of the property
+ * @param setter the method to invoke to set the value of the property
+ * @param attributes the attributes of the JavaScript property
+ */
+ public void defineProperty(String propertyName, Object delegateTo,
+ Method getter, Method setter, int attributes)
+ {
+ MemberBox getterBox = null;
+ if (getter != null) {
+ getterBox = new MemberBox(getter);
+
+ boolean delegatedForm;
+ if (!Modifier.isStatic(getter.getModifiers())) {
+ delegatedForm = (delegateTo != null);
+ getterBox.delegateTo = delegateTo;
+ } else {
+ delegatedForm = true;
+ // Ignore delegateTo for static getter but store
+ // non-null delegateTo indicator.
+ getterBox.delegateTo = Void.TYPE;
+ }
+
+ String errorId = null;
+ Class<?>[] parmTypes = getter.getParameterTypes();
+ if (parmTypes.length == 0) {
+ if (delegatedForm) {
+ errorId = "msg.obj.getter.parms";
+ }
+ } else if (parmTypes.length == 1) {
+ Object argType = parmTypes[0];
+ // Allow ScriptableObject for compatibility
+ if (!(argType == ScriptRuntime.ScriptableClass ||
+ argType == ScriptRuntime.ScriptableObjectClass))
+ {
+ errorId = "msg.bad.getter.parms";
+ } else if (!delegatedForm) {
+ errorId = "msg.bad.getter.parms";
+ }
+ } else {
+ errorId = "msg.bad.getter.parms";
+ }
+ if (errorId != null) {
+ throw Context.reportRuntimeError1(errorId, getter.toString());
+ }
+ }
+
+ MemberBox setterBox = null;
+ if (setter != null) {
+ if (setter.getReturnType() != Void.TYPE)
+ throw Context.reportRuntimeError1("msg.setter.return",
+ setter.toString());
+
+ setterBox = new MemberBox(setter);
+
+ boolean delegatedForm;
+ if (!Modifier.isStatic(setter.getModifiers())) {
+ delegatedForm = (delegateTo != null);
+ setterBox.delegateTo = delegateTo;
+ } else {
+ delegatedForm = true;
+ // Ignore delegateTo for static setter but store
+ // non-null delegateTo indicator.
+ setterBox.delegateTo = Void.TYPE;
+ }
+
+ String errorId = null;
+ Class<?>[] parmTypes = setter.getParameterTypes();
+ if (parmTypes.length == 1) {
+ if (delegatedForm) {
+ errorId = "msg.setter2.expected";
+ }
+ } else if (parmTypes.length == 2) {
+ Object argType = parmTypes[0];
+ // Allow ScriptableObject for compatibility
+ if (!(argType == ScriptRuntime.ScriptableClass ||
+ argType == ScriptRuntime.ScriptableObjectClass))
+ {
+ errorId = "msg.setter2.parms";
+ } else if (!delegatedForm) {
+ errorId = "msg.setter1.parms";
+ }
+ } else {
+ errorId = "msg.setter.parms";
+ }
+ if (errorId != null) {
+ throw Context.reportRuntimeError1(errorId, setter.toString());
+ }
+ }
+
+ GetterSlot gslot = (GetterSlot)getSlot(propertyName, 0,
+ SLOT_MODIFY_GETTER_SETTER);
+ gslot.setAttributes(attributes);
+ gslot.getter = getterBox;
+ gslot.setter = setterBox;
+ }
+
+ /**
+ * Search for names in a class, adding the resulting methods
+ * as properties.
+ *
+ * <p> Uses reflection to find the methods of the given names. Then
+ * FunctionObjects are constructed from the methods found, and
+ * are added to this object as properties with the given names.
+ *
+ * @param names the names of the Methods to add as function properties
+ * @param clazz the class to search for the Methods
+ * @param attributes the attributes of the new properties
+ * @see org.mozilla.javascript.FunctionObject
+ */
+ public void defineFunctionProperties(String[] names, Class<?> clazz,
+ int attributes)
+ {
+ Method[] methods = FunctionObject.getMethodList(clazz);
+ for (int i=0; i < names.length; i++) {
+ String name = names[i];
+ Method m = FunctionObject.findSingleMethod(methods, name);
+ if (m == null) {
+ throw Context.reportRuntimeError2(
+ "msg.method.not.found", name, clazz.getName());
+ }
+ FunctionObject f = new FunctionObject(name, m, this);
+ defineProperty(name, f, attributes);
+ }
+ }
+
+ /**
+ * Get the Object.prototype property.
+ * See ECMA 15.2.4.
+ */
+ public static Scriptable getObjectPrototype(Scriptable scope) {
+ return getClassPrototype(scope, "Object");
+ }
+
+ /**
+ * Get the Function.prototype property.
+ * See ECMA 15.3.4.
+ */
+ public static Scriptable getFunctionPrototype(Scriptable scope) {
+ return getClassPrototype(scope, "Function");
+ }
+
+ /**
+ * Get the prototype for the named class.
+ *
+ * For example, <code>getClassPrototype(s, "Date")</code> will first
+ * walk up the parent chain to find the outermost scope, then will
+ * search that scope for the Date constructor, and then will
+ * return Date.prototype. If any of the lookups fail, or
+ * the prototype is not a JavaScript object, then null will
+ * be returned.
+ *
+ * @param scope an object in the scope chain
+ * @param className the name of the constructor
+ * @return the prototype for the named class, or null if it
+ * cannot be found.
+ */
+ public static Scriptable getClassPrototype(Scriptable scope,
+ String className)
+ {
+ scope = getTopLevelScope(scope);
+ Object ctor = getProperty(scope, className);
+ Object proto;
+ if (ctor instanceof BaseFunction) {
+ proto = ((BaseFunction)ctor).getPrototypeProperty();
+ } else if (ctor instanceof Scriptable) {
+ Scriptable ctorObj = (Scriptable)ctor;
+ proto = ctorObj.get("prototype", ctorObj);
+ } else {
+ return null;
+ }
+ if (proto instanceof Scriptable) {
+ return (Scriptable)proto;
+ }
+ return null;
+ }
+
+ /**
+ * Get the global scope.
+ *
+ * <p>Walks the parent scope chain to find an object with a null
+ * parent scope (the global object).
+ *
+ * @param obj a JavaScript object
+ * @return the corresponding global scope
+ */
+ public static Scriptable getTopLevelScope(Scriptable obj)
+ {
+ for (;;) {
+ Scriptable parent = obj.getParentScope();
+ if (parent == null) {
+ return obj;
+ }
+ obj = parent;
+ }
+ }
+
+ /**
+ * Seal this object.
+ *
+ * A sealed object may not have properties added or removed. Once
+ * an object is sealed it may not be unsealed.
+ *
+ * @since 1.4R3
+ */
+ public synchronized void sealObject() {
+ if (count >= 0) {
+ // Make sure all LazilyLoadedCtors are initialized before sealing.
+ Slot slot = firstAdded;
+ while (slot != null) {
+ if (slot.value instanceof LazilyLoadedCtor) {
+ LazilyLoadedCtor initializer = (LazilyLoadedCtor) slot.value;
+ try {
+ initializer.init();
+ } finally {
+ slot.value = initializer.getValue();
+ }
+ }
+ slot = slot.orderedNext;
+ }
+ count = ~count;
+ }
+ }
+
+ /**
+ * Return true if this object is sealed.
+ *
+ * It is an error to attempt to add or remove properties to
+ * a sealed object.
+ *
+ * @return true if sealed, false otherwise.
+ * @since 1.4R3
+ */
+ public final boolean isSealed() {
+ return count < 0;
+ }
+
+ private void checkNotSealed(String name, int index)
+ {
+ if (!isSealed())
+ return;
+
+ String str = (name != null) ? name : Integer.toString(index);
+ throw Context.reportRuntimeError1("msg.modify.sealed", str);
+ }
+
+ /**
+ * Gets a named property from an object or any object in its prototype chain.
+ * <p>
+ * Searches the prototype chain for a property named <code>name</code>.
+ * <p>
+ * @param obj a JavaScript object
+ * @param name a property name
+ * @return the value of a property with name <code>name</code> found in
+ * <code>obj</code> or any object in its prototype chain, or
+ * <code>Scriptable.NOT_FOUND</code> if not found
+ * @since 1.5R2
+ */
+ public static Object getProperty(Scriptable obj, String name)
+ {
+ Scriptable start = obj;
+ Object result;
+ do {
+ result = obj.get(name, start);
+ if (result != Scriptable.NOT_FOUND)
+ break;
+ obj = obj.getPrototype();
+ } while (obj != null);
+ return result;
+ }
+
+ /**
+ * Gets an indexed property from an object or any object in its prototype chain.
+ * <p>
+ * Searches the prototype chain for a property with integral index
+ * <code>index</code>. Note that if you wish to look for properties with numerical
+ * but non-integral indicies, you should use getProperty(Scriptable,String) with
+ * the string value of the index.
+ * <p>
+ * @param obj a JavaScript object
+ * @param index an integral index
+ * @return the value of a property with index <code>index</code> found in
+ * <code>obj</code> or any object in its prototype chain, or
+ * <code>Scriptable.NOT_FOUND</code> if not found
+ * @since 1.5R2
+ */
+ public static Object getProperty(Scriptable obj, int index)
+ {
+ Scriptable start = obj;
+ Object result;
+ do {
+ result = obj.get(index, start);
+ if (result != Scriptable.NOT_FOUND)
+ break;
+ obj = obj.getPrototype();
+ } while (obj != null);
+ return result;
+ }
+
+ /**
+ * Returns whether a named property is defined in an object or any object
+ * in its prototype chain.
+ * <p>
+ * Searches the prototype chain for a property named <code>name</code>.
+ * <p>
+ * @param obj a JavaScript object
+ * @param name a property name
+ * @return the true if property was found
+ * @since 1.5R2
+ */
+ public static boolean hasProperty(Scriptable obj, String name)
+ {
+ return null != getBase(obj, name);
+ }
+
+ /**
+ * If hasProperty(obj, name) would return true, then if the property that
+ * was found is compatible with the new property, this method just returns.
+ * If the property is not compatible, then an exception is thrown.
+ *
+ * A property redefinition is incompatible if the first definition was a
+ * const declaration or if this one is. They are compatible only if neither
+ * was const.
+ */
+ public static void redefineProperty(Scriptable obj, String name,
+ boolean isConst)
+ {
+ Scriptable base = getBase(obj, name);
+ if (base == null)
+ return;
+ if (base instanceof ConstProperties) {
+ ConstProperties cp = (ConstProperties)base;
+
+ if (cp.isConst(name))
+ throw Context.reportRuntimeError1("msg.const.redecl", name);
+ }
+ if (isConst)
+ throw Context.reportRuntimeError1("msg.var.redecl", name);
+ }
+ /**
+ * Returns whether an indexed property is defined in an object or any object
+ * in its prototype chain.
+ * <p>
+ * Searches the prototype chain for a property with index <code>index</code>.
+ * <p>
+ * @param obj a JavaScript object
+ * @param index a property index
+ * @return the true if property was found
+ * @since 1.5R2
+ */
+ public static boolean hasProperty(Scriptable obj, int index)
+ {
+ return null != getBase(obj, index);
+ }
+
+ /**
+ * Puts a named property in an object or in an object in its prototype chain.
+ * <p>
+ * Searches for the named property in the prototype chain. If it is found,
+ * the value of the property in <code>obj</code> is changed through a call
+ * to {@link Scriptable#put(String, Scriptable, Object)} on the
+ * prototype passing <code>obj</code> as the <code>start</code> argument.
+ * This allows the prototype to veto the property setting in case the
+ * prototype defines the property with [[ReadOnly]] attribute. If the
+ * property is not found, it is added in <code>obj</code>.
+ * @param obj a JavaScript object
+ * @param name a property name
+ * @param value any JavaScript value accepted by Scriptable.put
+ * @since 1.5R2
+ */
+ public static void putProperty(Scriptable obj, String name, Object value)
+ {
+ Scriptable base = getBase(obj, name);
+ if (base == null)
+ base = obj;
+ base.put(name, obj, value);
+ }
+
+ /**
+ * Puts a named property in an object or in an object in its prototype chain.
+ * <p>
+ * Searches for the named property in the prototype chain. If it is found,
+ * the value of the property in <code>obj</code> is changed through a call
+ * to {@link Scriptable#put(String, Scriptable, Object)} on the
+ * prototype passing <code>obj</code> as the <code>start</code> argument.
+ * This allows the prototype to veto the property setting in case the
+ * prototype defines the property with [[ReadOnly]] attribute. If the
+ * property is not found, it is added in <code>obj</code>.
+ * @param obj a JavaScript object
+ * @param name a property name
+ * @param value any JavaScript value accepted by Scriptable.put
+ * @since 1.5R2
+ */
+ public static void putConstProperty(Scriptable obj, String name, Object value)
+ {
+ Scriptable base = getBase(obj, name);
+ if (base == null)
+ base = obj;
+ if (base instanceof ConstProperties)
+ ((ConstProperties)base).putConst(name, obj, value);
+ }
+
+ /**
+ * Puts an indexed property in an object or in an object in its prototype chain.
+ * <p>
+ * Searches for the indexed property in the prototype chain. If it is found,
+ * the value of the property in <code>obj</code> is changed through a call
+ * to {@link Scriptable#put(int, Scriptable, Object)} on the prototype
+ * passing <code>obj</code> as the <code>start</code> argument. This allows
+ * the prototype to veto the property setting in case the prototype defines
+ * the property with [[ReadOnly]] attribute. If the property is not found,
+ * it is added in <code>obj</code>.
+ * @param obj a JavaScript object
+ * @param index a property index
+ * @param value any JavaScript value accepted by Scriptable.put
+ * @since 1.5R2
+ */
+ public static void putProperty(Scriptable obj, int index, Object value)
+ {
+ Scriptable base = getBase(obj, index);
+ if (base == null)
+ base = obj;
+ base.put(index, obj, value);
+ }
+
+ /**
+ * Removes the property from an object or its prototype chain.
+ * <p>
+ * Searches for a property with <code>name</code> in obj or
+ * its prototype chain. If it is found, the object's delete
+ * method is called.
+ * @param obj a JavaScript object
+ * @param name a property name
+ * @return true if the property doesn't exist or was successfully removed
+ * @since 1.5R2
+ */
+ public static boolean deleteProperty(Scriptable obj, String name)
+ {
+ Scriptable base = getBase(obj, name);
+ if (base == null)
+ return true;
+ base.delete(name);
+ return !base.has(name, obj);
+ }
+
+ /**
+ * Removes the property from an object or its prototype chain.
+ * <p>
+ * Searches for a property with <code>index</code> in obj or
+ * its prototype chain. If it is found, the object's delete
+ * method is called.
+ * @param obj a JavaScript object
+ * @param index a property index
+ * @return true if the property doesn't exist or was successfully removed
+ * @since 1.5R2
+ */
+ public static boolean deleteProperty(Scriptable obj, int index)
+ {
+ Scriptable base = getBase(obj, index);
+ if (base == null)
+ return true;
+ base.delete(index);
+ return !base.has(index, obj);
+ }
+
+ /**
+ * Returns an array of all ids from an object and its prototypes.
+ * <p>
+ * @param obj a JavaScript object
+ * @return an array of all ids from all object in the prototype chain.
+ * If a given id occurs multiple times in the prototype chain,
+ * it will occur only once in this list.
+ * @since 1.5R2
+ */
+ public static Object[] getPropertyIds(Scriptable obj)
+ {
+ if (obj == null) {
+ return ScriptRuntime.emptyArgs;
+ }
+ Object[] result = obj.getIds();
+ ObjToIntMap map = null;
+ for (;;) {
+ obj = obj.getPrototype();
+ if (obj == null) {
+ break;
+ }
+ Object[] ids = obj.getIds();
+ if (ids.length == 0) {
+ continue;
+ }
+ if (map == null) {
+ if (result.length == 0) {
+ result = ids;
+ continue;
+ }
+ map = new ObjToIntMap(result.length + ids.length);
+ for (int i = 0; i != result.length; ++i) {
+ map.intern(result[i]);
+ }
+ result = null; // Allow to GC the result
+ }
+ for (int i = 0; i != ids.length; ++i) {
+ map.intern(ids[i]);
+ }
+ }
+ if (map != null) {
+ result = map.getKeys();
+ }
+ return result;
+ }
+
+ /**
+ * Call a method of an object.
+ * @param obj the JavaScript object
+ * @param methodName the name of the function property
+ * @param args the arguments for the call
+ *
+ * @see Context#getCurrentContext()
+ */
+ public static Object callMethod(Scriptable obj, String methodName,
+ Object[] args)
+ {
+ return callMethod(null, obj, methodName, args);
+ }
+
+ /**
+ * Call a method of an object.
+ * @param cx the Context object associated with the current thread.
+ * @param obj the JavaScript object
+ * @param methodName the name of the function property
+ * @param args the arguments for the call
+ */
+ public static Object callMethod(Context cx, Scriptable obj,
+ String methodName,
+ Object[] args)
+ {
+ Object funObj = getProperty(obj, methodName);
+ if (!(funObj instanceof Function)) {
+ throw ScriptRuntime.notFunctionError(obj, methodName);
+ }
+ Function fun = (Function)funObj;
+ // XXX: What should be the scope when calling funObj?
+ // The following favor scope stored in the object on the assumption
+ // that is more useful especially under dynamic scope setup.
+ // An alternative is to check for dynamic scope flag
+ // and use ScriptableObject.getTopLevelScope(fun) if the flag is not
+ // set. But that require access to Context and messy code
+ // so for now it is not checked.
+ Scriptable scope = ScriptableObject.getTopLevelScope(obj);
+ if (cx != null) {
+ return fun.call(cx, scope, obj, args);
+ } else {
+ return Context.call(null, fun, scope, obj, args);
+ }
+ }
+
+ private static Scriptable getBase(Scriptable obj, String name)
+ {
+ do {
+ if (obj.has(name, obj))
+ break;
+ obj = obj.getPrototype();
+ } while(obj != null);
+ return obj;
+ }
+
+ private static Scriptable getBase(Scriptable obj, int index)
+ {
+ do {
+ if (obj.has(index, obj))
+ break;
+ obj = obj.getPrototype();
+ } while(obj != null);
+ return obj;
+ }
+
+ /**
+ * Get arbitrary application-specific value associated with this object.
+ * @param key key object to select particular value.
+ * @see #associateValue(Object key, Object value)
+ */
+ public final Object getAssociatedValue(Object key)
+ {
+ Map<Object,Object> h = associatedValues;
+ if (h == null)
+ return null;
+ return h.get(key);
+ }
+
+ /**
+ * Get arbitrary application-specific value associated with the top scope
+ * of the given scope.
+ * The method first calls {@link #getTopLevelScope(Scriptable scope)}
+ * and then searches the prototype chain of the top scope for the first
+ * object containing the associated value with the given key.
+ *
+ * @param scope the starting scope.
+ * @param key key object to select particular value.
+ * @see #getAssociatedValue(Object key)
+ */
+ public static Object getTopScopeValue(Scriptable scope, Object key)
+ {
+ scope = ScriptableObject.getTopLevelScope(scope);
+ for (;;) {
+ if (scope instanceof ScriptableObject) {
+ ScriptableObject so = (ScriptableObject)scope;
+ Object value = so.getAssociatedValue(key);
+ if (value != null) {
+ return value;
+ }
+ }
+ scope = scope.getPrototype();
+ if (scope == null) {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Associate arbitrary application-specific value with this object.
+ * Value can only be associated with the given object and key only once.
+ * The method ignores any subsequent attempts to change the already
+ * associated value.
+ * <p> The associated values are not serialized.
+ * @param key key object to select particular value.
+ * @param value the value to associate
+ * @return the passed value if the method is called first time for the
+ * given key or old value for any subsequent calls.
+ * @see #getAssociatedValue(Object key)
+ */
+ public synchronized final Object associateValue(Object key, Object value)
+ {
+ if (value == null) throw new IllegalArgumentException();
+ Map<Object,Object> h = associatedValues;
+ if (h == null) {
+ h = associatedValues;
+ if (h == null) {
+ h = new HashMap<Object,Object>();
+ associatedValues = h;
+ }
+ }
+ return Kit.initHash(h, key, value);
+ }
+
+ private Object getImpl(String name, int index, Scriptable start)
+ {
+ Slot slot = getSlot(name, index, SLOT_QUERY);
+ if (slot == null) {
+ return Scriptable.NOT_FOUND;
+ }
+ if (!(slot instanceof GetterSlot)) {
+ return slot.value;
+ }
+ Object getterObj = ((GetterSlot)slot).getter;
+ if (getterObj != null) {
+ if (getterObj instanceof MemberBox) {
+ MemberBox nativeGetter = (MemberBox)getterObj;
+ Object getterThis;
+ Object[] args;
+ if (nativeGetter.delegateTo == null) {
+ getterThis = start;
+ args = ScriptRuntime.emptyArgs;
+ } else {
+ getterThis = nativeGetter.delegateTo;
+ args = new Object[] { start };
+ }
+ return nativeGetter.invoke(getterThis, args);
+ } else {
+ Function f = (Function)getterObj;
+ Context cx = Context.getContext();
+ return f.call(cx, f.getParentScope(), start,
+ ScriptRuntime.emptyArgs);
+ }
+ }
+ Object value = slot.value;
+ if (value instanceof LazilyLoadedCtor) {
+ LazilyLoadedCtor initializer = (LazilyLoadedCtor)value;
+ try {
+ initializer.init();
+ } finally {
+ value = initializer.getValue();
+ slot.value = value;
+ }
+ }
+ return value;
+ }
+
+ /**
+ *
+ * @param name
+ * @param index
+ * @param start
+ * @param value
+ * @param constFlag EMPTY means normal put. UNINITIALIZED_CONST means
+ * defineConstProperty. READONLY means const initialization expression.
+ * @return false if this != start and no slot was found. true if this == start
+ * or this != start and a READONLY slot was found.
+ */
+ private boolean putImpl(String name, int index, Scriptable start,
+ Object value, int constFlag)
+ {
+ Slot slot;
+ if (this != start) {
+ slot = getSlot(name, index, SLOT_QUERY);
+ if (slot == null) {
+ return false;
+ }
+ } else {
+ checkNotSealed(name, index);
+ // either const hoisted declaration or initialization
+ if (constFlag != EMPTY) {
+ slot = getSlot(name, index, SLOT_MODIFY_CONST);
+ int attr = slot.getAttributes();
+ if ((attr & READONLY) == 0)
+ throw Context.reportRuntimeError1("msg.var.redecl", name);
+ if ((attr & UNINITIALIZED_CONST) != 0) {
+ slot.value = value;
+ // clear the bit on const initialization
+ if (constFlag != UNINITIALIZED_CONST)
+ slot.setAttributes(attr & ~UNINITIALIZED_CONST);
+ }
+ return true;
+ }
+ slot = getSlot(name, index, SLOT_MODIFY);
+ }
+ if ((slot.getAttributes() & READONLY) != 0)
+ return true;
+ if (slot instanceof GetterSlot) {
+ Object setterObj = ((GetterSlot)slot).setter;
+ if (setterObj == null) {
+ if (((GetterSlot)slot).getter != null) {
+ // Based on TC39 ES3.1 Draft of 9-Feb-2009, 8.12.4, step 2,
+ // we should throw a TypeError in this case.
+ throw ScriptRuntime.typeError1("msg.set.prop.no.setter", name);
+ }
+ } else {
+ Context cx = Context.getContext();
+ if (setterObj instanceof MemberBox) {
+ MemberBox nativeSetter = (MemberBox)setterObj;
+ Class<?> pTypes[] = nativeSetter.argTypes;
+ // XXX: cache tag since it is already calculated in
+ // defineProperty ?
+ Class<?> valueType = pTypes[pTypes.length - 1];
+ int tag = FunctionObject.getTypeTag(valueType);
+ Object actualArg = FunctionObject.convertArg(cx, start,
+ value, tag);
+ Object setterThis;
+ Object[] args;
+ if (nativeSetter.delegateTo == null) {
+ setterThis = start;
+ args = new Object[] { actualArg };
+ } else {
+ setterThis = nativeSetter.delegateTo;
+ args = new Object[] { start, actualArg };
+ }
+ nativeSetter.invoke(setterThis, args);
+ } else {
+ Function f = (Function)setterObj;
+ f.call(cx, f.getParentScope(), start,
+ new Object[] { value });
+ }
+ return true;
+ }
+ }
+ if (this == start) {
+ slot.value = value;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private Slot findAttributeSlot(String name, int index, int accessType)
+ {
+ Slot slot = getSlot(name, index, accessType);
+ if (slot == null) {
+ String str = (name != null ? name : Integer.toString(index));
+ throw Context.reportRuntimeError1("msg.prop.not.found", str);
+ }
+ return slot;
+ }
+
+ /**
+ * Locate the slot with given name or index.
+ *
+ * @param name property name or null if slot holds spare array index.
+ * @param index index or 0 if slot holds property name.
+ */
+ private Slot getSlot(String name, int index, int accessType)
+ {
+ Slot slot;
+
+ // Query last access cache and check that it was not deleted.
+ lastAccessCheck:
+ {
+ slot = lastAccess;
+ if (name != null) {
+ if (name != slot.name)
+ break lastAccessCheck;
+ // No String.equals here as successful slot search update
+ // name object with fresh reference of the same string.
+ } else {
+ if (slot.name != null || index != slot.indexOrHash)
+ break lastAccessCheck;
+ }
+
+ if (slot.wasDeleted)
+ break lastAccessCheck;
+
+ if (accessType == SLOT_MODIFY_GETTER_SETTER &&
+ !(slot instanceof GetterSlot))
+ break lastAccessCheck;
+
+ return slot;
+ }
+
+ slot = accessSlot(name, index, accessType);
+ if (slot != null) {
+ // Update the cache
+ lastAccess = slot;
+ }
+ return slot;
+ }
+
+ private Slot accessSlot(String name, int index, int accessType)
+ {
+ int indexOrHash = (name != null ? name.hashCode() : index);
+
+ if (accessType == SLOT_QUERY ||
+ accessType == SLOT_MODIFY ||
+ accessType == SLOT_MODIFY_CONST ||
+ accessType == SLOT_MODIFY_GETTER_SETTER)
+ {
+ // Check the hashtable without using synchronization
+
+ Slot[] slotsLocalRef = slots; // Get stable local reference
+ if (slotsLocalRef == null) {
+ if (accessType == SLOT_QUERY)
+ return null;
+ } else {
+ int tableSize = slotsLocalRef.length;
+ int slotIndex = getSlotIndex(tableSize, indexOrHash);
+ Slot slot = slotsLocalRef[slotIndex];
+ while (slot != null) {
+ String sname = slot.name;
+ if (sname != null) {
+ if (sname == name)
+ break;
+ if (name != null && indexOrHash == slot.indexOrHash) {
+ if (name.equals(sname)) {
+ // This will avoid calling String.equals when
+ // slot is accessed with same string object
+ // next time.
+ slot.name = name;
+ break;
+ }
+ }
+ } else if (name == null &&
+ indexOrHash == slot.indexOrHash) {
+ break;
+ }
+ slot = slot.next;
+ }
+ if (accessType == SLOT_QUERY) {
+ return slot;
+ } else if (accessType == SLOT_MODIFY) {
+ if (slot != null)
+ return slot;
+ } else if (accessType == SLOT_MODIFY_GETTER_SETTER) {
+ if (slot instanceof GetterSlot)
+ return slot;
+ } else if (accessType == SLOT_MODIFY_CONST) {
+ if (slot != null)
+ return slot;
+ }
+ }
+
+ // A new slot has to be inserted or the old has to be replaced
+ // by GetterSlot. Time to synchronize.
+
+ synchronized (this) {
+ // Refresh local ref if another thread triggered grow
+ slotsLocalRef = slots;
+ int insertPos;
+ if (count == 0) {
+ // Always throw away old slots if any on empty insert
+ slotsLocalRef = new Slot[5];
+ slots = slotsLocalRef;
+ insertPos = getSlotIndex(slotsLocalRef.length, indexOrHash);
+ } else {
+ int tableSize = slotsLocalRef.length;
+ insertPos = getSlotIndex(tableSize, indexOrHash);
+ Slot prev = slotsLocalRef[insertPos];
+ Slot slot = prev;
+ while (slot != null) {
+ if (slot.indexOrHash == indexOrHash &&
+ (slot.name == name ||
+ (name != null && name.equals(slot.name))))
+ {
+ break;
+ }
+ prev = slot;
+ slot = slot.next;
+ }
+
+ if (slot != null) {
+ // Another thread just added a slot with same
+ // name/index before this one entered synchronized
+ // block. This is a race in application code and
+ // probably indicates bug there. But for the hashtable
+ // implementation it is harmless with the only
+ // complication is the need to replace the added slot
+ // if we need GetterSlot and the old one is not.
+ if (accessType == SLOT_MODIFY_GETTER_SETTER &&
+ !(slot instanceof GetterSlot))
+ {
+ GetterSlot newSlot = new GetterSlot(name,
+ indexOrHash, slot.getAttributes());
+ newSlot.value = slot.value;
+ newSlot.next = slot.next;
+ // add new slot to linked list
+ if (lastAdded != null)
+ lastAdded.orderedNext = newSlot;
+ if (firstAdded == null)
+ firstAdded = newSlot;
+ lastAdded = newSlot;
+ // add new slot to hash table
+ if (prev == slot) {
+ slotsLocalRef[insertPos] = newSlot;
+ } else {
+ prev.next = newSlot;
+ }
+ // other housekeeping
+ slot.wasDeleted = true;
+ slot.value = null;
+ slot.name = null;
+ if (slot == lastAccess) {
+ lastAccess = REMOVED;
+ }
+ slot = newSlot;
+ } else if (accessType == SLOT_MODIFY_CONST) {
+ return null;
+ }
+ return slot;
+ }
+
+ // Check if the table is not too full before inserting.
+ if (4 * (count + 1) > 3 * slotsLocalRef.length) {
+ slotsLocalRef = new Slot[slotsLocalRef.length * 2 + 1];
+ copyTable(slots, slotsLocalRef, count);
+ slots = slotsLocalRef;
+ insertPos = getSlotIndex(slotsLocalRef.length,
+ indexOrHash);
+ }
+ }
+
+ Slot newSlot = (accessType == SLOT_MODIFY_GETTER_SETTER
+ ? new GetterSlot(name, indexOrHash, 0)
+ : new Slot(name, indexOrHash, 0));
+ if (accessType == SLOT_MODIFY_CONST)
+ newSlot.setAttributes(CONST);
+ ++count;
+ // add new slot to linked list
+ if (lastAdded != null)
+ lastAdded.orderedNext = newSlot;
+ if (firstAdded == null)
+ firstAdded = newSlot;
+ lastAdded = newSlot;
+ // add new slot to hash table, return it
+ addKnownAbsentSlot(slotsLocalRef, newSlot, insertPos);
+ return newSlot;
+ }
+
+ } else if (accessType == SLOT_REMOVE) {
+ synchronized (this) {
+ Slot[] slotsLocalRef = slots;
+ if (count != 0) {
+ int tableSize = slots.length;
+ int slotIndex = getSlotIndex(tableSize, indexOrHash);
+ Slot prev = slotsLocalRef[slotIndex];
+ Slot slot = prev;
+ while (slot != null) {
+ if (slot.indexOrHash == indexOrHash &&
+ (slot.name == name ||
+ (name != null && name.equals(slot.name))))
+ {
+ break;
+ }
+ prev = slot;
+ slot = slot.next;
+ }
+ if (slot != null && (slot.getAttributes() & PERMANENT) == 0) {
+ count--;
+ // remove slot from hash table
+ if (prev == slot) {
+ slotsLocalRef[slotIndex] = slot.next;
+ } else {
+ prev.next = slot.next;
+ }
+ // Mark the slot as removed. It is still referenced
+ // from the order-added linked list, but will be
+ // cleaned up later
+ slot.wasDeleted = true;
+ slot.value = null;
+ slot.name = null;
+ if (slot == lastAccess) {
+ lastAccess = REMOVED;
+ }
+ }
+ }
+ }
+ return null;
+
+ } else {
+ throw Kit.codeBug();
+ }
+ }
+
+ private static int getSlotIndex(int tableSize, int indexOrHash)
+ {
+ return (indexOrHash & 0x7fffffff) % tableSize;
+ }
+
+ // Must be inside synchronized (this)
+ private static void copyTable(Slot[] slots, Slot[] newSlots, int count)
+ {
+ if (count == 0) throw Kit.codeBug();
+
+ int tableSize = newSlots.length;
+ int i = slots.length;
+ for (;;) {
+ --i;
+ Slot slot = slots[i];
+ while (slot != null) {
+ int insertPos = getSlotIndex(tableSize, slot.indexOrHash);
+ Slot next = slot.next;
+ addKnownAbsentSlot(newSlots, slot, insertPos);
+ slot.next = null;
+ slot = next;
+ if (--count == 0)
+ return;
+ }
+ }
+ }
+
+ /**
+ * Add slot with keys that are known to absent from the table.
+ * This is an optimization to use when inserting into empty table,
+ * after table growth or during deserialization.
+ */
+ private static void addKnownAbsentSlot(Slot[] slots, Slot slot,
+ int insertPos)
+ {
+ if (slots[insertPos] == null) {
+ slots[insertPos] = slot;
+ } else {
+ Slot prev = slots[insertPos];
+ while (prev.next != null) {
+ prev = prev.next;
+ }
+ prev.next = slot;
+ }
+ }
+
+ Object[] getIds(boolean getAll) {
+ Slot[] s = slots;
+ Object[] a = ScriptRuntime.emptyArgs;
+ if (s == null)
+ return a;
+ int c = 0;
+ Slot slot = firstAdded;
+ while (slot != null && slot.wasDeleted) {
+ // as long as we're traversing the order-added linked list,
+ // remove deleted slots
+ slot = slot.orderedNext;
+ }
+ firstAdded = slot;
+ if (slot != null) {
+ for (;;) {
+ if (getAll || (slot.getAttributes() & DONTENUM) == 0) {
+ if (c == 0)
+ a = new Object[s.length];
+ a[c++] = slot.name != null
+ ? (Object) slot.name
+ : Integer.valueOf(slot.indexOrHash);
+ }
+ Slot next = slot.orderedNext;
+ while (next != null && next.wasDeleted) {
+ // remove deleted slots
+ next = next.orderedNext;
+ }
+ slot.orderedNext = next;
+ if (next == null) {
+ break;
+ }
+ slot = next;
+ }
+ }
+ lastAdded = slot;
+ if (c == a.length)
+ return a;
+ Object[] result = new Object[c];
+ System.arraycopy(a, 0, result, 0, c);
+ return result;
+ }
+
+ private synchronized void writeObject(ObjectOutputStream out)
+ throws IOException
+ {
+ out.defaultWriteObject();
+ int objectsCount = count;
+ if (objectsCount < 0) {
+ // "this" was sealed
+ objectsCount = ~objectsCount;
+ }
+ if (objectsCount == 0) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(slots.length);
+ Slot slot = firstAdded;
+ while (slot != null && slot.wasDeleted) {
+ // as long as we're traversing the order-added linked list,
+ // remove deleted slots
+ slot = slot.orderedNext;
+ }
+ firstAdded = slot;
+ while (slot != null) {
+ out.writeObject(slot);
+ Slot next = slot.orderedNext;
+ while (next != null && next.wasDeleted) {
+ // remove deleted slots
+ next = next.orderedNext;
+ }
+ slot.orderedNext = next;
+ slot = next;
+ }
+ }
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+ lastAccess = REMOVED;
+
+ int tableSize = in.readInt();
+ if (tableSize != 0) {
+ slots = new Slot[tableSize];
+ int objectsCount = count;
+ if (objectsCount < 0) {
+ // "this" was sealed
+ objectsCount = ~objectsCount;
+ }
+ Slot prev = null;
+ for (int i=0; i != objectsCount; ++i) {
+ lastAdded = (Slot)in.readObject();
+ if (i==0) {
+ firstAdded = lastAdded;
+ } else {
+ prev.orderedNext = lastAdded;
+ }
+ int slotIndex = getSlotIndex(tableSize, lastAdded.indexOrHash);
+ addKnownAbsentSlot(slots, lastAdded, slotIndex);
+ prev = lastAdded;
+ }
+ }
+ }
+
+}
diff --git a/src/org/mozilla/javascript/SecureCaller.java b/src/org/mozilla/javascript/SecureCaller.java
new file mode 100644
index 0000000..3a25290
--- /dev/null
+++ b/src/org/mozilla/javascript/SecureCaller.java
@@ -0,0 +1,196 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.SoftReference;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.SecureClassLoader;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * @author Attila Szegedi
+ */
+public abstract class SecureCaller
+{
+ private static final byte[] secureCallerImplBytecode = loadBytecode();
+
+ // We're storing a CodeSource -> (ClassLoader -> SecureRenderer), since we
+ // need to have one renderer per class loader. We're using weak hash maps
+ // and soft references all the way, since we don't want to interfere with
+ // cleanup of either CodeSource or ClassLoader objects.
+ private static final Map<CodeSource,Map<ClassLoader,SoftReference<SecureCaller>>>
+ callers =
+ new WeakHashMap<CodeSource,Map<ClassLoader,SoftReference<SecureCaller>>>();
+
+ public abstract Object call(Callable callable, Context cx,
+ Scriptable scope, Scriptable thisObj, Object[] args);
+
+ /**
+ * Call the specified callable using a protection domain belonging to the
+ * specified code source.
+ */
+ static Object callSecurely(final CodeSource codeSource, Callable callable,
+ Context cx, Scriptable scope, Scriptable thisObj, Object[] args)
+ {
+ final Thread thread = Thread.currentThread();
+ // Run in doPrivileged as we might be checked for "getClassLoader"
+ // runtime permission
+ final ClassLoader classLoader = (ClassLoader)AccessController.doPrivileged(
+ new PrivilegedAction<Object>() {
+ public Object run() {
+ return thread.getContextClassLoader();
+ }
+ });
+ Map<ClassLoader,SoftReference<SecureCaller>> classLoaderMap;
+ synchronized(callers)
+ {
+ classLoaderMap = callers.get(codeSource);
+ if(classLoaderMap == null)
+ {
+ classLoaderMap = new WeakHashMap<ClassLoader,SoftReference<SecureCaller>>();
+ callers.put(codeSource, classLoaderMap);
+ }
+ }
+ SecureCaller caller;
+ synchronized(classLoaderMap)
+ {
+ SoftReference<SecureCaller> ref = classLoaderMap.get(classLoader);
+ if (ref != null) {
+ caller = ref.get();
+ } else {
+ caller = null;
+ }
+ if (caller == null) {
+ try
+ {
+ // Run in doPrivileged as we'll be checked for
+ // "createClassLoader" runtime permission
+ caller = (SecureCaller)AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Object>()
+ {
+ public Object run() throws Exception
+ {
+ ClassLoader effectiveClassLoader;
+ Class<?> thisClass = getClass();
+ if(classLoader.loadClass(thisClass.getName()) != thisClass) {
+ effectiveClassLoader = thisClass.getClassLoader();
+ } else {
+ effectiveClassLoader = classLoader;
+ }
+ SecureClassLoaderImpl secCl =
+ new SecureClassLoaderImpl(effectiveClassLoader);
+ Class<?> c = secCl.defineAndLinkClass(
+ SecureCaller.class.getName() + "Impl",
+ secureCallerImplBytecode, codeSource);
+ return c.newInstance();
+ }
+ });
+ classLoaderMap.put(classLoader, new SoftReference<SecureCaller>(caller));
+ }
+ catch(PrivilegedActionException ex)
+ {
+ throw new UndeclaredThrowableException(ex.getCause());
+ }
+ }
+ }
+ return caller.call(callable, cx, scope, thisObj, args);
+ }
+
+ private static class SecureClassLoaderImpl extends SecureClassLoader
+ {
+ SecureClassLoaderImpl(ClassLoader parent)
+ {
+ super(parent);
+ }
+
+ Class<?> defineAndLinkClass(String name, byte[] bytes, CodeSource cs)
+ {
+ Class<?> cl = defineClass(name, bytes, 0, bytes.length, cs);
+ resolveClass(cl);
+ return cl;
+ }
+ }
+
+ private static byte[] loadBytecode()
+ {
+ return (byte[])AccessController.doPrivileged(new PrivilegedAction<Object>()
+ {
+ public Object run()
+ {
+ return loadBytecodePrivileged();
+ }
+ });
+ }
+
+ private static byte[] loadBytecodePrivileged()
+ {
+ URL url = SecureCaller.class.getResource("SecureCallerImpl.clazz");
+ try
+ {
+ InputStream in = url.openStream();
+ try
+ {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ for(;;)
+ {
+ int r = in.read();
+ if(r == -1)
+ {
+ return bout.toByteArray();
+ }
+ bout.write(r);
+ }
+ }
+ finally
+ {
+ in.close();
+ }
+ }
+ catch(IOException e)
+ {
+ throw new UndeclaredThrowableException(e);
+ }
+ }
+}
diff --git a/src/org/mozilla/javascript/SecurityController.java b/src/org/mozilla/javascript/SecurityController.java
new file mode 100644
index 0000000..bd625d9
--- /dev/null
+++ b/src/org/mozilla/javascript/SecurityController.java
@@ -0,0 +1,211 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * This class describes the support needed to implement security.
+ * <p>
+ * Three main pieces of functionality are required to implement
+ * security for JavaScript. First, it must be possible to define
+ * classes with an associated security domain. (This security
+ * domain may be any object incorporating notion of access
+ * restrictions that has meaning to an embedding; for a client-side
+ * JavaScript embedding this would typically be
+ * java.security.ProtectionDomain or similar object depending on an
+ * origin URL and/or a digital certificate.)
+ * Next it must be possible to get a security domain object that
+ * allows a particular action only if all security domains
+ * associated with code on the current Java stack allows it. And
+ * finally, it must be possible to execute script code with
+ * associated security domain injected into Java stack.
+ * <p>
+ * These three pieces of functionality are encapsulated in the
+ * SecurityController class.
+ *
+ * @see org.mozilla.javascript.Context#setSecurityController(SecurityController)
+ * @see java.lang.ClassLoader
+ * @since 1.5 Release 4
+ */
+public abstract class SecurityController
+{
+ private static SecurityController global;
+
+// The method must NOT be public or protected
+ static SecurityController global()
+ {
+ return global;
+ }
+
+ /**
+ * Check if global {@link SecurityController} was already installed.
+ * @see #initGlobal(SecurityController controller)
+ */
+ public static boolean hasGlobal()
+ {
+ return global != null;
+ }
+
+ /**
+ * Initialize global controller that will be used for all
+ * security-related operations. The global controller takes precedence
+ * over already installed {@link Context}-specific controllers and cause
+ * any subsequent call to
+ * {@link Context#setSecurityController(SecurityController)}
+ * to throw an exception.
+ * <p>
+ * The method can only be called once.
+ *
+ * @see #hasGlobal()
+ */
+ public static void initGlobal(SecurityController controller)
+ {
+ if (controller == null) throw new IllegalArgumentException();
+ if (global != null) {
+ throw new SecurityException("Cannot overwrite already installed global SecurityController");
+ }
+ global = controller;
+ }
+
+ /**
+ * Get class loader-like object that can be used
+ * to define classes with the given security context.
+ * @param parentLoader parent class loader to delegate search for classes
+ * not defined by the class loader itself
+ * @param securityDomain some object specifying the security
+ * context of the code that is defined by the returned class loader.
+ */
+ public abstract GeneratedClassLoader createClassLoader(
+ ClassLoader parentLoader, Object securityDomain);
+
+ /**
+ * Create {@link GeneratedClassLoader} with restrictions imposed by
+ * staticDomain and all current stack frames.
+ * The method uses the SecurityController instance associated with the
+ * current {@link Context} to construct proper dynamic domain and create
+ * corresponding class loader.
+ * <par>
+ * If no SecurityController is associated with the current {@link Context} ,
+ * the method calls {@link Context#createClassLoader(ClassLoader parent)}.
+ *
+ * @param parent parent class loader. If null,
+ * {@link Context#getApplicationClassLoader()} will be used.
+ * @param staticDomain static security domain.
+ */
+ public static GeneratedClassLoader createLoader(
+ ClassLoader parent, Object staticDomain)
+ {
+ Context cx = Context.getContext();
+ if (parent == null) {
+ parent = cx.getApplicationClassLoader();
+ }
+ SecurityController sc = cx.getSecurityController();
+ GeneratedClassLoader loader;
+ if (sc == null) {
+ loader = cx.createClassLoader(parent);
+ } else {
+ Object dynamicDomain = sc.getDynamicSecurityDomain(staticDomain);
+ loader = sc.createClassLoader(parent, dynamicDomain);
+ }
+ return loader;
+ }
+
+ public static Class<?> getStaticSecurityDomainClass() {
+ SecurityController sc = Context.getContext().getSecurityController();
+ return sc == null ? null : sc.getStaticSecurityDomainClassInternal();
+ }
+
+ public Class<?> getStaticSecurityDomainClassInternal()
+ {
+ return null;
+ }
+
+ /**
+ * Get dynamic security domain that allows an action only if it is allowed
+ * by the current Java stack and <i>securityDomain</i>. If
+ * <i>securityDomain</i> is null, return domain representing permissions
+ * allowed by the current stack.
+ */
+ public abstract Object getDynamicSecurityDomain(Object securityDomain);
+
+ /**
+ * Call {@link
+ * Callable#call(Context cx, Scriptable scope, Scriptable thisObj,
+ * Object[] args)}
+ * of <i>callable</i> under restricted security domain where an action is
+ * allowed only if it is allowed according to the Java stack on the
+ * moment of the <i>execWithDomain</i> call and <i>securityDomain</i>.
+ * Any call to {@link #getDynamicSecurityDomain(Object)} during
+ * execution of <tt>callable.call(cx, scope, thisObj, args)</tt>
+ * should return a domain incorporate restrictions imposed by
+ * <i>securityDomain</i> and Java stack on the moment of callWithDomain
+ * invocation.
+ * <p>
+ * The method should always be overridden, it is not declared abstract
+ * for compatibility reasons.
+ */
+ public Object callWithDomain(Object securityDomain, Context cx,
+ final Callable callable, Scriptable scope,
+ final Scriptable thisObj, final Object[] args)
+ {
+ return execWithDomain(cx, scope, new Script()
+ {
+ public Object exec(Context cx, Scriptable scope)
+ {
+ return callable.call(cx, scope, thisObj, args);
+ }
+
+ }, securityDomain);
+ }
+
+ /**
+ * @deprecated The application should not override this method and instead
+ * override
+ * {@link #callWithDomain(Object securityDomain, Context cx, Callable callable, Scriptable scope, Scriptable thisObj, Object[] args)}.
+ */
+ public Object execWithDomain(Context cx, Scriptable scope,
+ Script script, Object securityDomain)
+ {
+ throw new IllegalStateException("callWithDomain should be overridden");
+ }
+
+
+}
diff --git a/src/org/mozilla/javascript/SecurityUtilities.java b/src/org/mozilla/javascript/SecurityUtilities.java
new file mode 100644
index 0000000..cabe3db
--- /dev/null
+++ b/src/org/mozilla/javascript/SecurityUtilities.java
@@ -0,0 +1,80 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+
+/**
+ * @author Attila Szegedi
+ */
+public class SecurityUtilities
+{
+ /**
+ * Retrieves a system property within a privileged block. Use it only when
+ * the property is used from within Rhino code and is not passed out of it.
+ * @param name the name of the system property
+ * @return the value of the system property
+ */
+ public static String getSystemProperty(final String name)
+ {
+ return (String)AccessController.doPrivileged(
+ new PrivilegedAction<Object>()
+ {
+ public Object run()
+ {
+ return System.getProperty(name);
+ }
+ });
+ }
+
+ public static ProtectionDomain getProtectionDomain(final Class<?> clazz)
+ {
+ return (ProtectionDomain)AccessController.doPrivileged(
+ new PrivilegedAction<Object>()
+ {
+ public Object run()
+ {
+ return clazz.getProtectionDomain();
+ }
+ });
+ }
+}
diff --git a/src/org/mozilla/javascript/SpecialRef.java b/src/org/mozilla/javascript/SpecialRef.java
new file mode 100644
index 0000000..b50b556
--- /dev/null
+++ b/src/org/mozilla/javascript/SpecialRef.java
@@ -0,0 +1,155 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov, igor at fastmail.fm
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+class SpecialRef extends Ref
+{
+ static final long serialVersionUID = -7521596632456797847L;
+
+ private static final int SPECIAL_NONE = 0;
+ private static final int SPECIAL_PROTO = 1;
+ private static final int SPECIAL_PARENT = 2;
+
+ private Scriptable target;
+ private int type;
+ private String name;
+
+ private SpecialRef(Scriptable target, int type, String name)
+ {
+ this.target = target;
+ this.type = type;
+ this.name = name;
+ }
+
+ static Ref createSpecial(Context cx, Object object, String name)
+ {
+ Scriptable target = ScriptRuntime.toObjectOrNull(cx, object);
+ if (target == null) {
+ throw ScriptRuntime.undefReadError(object, name);
+ }
+
+ int type;
+ if (name.equals("__proto__")) {
+ type = SPECIAL_PROTO;
+ } else if (name.equals("__parent__")) {
+ type = SPECIAL_PARENT;
+ } else {
+ throw new IllegalArgumentException(name);
+ }
+
+ if (!cx.hasFeature(Context.FEATURE_PARENT_PROTO_PROPERTIES)) {
+ // Clear special after checking for valid name!
+ type = SPECIAL_NONE;
+ }
+
+ return new SpecialRef(target, type, name);
+ }
+
+ @Override
+ public Object get(Context cx)
+ {
+ switch (type) {
+ case SPECIAL_NONE:
+ return ScriptRuntime.getObjectProp(target, name, cx);
+ case SPECIAL_PROTO:
+ return target.getPrototype();
+ case SPECIAL_PARENT:
+ return target.getParentScope();
+ default:
+ throw Kit.codeBug();
+ }
+ }
+
+ @Override
+ public Object set(Context cx, Object value)
+ {
+ switch (type) {
+ case SPECIAL_NONE:
+ return ScriptRuntime.setObjectProp(target, name, value, cx);
+ case SPECIAL_PROTO:
+ case SPECIAL_PARENT:
+ {
+ Scriptable obj = ScriptRuntime.toObjectOrNull(cx, value);
+ if (obj != null) {
+ // Check that obj does not contain on its prototype/scope
+ // chain to prevent cycles
+ Scriptable search = obj;
+ do {
+ if (search == target) {
+ throw Context.reportRuntimeError1(
+ "msg.cyclic.value", name);
+ }
+ if (type == SPECIAL_PROTO) {
+ search = search.getPrototype();
+ } else {
+ search = search.getParentScope();
+ }
+ } while (search != null);
+ }
+ if (type == SPECIAL_PROTO) {
+ target.setPrototype(obj);
+ } else {
+ target.setParentScope(obj);
+ }
+ return obj;
+ }
+ default:
+ throw Kit.codeBug();
+ }
+ }
+
+ @Override
+ public boolean has(Context cx)
+ {
+ if (type == SPECIAL_NONE) {
+ return ScriptRuntime.hasObjectElem(target, name, cx);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean delete(Context cx)
+ {
+ if (type == SPECIAL_NONE) {
+ return ScriptRuntime.deleteObjectElem(target, name, cx);
+ }
+ return false;
+ }
+}
+
diff --git a/src/org/mozilla/javascript/Synchronizer.java b/src/org/mozilla/javascript/Synchronizer.java
new file mode 100644
index 0000000..dbf4cef
--- /dev/null
+++ b/src/org/mozilla/javascript/Synchronizer.java
@@ -0,0 +1,82 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Delegator.java, released
+ * Sep 27, 2000.
+ *
+ * The Initial Developer of the Original Code is
+ * Matthias Radestock. <matthias at sorted.org>.
+ * Portions created by the Initial Developer are Copyright (C) 2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * This class provides support for implementing Java-style synchronized
+ * methods in Javascript.
+ *
+ * Synchronized functions are created from ordinary Javascript
+ * functions by the <code>Synchronizer</code> constructor, e.g.
+ * <code>new Packages.org.mozilla.javascript.Synchronizer(fun)</code>.
+ * The resulting object is a function that establishes an exclusive
+ * lock on the <code>this</code> object of its invocation.
+ *
+ * The Rhino shell provides a short-cut for the creation of
+ * synchronized methods: <code>sync(fun)</code> has the same effect as
+ * calling the above constructor.
+ *
+ * @see org.mozilla.javascript.Delegator
+ * @author Matthias Radestock
+ */
+
+public class Synchronizer extends Delegator {
+
+ /**
+ * Create a new synchronized function from an existing one.
+ *
+ * @param obj the existing function
+ */
+ public Synchronizer(Scriptable obj) {
+ super(obj);
+ }
+
+ /**
+ * @see org.mozilla.javascript.Function#call
+ */
+ @Override
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ synchronized(thisObj instanceof Wrapper ? ((Wrapper)thisObj).unwrap() : thisObj) {
+ return ((Function)obj).call(cx,scope,thisObj,args);
+ }
+ }
+}
diff --git a/src/org/mozilla/javascript/Token.java b/src/org/mozilla/javascript/Token.java
new file mode 100644
index 0000000..e822f8f
--- /dev/null
+++ b/src/org/mozilla/javascript/Token.java
@@ -0,0 +1,434 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Roger Lawrence
+ * Mike McCabe
+ * Igor Bukanov
+ * Bob Jervis
+ * Milen Nankov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * This class implements the JavaScript scanner.
+ *
+ * It is based on the C source files jsscan.c and jsscan.h
+ * in the jsref package.
+ *
+ * @see org.mozilla.javascript.Parser
+ *
+ * @author Mike McCabe
+ * @author Brendan Eich
+ */
+
+public class Token
+{
+
+ // debug flags
+ public static final boolean printTrees = false;
+ static final boolean printICode = false;
+ static final boolean printNames = printTrees || printICode;
+
+ /**
+ * Token types. These values correspond to JSTokenType values in
+ * jsscan.c.
+ */
+
+ public final static int
+ // start enum
+ ERROR = -1, // well-known as the only code < EOF
+ EOF = 0, // end of file token - (not EOF_CHAR)
+ EOL = 1, // end of line
+
+ // Interpreter reuses the following as bytecodes
+ FIRST_BYTECODE_TOKEN = 2,
+
+ ENTERWITH = 2,
+ LEAVEWITH = 3,
+ RETURN = 4,
+ GOTO = 5,
+ IFEQ = 6,
+ IFNE = 7,
+ SETNAME = 8,
+ BITOR = 9,
+ BITXOR = 10,
+ BITAND = 11,
+ EQ = 12,
+ NE = 13,
+ LT = 14,
+ LE = 15,
+ GT = 16,
+ GE = 17,
+ LSH = 18,
+ RSH = 19,
+ URSH = 20,
+ ADD = 21,
+ SUB = 22,
+ MUL = 23,
+ DIV = 24,
+ MOD = 25,
+ NOT = 26,
+ BITNOT = 27,
+ POS = 28,
+ NEG = 29,
+ NEW = 30,
+ DELPROP = 31,
+ TYPEOF = 32,
+ GETPROP = 33,
+ GETPROPNOWARN = 34,
+ SETPROP = 35,
+ GETELEM = 36,
+ SETELEM = 37,
+ CALL = 38,
+ NAME = 39,
+ NUMBER = 40,
+ STRING = 41,
+ NULL = 42,
+ THIS = 43,
+ FALSE = 44,
+ TRUE = 45,
+ SHEQ = 46, // shallow equality (===)
+ SHNE = 47, // shallow inequality (!==)
+ REGEXP = 48,
+ BINDNAME = 49,
+ THROW = 50,
+ RETHROW = 51, // rethrow caught exception: catch (e if ) use it
+ IN = 52,
+ INSTANCEOF = 53,
+ LOCAL_LOAD = 54,
+ GETVAR = 55,
+ SETVAR = 56,
+ CATCH_SCOPE = 57,
+ ENUM_INIT_KEYS = 58,
+ ENUM_INIT_VALUES = 59,
+ ENUM_INIT_ARRAY= 60,
+ ENUM_NEXT = 61,
+ ENUM_ID = 62,
+ THISFN = 63,
+ RETURN_RESULT = 64, // to return previously stored return result
+ ARRAYLIT = 65, // array literal
+ OBJECTLIT = 66, // object literal
+ GET_REF = 67, // *reference
+ SET_REF = 68, // *reference = something
+ DEL_REF = 69, // delete reference
+ REF_CALL = 70, // f(args) = something or f(args)++
+ REF_SPECIAL = 71, // reference for special properties like __proto
+ YIELD = 72, // JS 1.7 yield pseudo keyword
+
+ // For XML support:
+ DEFAULTNAMESPACE = 73, // default xml namespace =
+ ESCXMLATTR = 74,
+ ESCXMLTEXT = 75,
+ REF_MEMBER = 76, // Reference for x. at y, x..y etc.
+ REF_NS_MEMBER = 77, // Reference for x.ns::y, x..ns::y etc.
+ REF_NAME = 78, // Reference for @y, @[y] etc.
+ REF_NS_NAME = 79; // Reference for ns::y, @ns::y@[y] etc.
+
+ // End of interpreter bytecodes
+ public final static int
+ LAST_BYTECODE_TOKEN = REF_NS_NAME,
+
+ TRY = 80,
+ SEMI = 81, // semicolon
+ LB = 82, // left and right brackets
+ RB = 83,
+ LC = 84, // left and right curlies (braces)
+ RC = 85,
+ LP = 86, // left and right parentheses
+ RP = 87,
+ COMMA = 88, // comma operator
+
+ ASSIGN = 89, // simple assignment (=)
+ ASSIGN_BITOR = 90, // |=
+ ASSIGN_BITXOR = 91, // ^=
+ ASSIGN_BITAND = 92, // |=
+ ASSIGN_LSH = 93, // <<=
+ ASSIGN_RSH = 94, // >>=
+ ASSIGN_URSH = 95, // >>>=
+ ASSIGN_ADD = 96, // +=
+ ASSIGN_SUB = 97, // -=
+ ASSIGN_MUL = 98, // *=
+ ASSIGN_DIV = 99, // /=
+ ASSIGN_MOD = 100; // %=
+
+ public final static int
+ FIRST_ASSIGN = ASSIGN,
+ LAST_ASSIGN = ASSIGN_MOD,
+
+ HOOK = 101, // conditional (?:)
+ COLON = 102,
+ OR = 103, // logical or (||)
+ AND = 104, // logical and (&&)
+ INC = 105, // increment/decrement (++ --)
+ DEC = 106,
+ DOT = 107, // member operator (.)
+ FUNCTION = 108, // function keyword
+ EXPORT = 109, // export keyword
+ IMPORT = 110, // import keyword
+ IF = 111, // if keyword
+ ELSE = 112, // else keyword
+ SWITCH = 113, // switch keyword
+ CASE = 114, // case keyword
+ DEFAULT = 115, // default keyword
+ WHILE = 116, // while keyword
+ DO = 117, // do keyword
+ FOR = 118, // for keyword
+ BREAK = 119, // break keyword
+ CONTINUE = 120, // continue keyword
+ VAR = 121, // var keyword
+ WITH = 122, // with keyword
+ CATCH = 123, // catch keyword
+ FINALLY = 124, // finally keyword
+ VOID = 125, // void keyword
+ RESERVED = 126, // reserved keywords
+
+ EMPTY = 127,
+
+ /* types used for the parse tree - these never get returned
+ * by the scanner.
+ */
+
+ BLOCK = 128, // statement block
+ LABEL = 129, // label
+ TARGET = 130,
+ LOOP = 131,
+ EXPR_VOID = 132, // expression statement in functions
+ EXPR_RESULT = 133, // expression statement in scripts
+ JSR = 134,
+ SCRIPT = 135, // top-level node for entire script
+ TYPEOFNAME = 136, // for typeof(simple-name)
+ USE_STACK = 137,
+ SETPROP_OP = 138, // x.y op= something
+ SETELEM_OP = 139, // x[y] op= something
+ LOCAL_BLOCK = 140,
+ SET_REF_OP = 141, // *reference op= something
+
+ // For XML support:
+ DOTDOT = 142, // member operator (..)
+ COLONCOLON = 143, // namespace::name
+ XML = 144, // XML type
+ DOTQUERY = 145, // .() -- e.g., x.emps.emp.(name == "terry")
+ XMLATTR = 146, // @
+ XMLEND = 147,
+
+ // Optimizer-only-tokens
+ TO_OBJECT = 148,
+ TO_DOUBLE = 149,
+
+ GET = 150, // JS 1.5 get pseudo keyword
+ SET = 151, // JS 1.5 set pseudo keyword
+ LET = 152, // JS 1.7 let pseudo keyword
+ CONST = 153,
+ SETCONST = 154,
+ SETCONSTVAR = 155,
+ ARRAYCOMP = 156, // array comprehension
+ LETEXPR = 157,
+ WITHEXPR = 158,
+ DEBUGGER = 159,
+ LAST_TOKEN = 159;
+
+ public static String name(int token)
+ {
+ if (!printNames) {
+ return String.valueOf(token);
+ }
+ switch (token) {
+ case ERROR: return "ERROR";
+ case EOF: return "EOF";
+ case EOL: return "EOL";
+ case ENTERWITH: return "ENTERWITH";
+ case LEAVEWITH: return "LEAVEWITH";
+ case RETURN: return "RETURN";
+ case GOTO: return "GOTO";
+ case IFEQ: return "IFEQ";
+ case IFNE: return "IFNE";
+ case SETNAME: return "SETNAME";
+ case BITOR: return "BITOR";
+ case BITXOR: return "BITXOR";
+ case BITAND: return "BITAND";
+ case EQ: return "EQ";
+ case NE: return "NE";
+ case LT: return "LT";
+ case LE: return "LE";
+ case GT: return "GT";
+ case GE: return "GE";
+ case LSH: return "LSH";
+ case RSH: return "RSH";
+ case URSH: return "URSH";
+ case ADD: return "ADD";
+ case SUB: return "SUB";
+ case MUL: return "MUL";
+ case DIV: return "DIV";
+ case MOD: return "MOD";
+ case NOT: return "NOT";
+ case BITNOT: return "BITNOT";
+ case POS: return "POS";
+ case NEG: return "NEG";
+ case NEW: return "NEW";
+ case DELPROP: return "DELPROP";
+ case TYPEOF: return "TYPEOF";
+ case GETPROP: return "GETPROP";
+ case GETPROPNOWARN: return "GETPROPNOWARN";
+ case SETPROP: return "SETPROP";
+ case GETELEM: return "GETELEM";
+ case SETELEM: return "SETELEM";
+ case CALL: return "CALL";
+ case NAME: return "NAME";
+ case NUMBER: return "NUMBER";
+ case STRING: return "STRING";
+ case NULL: return "NULL";
+ case THIS: return "THIS";
+ case FALSE: return "FALSE";
+ case TRUE: return "TRUE";
+ case SHEQ: return "SHEQ";
+ case SHNE: return "SHNE";
+ case REGEXP: return "OBJECT";
+ case BINDNAME: return "BINDNAME";
+ case THROW: return "THROW";
+ case RETHROW: return "RETHROW";
+ case IN: return "IN";
+ case INSTANCEOF: return "INSTANCEOF";
+ case LOCAL_LOAD: return "LOCAL_LOAD";
+ case GETVAR: return "GETVAR";
+ case SETVAR: return "SETVAR";
+ case CATCH_SCOPE: return "CATCH_SCOPE";
+ case ENUM_INIT_KEYS: return "ENUM_INIT_KEYS";
+ case ENUM_INIT_VALUES:return "ENUM_INIT_VALUES";
+ case ENUM_INIT_ARRAY: return "ENUM_INIT_ARRAY";
+ case ENUM_NEXT: return "ENUM_NEXT";
+ case ENUM_ID: return "ENUM_ID";
+ case THISFN: return "THISFN";
+ case RETURN_RESULT: return "RETURN_RESULT";
+ case ARRAYLIT: return "ARRAYLIT";
+ case OBJECTLIT: return "OBJECTLIT";
+ case GET_REF: return "GET_REF";
+ case SET_REF: return "SET_REF";
+ case DEL_REF: return "DEL_REF";
+ case REF_CALL: return "REF_CALL";
+ case REF_SPECIAL: return "REF_SPECIAL";
+ case DEFAULTNAMESPACE:return "DEFAULTNAMESPACE";
+ case ESCXMLTEXT: return "ESCXMLTEXT";
+ case ESCXMLATTR: return "ESCXMLATTR";
+ case REF_MEMBER: return "REF_MEMBER";
+ case REF_NS_MEMBER: return "REF_NS_MEMBER";
+ case REF_NAME: return "REF_NAME";
+ case REF_NS_NAME: return "REF_NS_NAME";
+ case TRY: return "TRY";
+ case SEMI: return "SEMI";
+ case LB: return "LB";
+ case RB: return "RB";
+ case LC: return "LC";
+ case RC: return "RC";
+ case LP: return "LP";
+ case RP: return "RP";
+ case COMMA: return "COMMA";
+ case ASSIGN: return "ASSIGN";
+ case ASSIGN_BITOR: return "ASSIGN_BITOR";
+ case ASSIGN_BITXOR: return "ASSIGN_BITXOR";
+ case ASSIGN_BITAND: return "ASSIGN_BITAND";
+ case ASSIGN_LSH: return "ASSIGN_LSH";
+ case ASSIGN_RSH: return "ASSIGN_RSH";
+ case ASSIGN_URSH: return "ASSIGN_URSH";
+ case ASSIGN_ADD: return "ASSIGN_ADD";
+ case ASSIGN_SUB: return "ASSIGN_SUB";
+ case ASSIGN_MUL: return "ASSIGN_MUL";
+ case ASSIGN_DIV: return "ASSIGN_DIV";
+ case ASSIGN_MOD: return "ASSIGN_MOD";
+ case HOOK: return "HOOK";
+ case COLON: return "COLON";
+ case OR: return "OR";
+ case AND: return "AND";
+ case INC: return "INC";
+ case DEC: return "DEC";
+ case DOT: return "DOT";
+ case FUNCTION: return "FUNCTION";
+ case EXPORT: return "EXPORT";
+ case IMPORT: return "IMPORT";
+ case IF: return "IF";
+ case ELSE: return "ELSE";
+ case SWITCH: return "SWITCH";
+ case CASE: return "CASE";
+ case DEFAULT: return "DEFAULT";
+ case WHILE: return "WHILE";
+ case DO: return "DO";
+ case FOR: return "FOR";
+ case BREAK: return "BREAK";
+ case CONTINUE: return "CONTINUE";
+ case VAR: return "VAR";
+ case WITH: return "WITH";
+ case CATCH: return "CATCH";
+ case FINALLY: return "FINALLY";
+ case VOID: return "VOID";
+ case RESERVED: return "RESERVED";
+ case EMPTY: return "EMPTY";
+ case BLOCK: return "BLOCK";
+ case LABEL: return "LABEL";
+ case TARGET: return "TARGET";
+ case LOOP: return "LOOP";
+ case EXPR_VOID: return "EXPR_VOID";
+ case EXPR_RESULT: return "EXPR_RESULT";
+ case JSR: return "JSR";
+ case SCRIPT: return "SCRIPT";
+ case TYPEOFNAME: return "TYPEOFNAME";
+ case USE_STACK: return "USE_STACK";
+ case SETPROP_OP: return "SETPROP_OP";
+ case SETELEM_OP: return "SETELEM_OP";
+ case LOCAL_BLOCK: return "LOCAL_BLOCK";
+ case SET_REF_OP: return "SET_REF_OP";
+ case DOTDOT: return "DOTDOT";
+ case COLONCOLON: return "COLONCOLON";
+ case XML: return "XML";
+ case DOTQUERY: return "DOTQUERY";
+ case XMLATTR: return "XMLATTR";
+ case XMLEND: return "XMLEND";
+ case TO_OBJECT: return "TO_OBJECT";
+ case TO_DOUBLE: return "TO_DOUBLE";
+ case GET: return "GET";
+ case SET: return "SET";
+ case LET: return "LET";
+ case YIELD: return "YIELD";
+ case CONST: return "CONST";
+ case SETCONST: return "SETCONST";
+ case ARRAYCOMP: return "ARRAYCOMP";
+ case WITHEXPR: return "WITHEXPR";
+ case LETEXPR: return "LETEXPR";
+ case DEBUGGER: return "DEBUGGER";
+ }
+
+ // Token without name
+ throw new IllegalStateException(String.valueOf(token));
+ }
+}
diff --git a/src/org/mozilla/javascript/TokenStream.java b/src/org/mozilla/javascript/TokenStream.java
new file mode 100644
index 0000000..7ea027d
--- /dev/null
+++ b/src/org/mozilla/javascript/TokenStream.java
@@ -0,0 +1,1463 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Roger Lawrence
+ * Mike McCabe
+ * Igor Bukanov
+ * Ethan Hugg
+ * Bob Jervis
+ * Terry Lucas
+ * Milen Nankov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.*;
+
+/**
+ * This class implements the JavaScript scanner.
+ *
+ * It is based on the C source files jsscan.c and jsscan.h
+ * in the jsref package.
+ *
+ * @see org.mozilla.javascript.Parser
+ *
+ * @author Mike McCabe
+ * @author Brendan Eich
+ */
+
+class TokenStream
+{
+ /*
+ * For chars - because we need something out-of-range
+ * to check. (And checking EOF by exception is annoying.)
+ * Note distinction from EOF token type!
+ */
+ private final static int
+ EOF_CHAR = -1;
+
+ TokenStream(Parser parser, Reader sourceReader, String sourceString,
+ int lineno)
+ {
+ this.parser = parser;
+ this.lineno = lineno;
+ if (sourceReader != null) {
+ if (sourceString != null) Kit.codeBug();
+ this.sourceReader = sourceReader;
+ this.sourceBuffer = new char[512];
+ this.sourceEnd = 0;
+ } else {
+ if (sourceString == null) Kit.codeBug();
+ this.sourceString = sourceString;
+ this.sourceEnd = sourceString.length();
+ }
+ this.sourceCursor = 0;
+ }
+
+ /* This function uses the cached op, string and number fields in
+ * TokenStream; if getToken has been called since the passed token
+ * was scanned, the op or string printed may be incorrect.
+ */
+ String tokenToString(int token)
+ {
+ if (Token.printTrees) {
+ String name = Token.name(token);
+
+ switch (token) {
+ case Token.STRING:
+ case Token.REGEXP:
+ case Token.NAME:
+ return name + " `" + this.string + "'";
+
+ case Token.NUMBER:
+ return "NUMBER " + this.number;
+ }
+
+ return name;
+ }
+ return "";
+ }
+
+ static boolean isKeyword(String s)
+ {
+ return Token.EOF != stringToKeyword(s);
+ }
+
+ private static int stringToKeyword(String name)
+ {
+// #string_id_map#
+// The following assumes that Token.EOF == 0
+ final int
+ Id_break = Token.BREAK,
+ Id_case = Token.CASE,
+ Id_continue = Token.CONTINUE,
+ Id_default = Token.DEFAULT,
+ Id_delete = Token.DELPROP,
+ Id_do = Token.DO,
+ Id_else = Token.ELSE,
+ Id_export = Token.EXPORT,
+ Id_false = Token.FALSE,
+ Id_for = Token.FOR,
+ Id_function = Token.FUNCTION,
+ Id_if = Token.IF,
+ Id_in = Token.IN,
+ Id_let = Token.LET,
+ Id_new = Token.NEW,
+ Id_null = Token.NULL,
+ Id_return = Token.RETURN,
+ Id_switch = Token.SWITCH,
+ Id_this = Token.THIS,
+ Id_true = Token.TRUE,
+ Id_typeof = Token.TYPEOF,
+ Id_var = Token.VAR,
+ Id_void = Token.VOID,
+ Id_while = Token.WHILE,
+ Id_with = Token.WITH,
+ Id_yield = Token.YIELD,
+
+ // the following are #ifdef RESERVE_JAVA_KEYWORDS in jsscan.c
+ Id_abstract = Token.RESERVED,
+ Id_boolean = Token.RESERVED,
+ Id_byte = Token.RESERVED,
+ Id_catch = Token.CATCH,
+ Id_char = Token.RESERVED,
+ Id_class = Token.RESERVED,
+ Id_const = Token.CONST,
+ Id_debugger = Token.DEBUGGER,
+ Id_double = Token.RESERVED,
+ Id_enum = Token.RESERVED,
+ Id_extends = Token.RESERVED,
+ Id_final = Token.RESERVED,
+ Id_finally = Token.FINALLY,
+ Id_float = Token.RESERVED,
+ Id_goto = Token.RESERVED,
+ Id_implements = Token.RESERVED,
+ Id_import = Token.IMPORT,
+ Id_instanceof = Token.INSTANCEOF,
+ Id_int = Token.RESERVED,
+ Id_interface = Token.RESERVED,
+ Id_long = Token.RESERVED,
+ Id_native = Token.RESERVED,
+ Id_package = Token.RESERVED,
+ Id_private = Token.RESERVED,
+ Id_protected = Token.RESERVED,
+ Id_public = Token.RESERVED,
+ Id_short = Token.RESERVED,
+ Id_static = Token.RESERVED,
+ Id_super = Token.RESERVED,
+ Id_synchronized = Token.RESERVED,
+ Id_throw = Token.THROW,
+ Id_throws = Token.RESERVED,
+ Id_transient = Token.RESERVED,
+ Id_try = Token.TRY,
+ Id_volatile = Token.RESERVED;
+
+ int id;
+ String s = name;
+// #generated# Last update: 2007-04-18 13:53:30 PDT
+ L0: { id = 0; String X = null; int c;
+ L: switch (s.length()) {
+ case 2: c=s.charAt(1);
+ if (c=='f') { if (s.charAt(0)=='i') {id=Id_if; break L0;} }
+ else if (c=='n') { if (s.charAt(0)=='i') {id=Id_in; break L0;} }
+ else if (c=='o') { if (s.charAt(0)=='d') {id=Id_do; break L0;} }
+ break L;
+ case 3: switch (s.charAt(0)) {
+ case 'f': if (s.charAt(2)=='r' && s.charAt(1)=='o') {id=Id_for; break L0;} break L;
+ case 'i': if (s.charAt(2)=='t' && s.charAt(1)=='n') {id=Id_int; break L0;} break L;
+ case 'l': if (s.charAt(2)=='t' && s.charAt(1)=='e') {id=Id_let; break L0;} break L;
+ case 'n': if (s.charAt(2)=='w' && s.charAt(1)=='e') {id=Id_new; break L0;} break L;
+ case 't': if (s.charAt(2)=='y' && s.charAt(1)=='r') {id=Id_try; break L0;} break L;
+ case 'v': if (s.charAt(2)=='r' && s.charAt(1)=='a') {id=Id_var; break L0;} break L;
+ } break L;
+ case 4: switch (s.charAt(0)) {
+ case 'b': X="byte";id=Id_byte; break L;
+ case 'c': c=s.charAt(3);
+ if (c=='e') { if (s.charAt(2)=='s' && s.charAt(1)=='a') {id=Id_case; break L0;} }
+ else if (c=='r') { if (s.charAt(2)=='a' && s.charAt(1)=='h') {id=Id_char; break L0;} }
+ break L;
+ case 'e': c=s.charAt(3);
+ if (c=='e') { if (s.charAt(2)=='s' && s.charAt(1)=='l') {id=Id_else; break L0;} }
+ else if (c=='m') { if (s.charAt(2)=='u' && s.charAt(1)=='n') {id=Id_enum; break L0;} }
+ break L;
+ case 'g': X="goto";id=Id_goto; break L;
+ case 'l': X="long";id=Id_long; break L;
+ case 'n': X="null";id=Id_null; break L;
+ case 't': c=s.charAt(3);
+ if (c=='e') { if (s.charAt(2)=='u' && s.charAt(1)=='r') {id=Id_true; break L0;} }
+ else if (c=='s') { if (s.charAt(2)=='i' && s.charAt(1)=='h') {id=Id_this; break L0;} }
+ break L;
+ case 'v': X="void";id=Id_void; break L;
+ case 'w': X="with";id=Id_with; break L;
+ } break L;
+ case 5: switch (s.charAt(2)) {
+ case 'a': X="class";id=Id_class; break L;
+ case 'e': c=s.charAt(0);
+ if (c=='b') { X="break";id=Id_break; }
+ else if (c=='y') { X="yield";id=Id_yield; }
+ break L;
+ case 'i': X="while";id=Id_while; break L;
+ case 'l': X="false";id=Id_false; break L;
+ case 'n': c=s.charAt(0);
+ if (c=='c') { X="const";id=Id_const; }
+ else if (c=='f') { X="final";id=Id_final; }
+ break L;
+ case 'o': c=s.charAt(0);
+ if (c=='f') { X="float";id=Id_float; }
+ else if (c=='s') { X="short";id=Id_short; }
+ break L;
+ case 'p': X="super";id=Id_super; break L;
+ case 'r': X="throw";id=Id_throw; break L;
+ case 't': X="catch";id=Id_catch; break L;
+ } break L;
+ case 6: switch (s.charAt(1)) {
+ case 'a': X="native";id=Id_native; break L;
+ case 'e': c=s.charAt(0);
+ if (c=='d') { X="delete";id=Id_delete; }
+ else if (c=='r') { X="return";id=Id_return; }
+ break L;
+ case 'h': X="throws";id=Id_throws; break L;
+ case 'm': X="import";id=Id_import; break L;
+ case 'o': X="double";id=Id_double; break L;
+ case 't': X="static";id=Id_static; break L;
+ case 'u': X="public";id=Id_public; break L;
+ case 'w': X="switch";id=Id_switch; break L;
+ case 'x': X="export";id=Id_export; break L;
+ case 'y': X="typeof";id=Id_typeof; break L;
+ } break L;
+ case 7: switch (s.charAt(1)) {
+ case 'a': X="package";id=Id_package; break L;
+ case 'e': X="default";id=Id_default; break L;
+ case 'i': X="finally";id=Id_finally; break L;
+ case 'o': X="boolean";id=Id_boolean; break L;
+ case 'r': X="private";id=Id_private; break L;
+ case 'x': X="extends";id=Id_extends; break L;
+ } break L;
+ case 8: switch (s.charAt(0)) {
+ case 'a': X="abstract";id=Id_abstract; break L;
+ case 'c': X="continue";id=Id_continue; break L;
+ case 'd': X="debugger";id=Id_debugger; break L;
+ case 'f': X="function";id=Id_function; break L;
+ case 'v': X="volatile";id=Id_volatile; break L;
+ } break L;
+ case 9: c=s.charAt(0);
+ if (c=='i') { X="interface";id=Id_interface; }
+ else if (c=='p') { X="protected";id=Id_protected; }
+ else if (c=='t') { X="transient";id=Id_transient; }
+ break L;
+ case 10: c=s.charAt(1);
+ if (c=='m') { X="implements";id=Id_implements; }
+ else if (c=='n') { X="instanceof";id=Id_instanceof; }
+ break L;
+ case 12: X="synchronized";id=Id_synchronized; break L;
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ }
+// #/generated#
+// #/string_id_map#
+ if (id == 0) { return Token.EOF; }
+ return id & 0xff;
+ }
+
+ final int getLineno() { return lineno; }
+
+ final String getString() { return string; }
+
+ final double getNumber() { return number; }
+
+ final boolean eof() { return hitEOF; }
+
+ final int getToken() throws IOException
+ {
+ int c;
+
+ retry:
+ for (;;) {
+ // Eat whitespace, possibly sensitive to newlines.
+ for (;;) {
+ c = getChar();
+ if (c == EOF_CHAR) {
+ return Token.EOF;
+ } else if (c == '\n') {
+ dirtyLine = false;
+ return Token.EOL;
+ } else if (!isJSSpace(c)) {
+ if (c != '-') {
+ dirtyLine = true;
+ }
+ break;
+ }
+ }
+
+ if (c == '@') return Token.XMLATTR;
+
+ // identifier/keyword/instanceof?
+ // watch out for starting with a <backslash>
+ boolean identifierStart;
+ boolean isUnicodeEscapeStart = false;
+ if (c == '\\') {
+ c = getChar();
+ if (c == 'u') {
+ identifierStart = true;
+ isUnicodeEscapeStart = true;
+ stringBufferTop = 0;
+ } else {
+ identifierStart = false;
+ ungetChar(c);
+ c = '\\';
+ }
+ } else {
+ identifierStart = Character.isJavaIdentifierStart((char)c);
+ if (identifierStart) {
+ stringBufferTop = 0;
+ addToString(c);
+ }
+ }
+
+ if (identifierStart) {
+ boolean containsEscape = isUnicodeEscapeStart;
+ for (;;) {
+ if (isUnicodeEscapeStart) {
+ // strictly speaking we should probably push-back
+ // all the bad characters if the <backslash>uXXXX
+ // sequence is malformed. But since there isn't a
+ // correct context(is there?) for a bad Unicode
+ // escape sequence in an identifier, we can report
+ // an error here.
+ int escapeVal = 0;
+ for (int i = 0; i != 4; ++i) {
+ c = getChar();
+ escapeVal = Kit.xDigitToInt(c, escapeVal);
+ // Next check takes care about c < 0 and bad escape
+ if (escapeVal < 0) { break; }
+ }
+ if (escapeVal < 0) {
+ parser.addError("msg.invalid.escape");
+ return Token.ERROR;
+ }
+ addToString(escapeVal);
+ isUnicodeEscapeStart = false;
+ } else {
+ c = getChar();
+ if (c == '\\') {
+ c = getChar();
+ if (c == 'u') {
+ isUnicodeEscapeStart = true;
+ containsEscape = true;
+ } else {
+ parser.addError("msg.illegal.character");
+ return Token.ERROR;
+ }
+ } else {
+ if (c == EOF_CHAR
+ || !Character.isJavaIdentifierPart((char)c))
+ {
+ break;
+ }
+ addToString(c);
+ }
+ }
+ }
+ ungetChar(c);
+
+ String str = getStringFromBuffer();
+ if (!containsEscape) {
+ // OPT we shouldn't have to make a string (object!) to
+ // check if it's a keyword.
+
+ // Return the corresponding token if it's a keyword
+ int result = stringToKeyword(str);
+ if (result != Token.EOF) {
+ if ((result == Token.LET || result == Token.YIELD) &&
+ parser.compilerEnv.getLanguageVersion()
+ < Context.VERSION_1_7)
+ {
+ // LET and YIELD are tokens only in 1.7 and later
+ string = result == Token.LET ? "let" : "yield";
+ result = Token.NAME;
+ }
+ if (result != Token.RESERVED) {
+ return result;
+ } else if (!parser.compilerEnv.
+ isReservedKeywordAsIdentifier())
+ {
+ return result;
+ } else {
+ // If implementation permits to use future reserved
+ // keywords in violation with the EcmaScript,
+ // treat it as name but issue warning
+ parser.addWarning("msg.reserved.keyword", str);
+ }
+ }
+ }
+ this.string = (String)allStrings.intern(str);
+ return Token.NAME;
+ }
+
+ // is it a number?
+ if (isDigit(c) || (c == '.' && isDigit(peekChar()))) {
+
+ stringBufferTop = 0;
+ int base = 10;
+
+ if (c == '0') {
+ c = getChar();
+ if (c == 'x' || c == 'X') {
+ base = 16;
+ c = getChar();
+ } else if (isDigit(c)) {
+ base = 8;
+ } else {
+ addToString('0');
+ }
+ }
+
+ if (base == 16) {
+ while (0 <= Kit.xDigitToInt(c, 0)) {
+ addToString(c);
+ c = getChar();
+ }
+ } else {
+ while ('0' <= c && c <= '9') {
+ /*
+ * We permit 08 and 09 as decimal numbers, which
+ * makes our behavior a superset of the ECMA
+ * numeric grammar. We might not always be so
+ * permissive, so we warn about it.
+ */
+ if (base == 8 && c >= '8') {
+ parser.addWarning("msg.bad.octal.literal",
+ c == '8' ? "8" : "9");
+ base = 10;
+ }
+ addToString(c);
+ c = getChar();
+ }
+ }
+
+ boolean isInteger = true;
+
+ if (base == 10 && (c == '.' || c == 'e' || c == 'E')) {
+ isInteger = false;
+ if (c == '.') {
+ do {
+ addToString(c);
+ c = getChar();
+ } while (isDigit(c));
+ }
+ if (c == 'e' || c == 'E') {
+ addToString(c);
+ c = getChar();
+ if (c == '+' || c == '-') {
+ addToString(c);
+ c = getChar();
+ }
+ if (!isDigit(c)) {
+ parser.addError("msg.missing.exponent");
+ return Token.ERROR;
+ }
+ do {
+ addToString(c);
+ c = getChar();
+ } while (isDigit(c));
+ }
+ }
+ ungetChar(c);
+ String numString = getStringFromBuffer();
+
+ double dval;
+ if (base == 10 && !isInteger) {
+ try {
+ // Use Java conversion to number from string...
+ dval = Double.valueOf(numString).doubleValue();
+ }
+ catch (NumberFormatException ex) {
+ parser.addError("msg.caught.nfe");
+ return Token.ERROR;
+ }
+ } else {
+ dval = ScriptRuntime.stringToNumber(numString, 0, base);
+ }
+
+ this.number = dval;
+ return Token.NUMBER;
+ }
+
+ // is it a string?
+ if (c == '"' || c == '\'') {
+ // We attempt to accumulate a string the fast way, by
+ // building it directly out of the reader. But if there
+ // are any escaped characters in the string, we revert to
+ // building it out of a StringBuffer.
+
+ int quoteChar = c;
+ stringBufferTop = 0;
+
+ c = getChar();
+ strLoop: while (c != quoteChar) {
+ if (c == '\n' || c == EOF_CHAR) {
+ ungetChar(c);
+ parser.addError("msg.unterminated.string.lit");
+ return Token.ERROR;
+ }
+
+ if (c == '\\') {
+ // We've hit an escaped character
+ int escapeVal;
+
+ c = getChar();
+ switch (c) {
+ case 'b': c = '\b'; break;
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+
+ // \v a late addition to the ECMA spec,
+ // it is not in Java, so use 0xb
+ case 'v': c = 0xb; break;
+
+ case 'u':
+ // Get 4 hex digits; if the u escape is not
+ // followed by 4 hex digits, use 'u' + the
+ // literal character sequence that follows.
+ int escapeStart = stringBufferTop;
+ addToString('u');
+ escapeVal = 0;
+ for (int i = 0; i != 4; ++i) {
+ c = getChar();
+ escapeVal = Kit.xDigitToInt(c, escapeVal);
+ if (escapeVal < 0) {
+ continue strLoop;
+ }
+ addToString(c);
+ }
+ // prepare for replace of stored 'u' sequence
+ // by escape value
+ stringBufferTop = escapeStart;
+ c = escapeVal;
+ break;
+ case 'x':
+ // Get 2 hex digits, defaulting to 'x'+literal
+ // sequence, as above.
+ c = getChar();
+ escapeVal = Kit.xDigitToInt(c, 0);
+ if (escapeVal < 0) {
+ addToString('x');
+ continue strLoop;
+ } else {
+ int c1 = c;
+ c = getChar();
+ escapeVal = Kit.xDigitToInt(c, escapeVal);
+ if (escapeVal < 0) {
+ addToString('x');
+ addToString(c1);
+ continue strLoop;
+ } else {
+ // got 2 hex digits
+ c = escapeVal;
+ }
+ }
+ break;
+
+ case '\n':
+ // Remove line terminator after escape to follow
+ // SpiderMonkey and C/C++
+ c = getChar();
+ continue strLoop;
+
+ default:
+ if ('0' <= c && c < '8') {
+ int val = c - '0';
+ c = getChar();
+ if ('0' <= c && c < '8') {
+ val = 8 * val + c - '0';
+ c = getChar();
+ if ('0' <= c && c < '8' && val <= 037) {
+ // c is 3rd char of octal sequence only
+ // if the resulting val <= 0377
+ val = 8 * val + c - '0';
+ c = getChar();
+ }
+ }
+ ungetChar(c);
+ c = val;
+ }
+ }
+ }
+ addToString(c);
+ c = getChar();
+ }
+
+ String str = getStringFromBuffer();
+ this.string = (String)allStrings.intern(str);
+ return Token.STRING;
+ }
+
+ switch (c) {
+ case ';': return Token.SEMI;
+ case '[': return Token.LB;
+ case ']': return Token.RB;
+ case '{': return Token.LC;
+ case '}': return Token.RC;
+ case '(': return Token.LP;
+ case ')': return Token.RP;
+ case ',': return Token.COMMA;
+ case '?': return Token.HOOK;
+ case ':':
+ if (matchChar(':')) {
+ return Token.COLONCOLON;
+ } else {
+ return Token.COLON;
+ }
+ case '.':
+ if (matchChar('.')) {
+ return Token.DOTDOT;
+ } else if (matchChar('(')) {
+ return Token.DOTQUERY;
+ } else {
+ return Token.DOT;
+ }
+
+ case '|':
+ if (matchChar('|')) {
+ return Token.OR;
+ } else if (matchChar('=')) {
+ return Token.ASSIGN_BITOR;
+ } else {
+ return Token.BITOR;
+ }
+
+ case '^':
+ if (matchChar('=')) {
+ return Token.ASSIGN_BITXOR;
+ } else {
+ return Token.BITXOR;
+ }
+
+ case '&':
+ if (matchChar('&')) {
+ return Token.AND;
+ } else if (matchChar('=')) {
+ return Token.ASSIGN_BITAND;
+ } else {
+ return Token.BITAND;
+ }
+
+ case '=':
+ if (matchChar('=')) {
+ if (matchChar('='))
+ return Token.SHEQ;
+ else
+ return Token.EQ;
+ } else {
+ return Token.ASSIGN;
+ }
+
+ case '!':
+ if (matchChar('=')) {
+ if (matchChar('='))
+ return Token.SHNE;
+ else
+ return Token.NE;
+ } else {
+ return Token.NOT;
+ }
+
+ case '<':
+ /* NB:treat HTML begin-comment as comment-till-eol */
+ if (matchChar('!')) {
+ if (matchChar('-')) {
+ if (matchChar('-')) {
+ skipLine();
+ continue retry;
+ }
+ ungetCharIgnoreLineEnd('-');
+ }
+ ungetCharIgnoreLineEnd('!');
+ }
+ if (matchChar('<')) {
+ if (matchChar('=')) {
+ return Token.ASSIGN_LSH;
+ } else {
+ return Token.LSH;
+ }
+ } else {
+ if (matchChar('=')) {
+ return Token.LE;
+ } else {
+ return Token.LT;
+ }
+ }
+
+ case '>':
+ if (matchChar('>')) {
+ if (matchChar('>')) {
+ if (matchChar('=')) {
+ return Token.ASSIGN_URSH;
+ } else {
+ return Token.URSH;
+ }
+ } else {
+ if (matchChar('=')) {
+ return Token.ASSIGN_RSH;
+ } else {
+ return Token.RSH;
+ }
+ }
+ } else {
+ if (matchChar('=')) {
+ return Token.GE;
+ } else {
+ return Token.GT;
+ }
+ }
+
+ case '*':
+ if (matchChar('=')) {
+ return Token.ASSIGN_MUL;
+ } else {
+ return Token.MUL;
+ }
+
+ case '/':
+ // is it a // comment?
+ if (matchChar('/')) {
+ skipLine();
+ continue retry;
+ }
+ if (matchChar('*')) {
+ boolean lookForSlash = false;
+ for (;;) {
+ c = getChar();
+ if (c == EOF_CHAR) {
+ parser.addError("msg.unterminated.comment");
+ return Token.ERROR;
+ } else if (c == '*') {
+ lookForSlash = true;
+ } else if (c == '/') {
+ if (lookForSlash) {
+ continue retry;
+ }
+ } else {
+ lookForSlash = false;
+ }
+ }
+ }
+
+ if (matchChar('=')) {
+ return Token.ASSIGN_DIV;
+ } else {
+ return Token.DIV;
+ }
+
+ case '%':
+ if (matchChar('=')) {
+ return Token.ASSIGN_MOD;
+ } else {
+ return Token.MOD;
+ }
+
+ case '~':
+ return Token.BITNOT;
+
+ case '+':
+ if (matchChar('=')) {
+ return Token.ASSIGN_ADD;
+ } else if (matchChar('+')) {
+ return Token.INC;
+ } else {
+ return Token.ADD;
+ }
+
+ case '-':
+ if (matchChar('=')) {
+ c = Token.ASSIGN_SUB;
+ } else if (matchChar('-')) {
+ if (!dirtyLine) {
+ // treat HTML end-comment after possible whitespace
+ // after line start as comment-utill-eol
+ if (matchChar('>')) {
+ skipLine();
+ continue retry;
+ }
+ }
+ c = Token.DEC;
+ } else {
+ c = Token.SUB;
+ }
+ dirtyLine = true;
+ return c;
+
+ default:
+ parser.addError("msg.illegal.character");
+ return Token.ERROR;
+ }
+ }
+ }
+
+ private static boolean isAlpha(int c)
+ {
+ // Use 'Z' < 'a'
+ if (c <= 'Z') {
+ return 'A' <= c;
+ } else {
+ return 'a' <= c && c <= 'z';
+ }
+ }
+
+ static boolean isDigit(int c)
+ {
+ return '0' <= c && c <= '9';
+ }
+
+ /* As defined in ECMA. jsscan.c uses C isspace() (which allows
+ * \v, I think.) note that code in getChar() implicitly accepts
+ * '\r' == \u000D as well.
+ */
+ static boolean isJSSpace(int c)
+ {
+ if (c <= 127) {
+ return c == 0x20 || c == 0x9 || c == 0xC || c == 0xB;
+ } else {
+ return c == 0xA0
+ || Character.getType((char)c) == Character.SPACE_SEPARATOR;
+ }
+ }
+
+ private static boolean isJSFormatChar(int c)
+ {
+ return c > 127 && Character.getType((char)c) == Character.FORMAT;
+ }
+
+ /**
+ * Parser calls the method when it gets / or /= in literal context.
+ */
+ void readRegExp(int startToken)
+ throws IOException
+ {
+ stringBufferTop = 0;
+ if (startToken == Token.ASSIGN_DIV) {
+ // Miss-scanned /=
+ addToString('=');
+ } else {
+ if (startToken != Token.DIV) Kit.codeBug();
+ }
+
+ boolean inCharSet = false; // true if inside a '['..']' pair
+ int c;
+ while ((c = getChar()) != '/' || inCharSet) {
+ if (c == '\n' || c == EOF_CHAR) {
+ ungetChar(c);
+ throw parser.reportError("msg.unterminated.re.lit");
+ }
+ if (c == '\\') {
+ addToString(c);
+ c = getChar();
+ } else if (c == '[') {
+ inCharSet = true;
+ } else if (c == ']') {
+ inCharSet = false;
+ }
+ addToString(c);
+ }
+ int reEnd = stringBufferTop;
+
+ while (true) {
+ if (matchChar('g'))
+ addToString('g');
+ else if (matchChar('i'))
+ addToString('i');
+ else if (matchChar('m'))
+ addToString('m');
+ else
+ break;
+ }
+
+ if (isAlpha(peekChar())) {
+ throw parser.reportError("msg.invalid.re.flag");
+ }
+
+ this.string = new String(stringBuffer, 0, reEnd);
+ this.regExpFlags = new String(stringBuffer, reEnd,
+ stringBufferTop - reEnd);
+ }
+
+ boolean isXMLAttribute()
+ {
+ return xmlIsAttribute;
+ }
+
+ int getFirstXMLToken() throws IOException
+ {
+ xmlOpenTagsCount = 0;
+ xmlIsAttribute = false;
+ xmlIsTagContent = false;
+ ungetChar('<');
+ return getNextXMLToken();
+ }
+
+ int getNextXMLToken() throws IOException
+ {
+ stringBufferTop = 0; // remember the XML
+
+ for (int c = getChar(); c != EOF_CHAR; c = getChar()) {
+ if (xmlIsTagContent) {
+ switch (c) {
+ case '>':
+ addToString(c);
+ xmlIsTagContent = false;
+ xmlIsAttribute = false;
+ break;
+ case '/':
+ addToString(c);
+ if (peekChar() == '>') {
+ c = getChar();
+ addToString(c);
+ xmlIsTagContent = false;
+ xmlOpenTagsCount--;
+ }
+ break;
+ case '{':
+ ungetChar(c);
+ this.string = getStringFromBuffer();
+ return Token.XML;
+ case '\'':
+ case '"':
+ addToString(c);
+ if (!readQuotedString(c)) return Token.ERROR;
+ break;
+ case '=':
+ addToString(c);
+ xmlIsAttribute = true;
+ break;
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ addToString(c);
+ break;
+ default:
+ addToString(c);
+ xmlIsAttribute = false;
+ break;
+ }
+
+ if (!xmlIsTagContent && xmlOpenTagsCount == 0) {
+ this.string = getStringFromBuffer();
+ return Token.XMLEND;
+ }
+ } else {
+ switch (c) {
+ case '<':
+ addToString(c);
+ c = peekChar();
+ switch (c) {
+ case '!':
+ c = getChar(); // Skip !
+ addToString(c);
+ c = peekChar();
+ switch (c) {
+ case '-':
+ c = getChar(); // Skip -
+ addToString(c);
+ c = getChar();
+ if (c == '-') {
+ addToString(c);
+ if(!readXmlComment()) return Token.ERROR;
+ } else {
+ // throw away the string in progress
+ stringBufferTop = 0;
+ this.string = null;
+ parser.addError("msg.XML.bad.form");
+ return Token.ERROR;
+ }
+ break;
+ case '[':
+ c = getChar(); // Skip [
+ addToString(c);
+ if (getChar() == 'C' &&
+ getChar() == 'D' &&
+ getChar() == 'A' &&
+ getChar() == 'T' &&
+ getChar() == 'A' &&
+ getChar() == '[')
+ {
+ addToString('C');
+ addToString('D');
+ addToString('A');
+ addToString('T');
+ addToString('A');
+ addToString('[');
+ if (!readCDATA()) return Token.ERROR;
+
+ } else {
+ // throw away the string in progress
+ stringBufferTop = 0;
+ this.string = null;
+ parser.addError("msg.XML.bad.form");
+ return Token.ERROR;
+ }
+ break;
+ default:
+ if(!readEntity()) return Token.ERROR;
+ break;
+ }
+ break;
+ case '?':
+ c = getChar(); // Skip ?
+ addToString(c);
+ if (!readPI()) return Token.ERROR;
+ break;
+ case '/':
+ // End tag
+ c = getChar(); // Skip /
+ addToString(c);
+ if (xmlOpenTagsCount == 0) {
+ // throw away the string in progress
+ stringBufferTop = 0;
+ this.string = null;
+ parser.addError("msg.XML.bad.form");
+ return Token.ERROR;
+ }
+ xmlIsTagContent = true;
+ xmlOpenTagsCount--;
+ break;
+ default:
+ // Start tag
+ xmlIsTagContent = true;
+ xmlOpenTagsCount++;
+ break;
+ }
+ break;
+ case '{':
+ ungetChar(c);
+ this.string = getStringFromBuffer();
+ return Token.XML;
+ default:
+ addToString(c);
+ break;
+ }
+ }
+ }
+
+ stringBufferTop = 0; // throw away the string in progress
+ this.string = null;
+ parser.addError("msg.XML.bad.form");
+ return Token.ERROR;
+ }
+
+ /**
+ *
+ */
+ private boolean readQuotedString(int quote) throws IOException
+ {
+ for (int c = getChar(); c != EOF_CHAR; c = getChar()) {
+ addToString(c);
+ if (c == quote) return true;
+ }
+
+ stringBufferTop = 0; // throw away the string in progress
+ this.string = null;
+ parser.addError("msg.XML.bad.form");
+ return false;
+ }
+
+ /**
+ *
+ */
+ private boolean readXmlComment() throws IOException
+ {
+ for (int c = getChar(); c != EOF_CHAR;) {
+ addToString(c);
+ if (c == '-' && peekChar() == '-') {
+ c = getChar();
+ addToString(c);
+ if (peekChar() == '>') {
+ c = getChar(); // Skip >
+ addToString(c);
+ return true;
+ } else {
+ continue;
+ }
+ }
+ c = getChar();
+ }
+
+ stringBufferTop = 0; // throw away the string in progress
+ this.string = null;
+ parser.addError("msg.XML.bad.form");
+ return false;
+ }
+
+ /**
+ *
+ */
+ private boolean readCDATA() throws IOException
+ {
+ for (int c = getChar(); c != EOF_CHAR;) {
+ addToString(c);
+ if (c == ']' && peekChar() == ']') {
+ c = getChar();
+ addToString(c);
+ if (peekChar() == '>') {
+ c = getChar(); // Skip >
+ addToString(c);
+ return true;
+ } else {
+ continue;
+ }
+ }
+ c = getChar();
+ }
+
+ stringBufferTop = 0; // throw away the string in progress
+ this.string = null;
+ parser.addError("msg.XML.bad.form");
+ return false;
+ }
+
+ /**
+ *
+ */
+ private boolean readEntity() throws IOException
+ {
+ int declTags = 1;
+ for (int c = getChar(); c != EOF_CHAR; c = getChar()) {
+ addToString(c);
+ switch (c) {
+ case '<':
+ declTags++;
+ break;
+ case '>':
+ declTags--;
+ if (declTags == 0) return true;
+ break;
+ }
+ }
+
+ stringBufferTop = 0; // throw away the string in progress
+ this.string = null;
+ parser.addError("msg.XML.bad.form");
+ return false;
+ }
+
+ /**
+ *
+ */
+ private boolean readPI() throws IOException
+ {
+ for (int c = getChar(); c != EOF_CHAR; c = getChar()) {
+ addToString(c);
+ if (c == '?' && peekChar() == '>') {
+ c = getChar(); // Skip >
+ addToString(c);
+ return true;
+ }
+ }
+
+ stringBufferTop = 0; // throw away the string in progress
+ this.string = null;
+ parser.addError("msg.XML.bad.form");
+ return false;
+ }
+
+ private String getStringFromBuffer()
+ {
+ return new String(stringBuffer, 0, stringBufferTop);
+ }
+
+ private void addToString(int c)
+ {
+ int N = stringBufferTop;
+ if (N == stringBuffer.length) {
+ char[] tmp = new char[stringBuffer.length * 2];
+ System.arraycopy(stringBuffer, 0, tmp, 0, N);
+ stringBuffer = tmp;
+ }
+ stringBuffer[N] = (char)c;
+ stringBufferTop = N + 1;
+ }
+
+ private void ungetChar(int c)
+ {
+ // can not unread past across line boundary
+ if (ungetCursor != 0 && ungetBuffer[ungetCursor - 1] == '\n')
+ Kit.codeBug();
+ ungetBuffer[ungetCursor++] = c;
+ }
+
+ private boolean matchChar(int test) throws IOException
+ {
+ int c = getCharIgnoreLineEnd();
+ if (c == test) {
+ return true;
+ } else {
+ ungetCharIgnoreLineEnd(c);
+ return false;
+ }
+ }
+
+ private int peekChar() throws IOException
+ {
+ int c = getChar();
+ ungetChar(c);
+ return c;
+ }
+
+ private int getChar() throws IOException
+ {
+ if (ungetCursor != 0) {
+ return ungetBuffer[--ungetCursor];
+ }
+
+ for(;;) {
+ int c;
+ if (sourceString != null) {
+ if (sourceCursor == sourceEnd) {
+ hitEOF = true;
+ return EOF_CHAR;
+ }
+ c = sourceString.charAt(sourceCursor++);
+ } else {
+ if (sourceCursor == sourceEnd) {
+ if (!fillSourceBuffer()) {
+ hitEOF = true;
+ return EOF_CHAR;
+ }
+ }
+ c = sourceBuffer[sourceCursor++];
+ }
+
+ if (lineEndChar >= 0) {
+ if (lineEndChar == '\r' && c == '\n') {
+ lineEndChar = '\n';
+ continue;
+ }
+ lineEndChar = -1;
+ lineStart = sourceCursor - 1;
+ lineno++;
+ }
+
+ if (c <= 127) {
+ if (c == '\n' || c == '\r') {
+ lineEndChar = c;
+ c = '\n';
+ }
+ } else {
+ if (isJSFormatChar(c)) {
+ continue;
+ }
+ if (ScriptRuntime.isJSLineTerminator(c)) {
+ lineEndChar = c;
+ c = '\n';
+ }
+ }
+ return c;
+ }
+ }
+
+ private int getCharIgnoreLineEnd() throws IOException
+ {
+ if (ungetCursor != 0) {
+ return ungetBuffer[--ungetCursor];
+ }
+
+ for(;;) {
+ int c;
+ if (sourceString != null) {
+ if (sourceCursor == sourceEnd) {
+ hitEOF = true;
+ return EOF_CHAR;
+ }
+ c = sourceString.charAt(sourceCursor++);
+ } else {
+ if (sourceCursor == sourceEnd) {
+ if (!fillSourceBuffer()) {
+ hitEOF = true;
+ return EOF_CHAR;
+ }
+ }
+ c = sourceBuffer[sourceCursor++];
+ }
+
+ if (c <= 127) {
+ if (c == '\n' || c == '\r') {
+ lineEndChar = c;
+ c = '\n';
+ }
+ } else {
+ if (isJSFormatChar(c)) {
+ continue;
+ }
+ if (ScriptRuntime.isJSLineTerminator(c)) {
+ lineEndChar = c;
+ c = '\n';
+ }
+ }
+ return c;
+ }
+ }
+
+ private void ungetCharIgnoreLineEnd(int c)
+ {
+ ungetBuffer[ungetCursor++] = c;
+ }
+
+ private void skipLine() throws IOException
+ {
+ // skip to end of line
+ int c;
+ while ((c = getChar()) != EOF_CHAR && c != '\n') { }
+ ungetChar(c);
+ }
+
+ final int getOffset()
+ {
+ int n = sourceCursor - lineStart;
+ if (lineEndChar >= 0) { --n; }
+ return n;
+ }
+
+ final String getLine()
+ {
+ if (sourceString != null) {
+ // String case
+ int lineEnd = sourceCursor;
+ if (lineEndChar >= 0) {
+ --lineEnd;
+ } else {
+ for (; lineEnd != sourceEnd; ++lineEnd) {
+ int c = sourceString.charAt(lineEnd);
+ if (ScriptRuntime.isJSLineTerminator(c)) {
+ break;
+ }
+ }
+ }
+ return sourceString.substring(lineStart, lineEnd);
+ } else {
+ // Reader case
+ int lineLength = sourceCursor - lineStart;
+ if (lineEndChar >= 0) {
+ --lineLength;
+ } else {
+ // Read until the end of line
+ for (;; ++lineLength) {
+ int i = lineStart + lineLength;
+ if (i == sourceEnd) {
+ try {
+ if (!fillSourceBuffer()) { break; }
+ } catch (IOException ioe) {
+ // ignore it, we're already displaying an error...
+ break;
+ }
+ // i recalculuation as fillSourceBuffer can move saved
+ // line buffer and change lineStart
+ i = lineStart + lineLength;
+ }
+ int c = sourceBuffer[i];
+ if (ScriptRuntime.isJSLineTerminator(c)) {
+ break;
+ }
+ }
+ }
+ return new String(sourceBuffer, lineStart, lineLength);
+ }
+ }
+
+ private boolean fillSourceBuffer() throws IOException
+ {
+ if (sourceString != null) Kit.codeBug();
+ if (sourceEnd == sourceBuffer.length) {
+ if (lineStart != 0) {
+ System.arraycopy(sourceBuffer, lineStart, sourceBuffer, 0,
+ sourceEnd - lineStart);
+ sourceEnd -= lineStart;
+ sourceCursor -= lineStart;
+ lineStart = 0;
+ } else {
+ char[] tmp = new char[sourceBuffer.length * 2];
+ System.arraycopy(sourceBuffer, 0, tmp, 0, sourceEnd);
+ sourceBuffer = tmp;
+ }
+ }
+ int n = sourceReader.read(sourceBuffer, sourceEnd,
+ sourceBuffer.length - sourceEnd);
+ if (n < 0) {
+ return false;
+ }
+ sourceEnd += n;
+ return true;
+ }
+
+ // stuff other than whitespace since start of line
+ private boolean dirtyLine;
+
+ String regExpFlags;
+
+ // Set this to an initial non-null value so that the Parser has
+ // something to retrieve even if an error has occurred and no
+ // string is found. Fosters one class of error, but saves lots of
+ // code.
+ private String string = "";
+ private double number;
+
+ private char[] stringBuffer = new char[128];
+ private int stringBufferTop;
+ private ObjToIntMap allStrings = new ObjToIntMap(50);
+
+ // Room to backtrace from to < on failed match of the last - in <!--
+ private final int[] ungetBuffer = new int[3];
+ private int ungetCursor;
+
+ private boolean hitEOF = false;
+
+ private int lineStart = 0;
+ private int lineno;
+ private int lineEndChar = -1;
+
+ private String sourceString;
+ private Reader sourceReader;
+ private char[] sourceBuffer;
+ private int sourceEnd;
+ private int sourceCursor;
+
+ // for xml tokenizer
+ private boolean xmlIsAttribute;
+ private boolean xmlIsTagContent;
+ private int xmlOpenTagsCount;
+
+ private Parser parser;
+}
diff --git a/src/org/mozilla/javascript/UintMap.java b/src/org/mozilla/javascript/UintMap.java
new file mode 100644
index 0000000..0027819
--- /dev/null
+++ b/src/org/mozilla/javascript/UintMap.java
@@ -0,0 +1,659 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.Serializable;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * Map to associate non-negative integers to objects or integers.
+ * The map does not synchronize any of its operation, so either use
+ * it from a single thread or do own synchronization or perform all mutation
+ * operations on one thread before passing the map to others.
+ *
+ * @author Igor Bukanov
+ *
+ */
+
+public class UintMap implements Serializable
+{
+ static final long serialVersionUID = 4242698212885848444L;
+
+// Map implementation via hashtable,
+// follows "The Art of Computer Programming" by Donald E. Knuth
+
+ public UintMap() {
+ this(4);
+ }
+
+ public UintMap(int initialCapacity) {
+ if (initialCapacity < 0) Kit.codeBug();
+ // Table grow when number of stored keys >= 3/4 of max capacity
+ int minimalCapacity = initialCapacity * 4 / 3;
+ int i;
+ for (i = 2; (1 << i) < minimalCapacity; ++i) { }
+ power = i;
+ if (check && power < 2) Kit.codeBug();
+ }
+
+ public boolean isEmpty() {
+ return keyCount == 0;
+ }
+
+ public int size() {
+ return keyCount;
+ }
+
+ public boolean has(int key) {
+ if (key < 0) Kit.codeBug();
+ return 0 <= findIndex(key);
+ }
+
+ /**
+ * Get object value assigned with key.
+ * @return key object value or null if key is absent
+ */
+ public Object getObject(int key) {
+ if (key < 0) Kit.codeBug();
+ if (values != null) {
+ int index = findIndex(key);
+ if (0 <= index) {
+ return values[index];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get integer value assigned with key.
+ * @return key integer value or defaultValue if key is absent
+ */
+ public int getInt(int key, int defaultValue) {
+ if (key < 0) Kit.codeBug();
+ int index = findIndex(key);
+ if (0 <= index) {
+ if (ivaluesShift != 0) {
+ return keys[ivaluesShift + index];
+ }
+ return 0;
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Get integer value assigned with key.
+ * @return key integer value or defaultValue if key does not exist or does
+ * not have int value
+ * @throws RuntimeException if key does not exist
+ */
+ public int getExistingInt(int key) {
+ if (key < 0) Kit.codeBug();
+ int index = findIndex(key);
+ if (0 <= index) {
+ if (ivaluesShift != 0) {
+ return keys[ivaluesShift + index];
+ }
+ return 0;
+ }
+ // Key must exist
+ Kit.codeBug();
+ return 0;
+ }
+
+ /**
+ * Set object value of the key.
+ * If key does not exist, also set its int value to 0.
+ */
+ public void put(int key, Object value) {
+ if (key < 0) Kit.codeBug();
+ int index = ensureIndex(key, false);
+ if (values == null) {
+ values = new Object[1 << power];
+ }
+ values[index] = value;
+ }
+
+ /**
+ * Set int value of the key.
+ * If key does not exist, also set its object value to null.
+ */
+ public void put(int key, int value) {
+ if (key < 0) Kit.codeBug();
+ int index = ensureIndex(key, true);
+ if (ivaluesShift == 0) {
+ int N = 1 << power;
+ // keys.length can be N * 2 after clear which set ivaluesShift to 0
+ if (keys.length != N * 2) {
+ int[] tmp = new int[N * 2];
+ System.arraycopy(keys, 0, tmp, 0, N);
+ keys = tmp;
+ }
+ ivaluesShift = N;
+ }
+ keys[ivaluesShift + index] = value;
+ }
+
+ public void remove(int key) {
+ if (key < 0) Kit.codeBug();
+ int index = findIndex(key);
+ if (0 <= index) {
+ keys[index] = DELETED;
+ --keyCount;
+ // Allow to GC value and make sure that new key with the deleted
+ // slot shall get proper default values
+ if (values != null) { values[index] = null; }
+ if (ivaluesShift != 0) { keys[ivaluesShift + index] = 0; }
+ }
+ }
+
+ public void clear() {
+ int N = 1 << power;
+ if (keys != null) {
+ for (int i = 0; i != N; ++i) {
+ keys[i] = EMPTY;
+ }
+ if (values != null) {
+ for (int i = 0; i != N; ++i) {
+ values[i] = null;
+ }
+ }
+ }
+ ivaluesShift = 0;
+ keyCount = 0;
+ occupiedCount = 0;
+ }
+
+ /** Return array of present keys */
+ public int[] getKeys() {
+ int[] keys = this.keys;
+ int n = keyCount;
+ int[] result = new int[n];
+ for (int i = 0; n != 0; ++i) {
+ int entry = keys[i];
+ if (entry != EMPTY && entry != DELETED) {
+ result[--n] = entry;
+ }
+ }
+ return result;
+ }
+
+ private static int tableLookupStep(int fraction, int mask, int power) {
+ int shift = 32 - 2 * power;
+ if (shift >= 0) {
+ return ((fraction >>> shift) & mask) | 1;
+ }
+ else {
+ return (fraction & (mask >>> -shift)) | 1;
+ }
+ }
+
+ private int findIndex(int key) {
+ int[] keys = this.keys;
+ if (keys != null) {
+ int fraction = key * A;
+ int index = fraction >>> (32 - power);
+ int entry = keys[index];
+ if (entry == key) { return index; }
+ if (entry != EMPTY) {
+ // Search in table after first failed attempt
+ int mask = (1 << power) - 1;
+ int step = tableLookupStep(fraction, mask, power);
+ int n = 0;
+ do {
+ if (check) {
+ if (n >= occupiedCount) Kit.codeBug();
+ ++n;
+ }
+ index = (index + step) & mask;
+ entry = keys[index];
+ if (entry == key) { return index; }
+ } while (entry != EMPTY);
+ }
+ }
+ return -1;
+ }
+
+// Insert key that is not present to table without deleted entries
+// and enough free space
+ private int insertNewKey(int key) {
+ if (check && occupiedCount != keyCount) Kit.codeBug();
+ if (check && keyCount == 1 << power) Kit.codeBug();
+ int[] keys = this.keys;
+ int fraction = key * A;
+ int index = fraction >>> (32 - power);
+ if (keys[index] != EMPTY) {
+ int mask = (1 << power) - 1;
+ int step = tableLookupStep(fraction, mask, power);
+ int firstIndex = index;
+ do {
+ if (check && keys[index] == DELETED) Kit.codeBug();
+ index = (index + step) & mask;
+ if (check && firstIndex == index) Kit.codeBug();
+ } while (keys[index] != EMPTY);
+ }
+ keys[index] = key;
+ ++occupiedCount;
+ ++keyCount;
+ return index;
+ }
+
+ private void rehashTable(boolean ensureIntSpace) {
+ if (keys != null) {
+ // Check if removing deleted entries would free enough space
+ if (keyCount * 2 >= occupiedCount) {
+ // Need to grow: less then half of deleted entries
+ ++power;
+ }
+ }
+ int N = 1 << power;
+ int[] old = keys;
+ int oldShift = ivaluesShift;
+ if (oldShift == 0 && !ensureIntSpace) {
+ keys = new int[N];
+ }
+ else {
+ ivaluesShift = N; keys = new int[N * 2];
+ }
+ for (int i = 0; i != N; ++i) { keys[i] = EMPTY; }
+
+ Object[] oldValues = values;
+ if (oldValues != null) { values = new Object[N]; }
+
+ int oldCount = keyCount;
+ occupiedCount = 0;
+ if (oldCount != 0) {
+ keyCount = 0;
+ for (int i = 0, remaining = oldCount; remaining != 0; ++i) {
+ int key = old[i];
+ if (key != EMPTY && key != DELETED) {
+ int index = insertNewKey(key);
+ if (oldValues != null) {
+ values[index] = oldValues[i];
+ }
+ if (oldShift != 0) {
+ keys[ivaluesShift + index] = old[oldShift + i];
+ }
+ --remaining;
+ }
+ }
+ }
+ }
+
+// Ensure key index creating one if necessary
+ private int ensureIndex(int key, boolean intType) {
+ int index = -1;
+ int firstDeleted = -1;
+ int[] keys = this.keys;
+ if (keys != null) {
+ int fraction = key * A;
+ index = fraction >>> (32 - power);
+ int entry = keys[index];
+ if (entry == key) { return index; }
+ if (entry != EMPTY) {
+ if (entry == DELETED) { firstDeleted = index; }
+ // Search in table after first failed attempt
+ int mask = (1 << power) - 1;
+ int step = tableLookupStep(fraction, mask, power);
+ int n = 0;
+ do {
+ if (check) {
+ if (n >= occupiedCount) Kit.codeBug();
+ ++n;
+ }
+ index = (index + step) & mask;
+ entry = keys[index];
+ if (entry == key) { return index; }
+ if (entry == DELETED && firstDeleted < 0) {
+ firstDeleted = index;
+ }
+ } while (entry != EMPTY);
+ }
+ }
+ // Inserting of new key
+ if (check && keys != null && keys[index] != EMPTY)
+ Kit.codeBug();
+ if (firstDeleted >= 0) {
+ index = firstDeleted;
+ }
+ else {
+ // Need to consume empty entry: check occupation level
+ if (keys == null || occupiedCount * 4 >= (1 << power) * 3) {
+ // Too litle unused entries: rehash
+ rehashTable(intType);
+ keys = this.keys;
+ return insertNewKey(key);
+ }
+ ++occupiedCount;
+ }
+ keys[index] = key;
+ ++keyCount;
+ return index;
+ }
+
+ private void writeObject(ObjectOutputStream out)
+ throws IOException
+ {
+ out.defaultWriteObject();
+
+ int count = keyCount;
+ if (count != 0) {
+ boolean hasIntValues = (ivaluesShift != 0);
+ boolean hasObjectValues = (values != null);
+ out.writeBoolean(hasIntValues);
+ out.writeBoolean(hasObjectValues);
+
+ for (int i = 0; count != 0; ++i) {
+ int key = keys[i];
+ if (key != EMPTY && key != DELETED) {
+ --count;
+ out.writeInt(key);
+ if (hasIntValues) {
+ out.writeInt(keys[ivaluesShift + i]);
+ }
+ if (hasObjectValues) {
+ out.writeObject(values[i]);
+ }
+ }
+ }
+ }
+ }
+
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+
+ int writtenKeyCount = keyCount;
+ if (writtenKeyCount != 0) {
+ keyCount = 0;
+ boolean hasIntValues = in.readBoolean();
+ boolean hasObjectValues = in.readBoolean();
+
+ int N = 1 << power;
+ if (hasIntValues) {
+ keys = new int[2 * N];
+ ivaluesShift = N;
+ }else {
+ keys = new int[N];
+ }
+ for (int i = 0; i != N; ++i) {
+ keys[i] = EMPTY;
+ }
+ if (hasObjectValues) {
+ values = new Object[N];
+ }
+ for (int i = 0; i != writtenKeyCount; ++i) {
+ int key = in.readInt();
+ int index = insertNewKey(key);
+ if (hasIntValues) {
+ int ivalue = in.readInt();
+ keys[ivaluesShift + index] = ivalue;
+ }
+ if (hasObjectValues) {
+ values[index] = in.readObject();
+ }
+ }
+ }
+ }
+
+// A == golden_ratio * (1 << 32) = ((sqrt(5) - 1) / 2) * (1 << 32)
+// See Knuth etc.
+ private static final int A = 0x9e3779b9;
+
+ private static final int EMPTY = -1;
+ private static final int DELETED = -2;
+
+// Structure of kyes and values arrays (N == 1 << power):
+// keys[0 <= i < N]: key value or EMPTY or DELETED mark
+// values[0 <= i < N]: value of key at keys[i]
+// keys[N <= i < 2N]: int values of keys at keys[i - N]
+
+ private transient int[] keys;
+ private transient Object[] values;
+
+ private int power;
+ private int keyCount;
+ private transient int occupiedCount; // == keyCount + deleted_count
+
+ // If ivaluesShift != 0, keys[ivaluesShift + index] contains integer
+ // values associated with keys
+ private transient int ivaluesShift;
+
+// If true, enables consitency checks
+ private static final boolean check = false;
+
+/* TEST START
+
+ public static void main(String[] args) {
+ if (!check) {
+ System.err.println("Set check to true and re-run");
+ throw new RuntimeException("Set check to true and re-run");
+ }
+
+ UintMap map;
+ map = new UintMap();
+ testHash(map, 2);
+ map = new UintMap();
+ testHash(map, 10 * 1000);
+ map = new UintMap(30 * 1000);
+ testHash(map, 10 * 100);
+ map.clear();
+ testHash(map, 4);
+ map = new UintMap(0);
+ testHash(map, 10 * 100);
+ }
+
+ private static void testHash(UintMap map, int N) {
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ map.put(i, i);
+ check(i == map.getInt(i, -1));
+ }
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ map.put(i, i);
+ check(i == map.getInt(i, -1));
+ }
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ map.put(i, new Integer(i));
+ check(-1 == map.getInt(i, -1));
+ Integer obj = (Integer)map.getObject(i);
+ check(obj != null && i == obj.intValue());
+ }
+
+ check(map.size() == N);
+
+ System.out.print("."); System.out.flush();
+ int[] keys = map.getKeys();
+ check(keys.length == N);
+ for (int i = 0; i != N; ++i) {
+ int key = keys[i];
+ check(map.has(key));
+ check(!map.isIntType(key));
+ check(map.isObjectType(key));
+ Integer obj = (Integer) map.getObject(key);
+ check(obj != null && key == obj.intValue());
+ }
+
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ check(-1 == map.getInt(i, -1));
+ }
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ map.put(i * i, i);
+ check(i == map.getInt(i * i, -1));
+ }
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ check(i == map.getInt(i * i, -1));
+ }
+
+ System.out.print("."); System.out.flush();
+ for (int i = 0; i != N; ++i) {
+ map.put(i * i, new Integer(i));
+ check(-1 == map.getInt(i * i, -1));
+ map.remove(i * i);
+ check(!map.has(i * i));
+ map.put(i * i, i);
+ check(map.isIntType(i * i));
+ check(null == map.getObject(i * i));
+ map.remove(i * i);
+ check(!map.isObjectType(i * i));
+ check(!map.isIntType(i * i));
+ }
+
+ int old_size = map.size();
+ for (int i = 0; i != N; ++i) {
+ map.remove(i * i);
+ check(map.size() == old_size);
+ }
+
+ System.out.print("."); System.out.flush();
+ map.clear();
+ check(map.size() == 0);
+ for (int i = 0; i != N; ++i) {
+ map.put(i * i, i);
+ map.put(i * i + 1, new Double(i+0.5));
+ }
+ checkSameMaps(map, (UintMap)writeAndRead(map));
+
+ System.out.print("."); System.out.flush();
+ map = new UintMap(0);
+ checkSameMaps(map, (UintMap)writeAndRead(map));
+ map = new UintMap(1);
+ checkSameMaps(map, (UintMap)writeAndRead(map));
+ map = new UintMap(1000);
+ checkSameMaps(map, (UintMap)writeAndRead(map));
+
+ System.out.print("."); System.out.flush();
+ map = new UintMap(N / 10);
+ for (int i = 0; i != N; ++i) {
+ map.put(2*i+1, i);
+ }
+ checkSameMaps(map, (UintMap)writeAndRead(map));
+
+ System.out.print("."); System.out.flush();
+ map = new UintMap(N / 10);
+ for (int i = 0; i != N; ++i) {
+ map.put(2*i+1, i);
+ }
+ for (int i = 0; i != N / 2; ++i) {
+ map.remove(2*i+1);
+ }
+ checkSameMaps(map, (UintMap)writeAndRead(map));
+
+ System.out.print("."); System.out.flush();
+ map = new UintMap();
+ for (int i = 0; i != N; ++i) {
+ map.put(2*i+1, new Double(i + 10));
+ }
+ for (int i = 0; i != N / 2; ++i) {
+ map.remove(2*i+1);
+ }
+ checkSameMaps(map, (UintMap)writeAndRead(map));
+
+ System.out.println(); System.out.flush();
+
+ }
+
+ private static void checkSameMaps(UintMap map1, UintMap map2) {
+ check(map1.size() == map2.size());
+ int[] keys = map1.getKeys();
+ check(keys.length == map1.size());
+ for (int i = 0; i != keys.length; ++i) {
+ int key = keys[i];
+ check(map2.has(key));
+ check(map1.isObjectType(key) == map2.isObjectType(key));
+ check(map1.isIntType(key) == map2.isIntType(key));
+ Object o1 = map1.getObject(key);
+ Object o2 = map2.getObject(key);
+ if (map1.isObjectType(key)) {
+ check(o1.equals(o2));
+ }else {
+ check(map1.getObject(key) == null);
+ check(map2.getObject(key) == null);
+ }
+ if (map1.isIntType(key)) {
+ check(map1.getExistingInt(key) == map2.getExistingInt(key));
+ }else {
+ check(map1.getInt(key, -10) == -10);
+ check(map1.getInt(key, -11) == -11);
+ check(map2.getInt(key, -10) == -10);
+ check(map2.getInt(key, -11) == -11);
+ }
+ }
+ }
+
+ private static void check(boolean condition) {
+ if (!condition) Kit.codeBug();
+ }
+
+ private static Object writeAndRead(Object obj) {
+ try {
+ java.io.ByteArrayOutputStream
+ bos = new java.io.ByteArrayOutputStream();
+ java.io.ObjectOutputStream
+ out = new java.io.ObjectOutputStream(bos);
+ out.writeObject(obj);
+ out.close();
+ byte[] data = bos.toByteArray();
+ java.io.ByteArrayInputStream
+ bis = new java.io.ByteArrayInputStream(data);
+ java.io.ObjectInputStream
+ in = new java.io.ObjectInputStream(bis);
+ Object result = in.readObject();
+ in.close();
+ return result;
+ }catch (Exception ex) {
+ ex.printStackTrace();
+ throw new RuntimeException("Unexpected");
+ }
+ }
+
+// TEST END */
+}
diff --git a/src/org/mozilla/javascript/Undefined.java b/src/org/mozilla/javascript/Undefined.java
new file mode 100644
index 0000000..472f26c
--- /dev/null
+++ b/src/org/mozilla/javascript/Undefined.java
@@ -0,0 +1,60 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.Serializable;
+
+/**
+ * This class implements the Undefined value in JavaScript.
+ */
+public class Undefined implements Serializable
+{
+ static final long serialVersionUID = 9195680630202616767L;
+
+ public static final Object instance = new Undefined();
+
+ private Undefined()
+ {
+ }
+
+ public Object readResolve()
+ {
+ return instance;
+ }
+}
diff --git a/src/org/mozilla/javascript/UniqueTag.java b/src/org/mozilla/javascript/UniqueTag.java
new file mode 100644
index 0000000..feb9840
--- /dev/null
+++ b/src/org/mozilla/javascript/UniqueTag.java
@@ -0,0 +1,121 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+import java.io.Serializable;
+
+/**
+ * Class instances represent serializable tags to mark special Object values.
+ * <p>
+ * Compatibility note: under jdk 1.1 use
+ * org.mozilla.javascript.serialize.ScriptableInputStream to read serialized
+ * instances of UniqueTag as under this JDK version the default
+ * ObjectInputStream would not restore them correctly as it lacks support
+ * for readResolve method
+ */
+public final class UniqueTag implements Serializable
+{
+ static final long serialVersionUID = -4320556826714577259L;
+
+ private static final int ID_NOT_FOUND = 1;
+ private static final int ID_NULL_VALUE = 2;
+ private static final int ID_DOUBLE_MARK = 3;
+
+ /**
+ * Tag to mark non-existing values.
+ */
+ public static final UniqueTag
+ NOT_FOUND = new UniqueTag(ID_NOT_FOUND);
+
+ /**
+ * Tag to distinguish between uninitialized and null values.
+ */
+ public static final UniqueTag
+ NULL_VALUE = new UniqueTag(ID_NULL_VALUE);
+
+ /**
+ * Tag to indicate that a object represents "double" with the real value
+ * stored somewhere else.
+ */
+ public static final UniqueTag
+ DOUBLE_MARK = new UniqueTag(ID_DOUBLE_MARK);
+
+ private final int tagId;
+
+ private UniqueTag(int tagId)
+ {
+ this.tagId = tagId;
+ }
+
+ public Object readResolve()
+ {
+ switch (tagId) {
+ case ID_NOT_FOUND:
+ return NOT_FOUND;
+ case ID_NULL_VALUE:
+ return NULL_VALUE;
+ case ID_DOUBLE_MARK:
+ return DOUBLE_MARK;
+ }
+ throw new IllegalStateException(String.valueOf(tagId));
+ }
+
+// Overridden for better debug printouts
+ @Override
+ public String toString()
+ {
+ String name;
+ switch (tagId) {
+ case ID_NOT_FOUND:
+ name = "NOT_FOUND";
+ break;
+ case ID_NULL_VALUE:
+ name = "NULL_VALUE";
+ break;
+ case ID_DOUBLE_MARK:
+ name = "DOUBLE_MARK";
+ break;
+ default:
+ throw Kit.codeBug();
+ }
+ return super.toString()+": "+name;
+ }
+
+}
+
diff --git a/src/org/mozilla/javascript/VMBridge.java b/src/org/mozilla/javascript/VMBridge.java
new file mode 100644
index 0000000..725a227
--- /dev/null
+++ b/src/org/mozilla/javascript/VMBridge.java
@@ -0,0 +1,183 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Member;
+import java.util.Iterator;
+
+public abstract class VMBridge
+{
+
+ static final VMBridge instance = makeInstance();
+
+ private static VMBridge makeInstance()
+ {
+ String[] classNames = {
+ "org.mozilla.javascript.VMBridge_custom",
+ "org.mozilla.javascript.jdk15.VMBridge_jdk15",
+ "org.mozilla.javascript.jdk13.VMBridge_jdk13",
+ "org.mozilla.javascript.jdk11.VMBridge_jdk11",
+ };
+ for (int i = 0; i != classNames.length; ++i) {
+ String className = classNames[i];
+ Class<?> cl = Kit.classOrNull(className);
+ if (cl != null) {
+ VMBridge bridge = (VMBridge)Kit.newInstanceOrNull(cl);
+ if (bridge != null) {
+ return bridge;
+ }
+ }
+ }
+ throw new IllegalStateException("Failed to create VMBridge instance");
+ }
+
+ /**
+ * Return a helper object to optimize {@link Context} access.
+ * <p>
+ * The runtime will pass the resulting helper object to the subsequent
+ * calls to {@link #getContext(Object contextHelper)} and
+ * {@link #setContext(Object contextHelper, Context cx)} methods.
+ * In this way the implementation can use the helper to cache
+ * information about current thread to make {@link Context} access faster.
+ */
+ protected abstract Object getThreadContextHelper();
+
+ /**
+ * Get {@link Context} instance associated with the current thread
+ * or null if none.
+ *
+ * @param contextHelper The result of {@link #getThreadContextHelper()}
+ * called from the current thread.
+ */
+ protected abstract Context getContext(Object contextHelper);
+
+ /**
+ * Associate {@link Context} instance with the current thread or remove
+ * the current association if <tt>cx</tt> is null.
+ *
+ * @param contextHelper The result of {@link #getThreadContextHelper()}
+ * called from the current thread.
+ */
+ protected abstract void setContext(Object contextHelper, Context cx);
+
+ /**
+ * Return the ClassLoader instance associated with the current thread.
+ */
+ protected abstract ClassLoader getCurrentThreadClassLoader();
+
+ /**
+ * In many JVMSs, public methods in private
+ * classes are not accessible by default (Sun Bug #4071593).
+ * VMBridge instance should try to workaround that via, for example,
+ * calling method.setAccessible(true) when it is available.
+ * The implementation is responsible to catch all possible exceptions
+ * like SecurityException if the workaround is not available.
+ *
+ * @return true if it was possible to make method accessible
+ * or false otherwise.
+ */
+ protected abstract boolean tryToMakeAccessible(Object accessibleObject);
+
+ /**
+ * Create helper object to create later proxies implementing the specified
+ * interfaces later. Under JDK 1.3 the implementation can look like:
+ * <pre>
+ * return java.lang.reflect.Proxy.getProxyClass(..., interfaces).
+ * getConstructor(new Class[] {
+ * java.lang.reflect.InvocationHandler.class });
+ * </pre>
+ *
+ * @param interfaces Array with one or more interface class objects.
+ */
+ protected Object getInterfaceProxyHelper(ContextFactory cf,
+ Class<?>[] interfaces)
+ {
+ throw Context.reportRuntimeError(
+ "VMBridge.getInterfaceProxyHelper is not supported");
+ }
+
+ /**
+ * Create proxy object for {@link InterfaceAdapter}. The proxy should call
+ * {@link InterfaceAdapter#invoke(ContextFactory cf,
+ * Object target,
+ * Scriptable topScope,
+ * Method method,
+ * Object[] args)}
+ * as implementation of interface methods associated with
+ * <tt>proxyHelper</tt>.
+ *
+ * @param proxyHelper The result of the previous call to
+ * {@link #getInterfaceProxyHelper(ContextFactory, Class[])}.
+ */
+ protected Object newInterfaceProxy(Object proxyHelper,
+ ContextFactory cf,
+ InterfaceAdapter adapter,
+ Object target,
+ Scriptable topScope)
+ {
+ throw Context.reportRuntimeError(
+ "VMBridge.newInterfaceProxy is not supported");
+ }
+
+ /**
+ * Returns whether or not a given member (method or constructor)
+ * has variable arguments.
+ * Variable argument methods have only been supported in Java since
+ * JDK 1.5.
+ */
+ protected abstract boolean isVarArgs(Member member);
+
+ /**
+ * If "obj" is a java.util.Iterator or a java.lang.Iterable, return a
+ * wrapping as a JavaScript Iterator. Otherwise, return null.
+ * This method is in VMBridge since Iterable is a JDK 1.5 addition.
+ */
+ public Iterator<?> getJavaIterator(Context cx, Scriptable scope, Object obj) {
+ if (obj instanceof Wrapper) {
+ Object unwrapped = ((Wrapper) obj).unwrap();
+ Iterator<?> iterator = null;
+ if (unwrapped instanceof Iterator)
+ iterator = (Iterator<?>) unwrapped;
+ return iterator;
+ }
+ return null;
+ }
+}
diff --git a/src/org/mozilla/javascript/WrapFactory.java b/src/org/mozilla/javascript/WrapFactory.java
new file mode 100644
index 0000000..665f28b
--- /dev/null
+++ b/src/org/mozilla/javascript/WrapFactory.java
@@ -0,0 +1,183 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * Embeddings that wish to provide their own custom wrappings for Java
+ * objects may extend this class and call
+ * {@link Context#setWrapFactory(WrapFactory)}
+ * Once an instance of this class or an extension of this class is enabled
+ * for a given context (by calling setWrapFactory on that context), Rhino
+ * will call the methods of this class whenever it needs to wrap a value
+ * resulting from a call to a Java method or an access to a Java field.
+ *
+ * @see org.mozilla.javascript.Context#setWrapFactory(WrapFactory)
+ * @since 1.5 Release 4
+ */
+public class WrapFactory
+{
+ /**
+ * Wrap the object.
+ * <p>
+ * The value returned must be one of
+ * <UL>
+ * <LI>java.lang.Boolean</LI>
+ * <LI>java.lang.String</LI>
+ * <LI>java.lang.Number</LI>
+ * <LI>org.mozilla.javascript.Scriptable objects</LI>
+ * <LI>The value returned by Context.getUndefinedValue()</LI>
+ * <LI>null</LI>
+ * </UL>
+ * @param cx the current Context for this thread
+ * @param scope the scope of the executing script
+ * @param obj the object to be wrapped. Note it can be null.
+ * @param staticType type hint. If security restrictions prevent to wrap
+ object based on its class, staticType will be used instead.
+ * @return the wrapped value.
+ */
+ public Object wrap(Context cx, Scriptable scope,
+ Object obj, Class<?> staticType)
+ {
+ if (obj == null || obj == Undefined.instance
+ || obj instanceof Scriptable)
+ {
+ return obj;
+ }
+ if (staticType != null && staticType.isPrimitive()) {
+ if (staticType == Void.TYPE)
+ return Undefined.instance;
+ if (staticType == Character.TYPE)
+ return new Integer(((Character) obj).charValue());
+ return obj;
+ }
+ if (!isJavaPrimitiveWrap()) {
+ if (obj instanceof String || obj instanceof Number
+ || obj instanceof Boolean)
+ {
+ return obj;
+ } else if (obj instanceof Character) {
+ return String.valueOf(((Character)obj).charValue());
+ }
+ }
+ Class<?> cls = obj.getClass();
+ if (cls.isArray()) {
+ return NativeJavaArray.wrap(scope, obj);
+ }
+ return wrapAsJavaObject(cx, scope, obj, staticType);
+ }
+
+ /**
+ * Wrap an object newly created by a constructor call.
+ * @param cx the current Context for this thread
+ * @param scope the scope of the executing script
+ * @param obj the object to be wrapped
+ * @return the wrapped value.
+ */
+ public Scriptable wrapNewObject(Context cx, Scriptable scope, Object obj)
+ {
+ if (obj instanceof Scriptable) {
+ return (Scriptable)obj;
+ }
+ Class<?> cls = obj.getClass();
+ if (cls.isArray()) {
+ return NativeJavaArray.wrap(scope, obj);
+ }
+ return wrapAsJavaObject(cx, scope, obj, null);
+ }
+
+ /**
+ * Wrap Java object as Scriptable instance to allow full access to its
+ * methods and fields from JavaScript.
+ * <p>
+ * {@link #wrap(Context, Scriptable, Object, Class)} and
+ * {@link #wrapNewObject(Context, Scriptable, Object)} call this method
+ * when they can not convert <tt>javaObject</tt> to JavaScript primitive
+ * value or JavaScript array.
+ * <p>
+ * Subclasses can override the method to provide custom wrappers
+ * for Java objects.
+ * @param cx the current Context for this thread
+ * @param scope the scope of the executing script
+ * @param javaObject the object to be wrapped
+ * @param staticType type hint. If security restrictions prevent to wrap
+ object based on its class, staticType will be used instead.
+ * @return the wrapped value which shall not be null
+ */
+ public Scriptable wrapAsJavaObject(Context cx, Scriptable scope,
+ Object javaObject, Class<?> staticType)
+ {
+ Scriptable wrap;
+ wrap = new NativeJavaObject(scope, javaObject, staticType);
+ return wrap;
+ }
+
+ /**
+ * Return <code>false</code> if result of Java method, which is instance of
+ * <code>String</code>, <code>Number</code>, <code>Boolean</code> and
+ * <code>Character</code>, should be used directly as JavaScript primitive
+ * type.
+ * By default the method returns true to indicate that instances of
+ * <code>String</code>, <code>Number</code>, <code>Boolean</code> and
+ * <code>Character</code> should be wrapped as any other Java object and
+ * scripts can access any Java method available in these objects.
+ * Use {@link #setJavaPrimitiveWrap(boolean)} to change this.
+ */
+ public final boolean isJavaPrimitiveWrap()
+ {
+ return javaPrimitiveWrap;
+ }
+
+ /**
+ * @see #isJavaPrimitiveWrap()
+ */
+ public final void setJavaPrimitiveWrap(boolean value)
+ {
+ Context cx = Context.getCurrentContext();
+ if (cx != null && cx.isSealed()) {
+ Context.onSealedMutation();
+ }
+ javaPrimitiveWrap = value;
+ }
+
+ private boolean javaPrimitiveWrap = true;
+
+}
diff --git a/src/org/mozilla/javascript/WrappedException.java b/src/org/mozilla/javascript/WrappedException.java
new file mode 100644
index 0000000..c749f74
--- /dev/null
+++ b/src/org/mozilla/javascript/WrappedException.java
@@ -0,0 +1,93 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript;
+
+/**
+ * A wrapper for runtime exceptions.
+ *
+ * Used by the JavaScript runtime to wrap and propagate exceptions that occur
+ * during runtime.
+ *
+ * @author Norris Boyd
+ */
+public class WrappedException extends EvaluatorException
+{
+ static final long serialVersionUID = -1551979216966520648L;
+
+ /**
+ * @see Context#throwAsScriptRuntimeEx(Throwable e)
+ */
+ public WrappedException(Throwable exception)
+ {
+ super("Wrapped "+exception.toString());
+ this.exception = exception;
+ Kit.initCause(this, exception);
+
+ int[] linep = { 0 };
+ String sourceName = Context.getSourcePositionFromStack(linep);
+ int lineNumber = linep[0];
+ if (sourceName != null) {
+ initSourceName(sourceName);
+ }
+ if (lineNumber != 0) {
+ initLineNumber(lineNumber);
+ }
+ }
+
+ /**
+ * Get the wrapped exception.
+ *
+ * @return the exception that was presented as a argument to the
+ * constructor when this object was created
+ */
+ public Throwable getWrappedException()
+ {
+ return exception;
+ }
+
+ /**
+ * @deprecated Use {@link #getWrappedException()} instead.
+ */
+ public Object unwrap()
+ {
+ return getWrappedException();
+ }
+
+ private Throwable exception;
+}
diff --git a/src/org/mozilla/javascript/Wrapper.java b/src/org/mozilla/javascript/Wrapper.java
new file mode 100644
index 0000000..cb2d2f5
--- /dev/null
+++ b/src/org/mozilla/javascript/Wrapper.java
@@ -0,0 +1,58 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript;
+
+/**
+ * Objects that can wrap other values for reflection in the JS environment
+ * will implement Wrapper.
+ *
+ * Wrapper defines a single method that can be called to unwrap the object.
+ */
+
+public interface Wrapper {
+
+ /**
+ * Unwrap the object by returning the wrapped value.
+ *
+ * @return a wrapped value
+ */
+ public Object unwrap();
+}
diff --git a/src/org/mozilla/javascript/debug/DebugFrame.java b/src/org/mozilla/javascript/debug/DebugFrame.java
new file mode 100644
index 0000000..ef15710
--- /dev/null
+++ b/src/org/mozilla/javascript/debug/DebugFrame.java
@@ -0,0 +1,91 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript.debug;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Scriptable;
+
+/**
+Interface to implement if the application is interested in receiving debug
+information during execution of a particular script or function.
+*/
+public interface DebugFrame {
+
+/**
+Called when execution is ready to start bytecode interpretation for entered a particular function or script.
+
+ at param cx current Context for this thread
+ at param activation the activation scope for the function or script.
+ at param thisObj value of the JavaScript <code>this</code> object
+ at param args the array of arguments
+*/
+ public void onEnter(Context cx, Scriptable activation,
+ Scriptable thisObj, Object[] args);
+/**
+Called when executed code reaches new line in the source.
+ at param cx current Context for this thread
+ at param lineNumber current line number in the script source
+*/
+ public void onLineChange(Context cx, int lineNumber);
+
+/**
+Called when thrown exception is handled by the function or script.
+ at param cx current Context for this thread
+ at param ex exception object
+*/
+ public void onExceptionThrown(Context cx, Throwable ex);
+
+/**
+Called when the function or script for this frame is about to return.
+ at param cx current Context for this thread
+ at param byThrow if true function will leave by throwing exception, otherwise it
+ will execute normal return
+ at param resultOrException function result in case of normal return or
+ exception object if about to throw exception
+*/
+ public void onExit(Context cx, boolean byThrow, Object resultOrException);
+
+/**
+Called when the function or script executes a 'debugger' statement.
+ at param cx current Context for this thread
+*/
+ public void onDebuggerStatement(Context cx);
+}
diff --git a/src/org/mozilla/javascript/debug/DebuggableObject.java b/src/org/mozilla/javascript/debug/DebuggableObject.java
new file mode 100644
index 0000000..23e7421
--- /dev/null
+++ b/src/org/mozilla/javascript/debug/DebuggableObject.java
@@ -0,0 +1,61 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript.debug;
+
+/**
+ * This interface exposes debugging information from objects.
+ */
+public interface DebuggableObject {
+
+ /**
+ * Returns an array of ids for the properties of the object.
+ *
+ * <p>All properties, even those with attribute {DontEnum}, are listed.
+ * This allows the debugger to display all properties of the object.<p>
+ *
+ * @return an array of java.lang.Objects with an entry for every
+ * listed property. Properties accessed via an integer index will
+ * have a corresponding
+ * Integer entry in the returned array. Properties accessed by
+ * a String will have a String entry in the returned array.
+ */
+ public Object[] getAllIds();
+}
diff --git a/src/org/mozilla/javascript/debug/DebuggableScript.java b/src/org/mozilla/javascript/debug/DebuggableScript.java
new file mode 100644
index 0000000..705e442
--- /dev/null
+++ b/src/org/mozilla/javascript/debug/DebuggableScript.java
@@ -0,0 +1,119 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript.debug;
+
+/**
+ * This interface exposes debugging information from executable
+ * code (either functions or top-level scripts).
+ */
+public interface DebuggableScript
+{
+ public boolean isTopLevel();
+
+ /**
+ * Returns true if this is a function, false if it is a script.
+ */
+ public boolean isFunction();
+
+ /**
+ * Get name of the function described by this script.
+ * Return null or an empty string if this script is not a function.
+ */
+ public String getFunctionName();
+
+ /**
+ * Get number of declared parameters in the function.
+ * Return 0 if this script is not a function.
+ *
+ * @see #getParamAndVarCount()
+ * @see #getParamOrVarName(int index)
+ */
+ public int getParamCount();
+
+ /**
+ * Get number of declared parameters and local variables.
+ * Return number of declared global variables if this script is not a
+ * function.
+ *
+ * @see #getParamCount()
+ * @see #getParamOrVarName(int index)
+ */
+ public int getParamAndVarCount();
+
+ /**
+ * Get name of a declared parameter or local variable.
+ * <tt>index</tt> should be less then {@link #getParamAndVarCount()}.
+ * If <tt>index < {@link #getParamCount()}</tt>, return
+ * the name of the corresponding parameter, otherwise return the name
+ * of variable.
+ * If this script is not function, return the name of the declared
+ * global variable.
+ */
+ public String getParamOrVarName(int index);
+
+ /**
+ * Get the name of the source (usually filename or URL)
+ * of the script.
+ */
+ public String getSourceName();
+
+ /**
+ * Returns true if this script or function were runtime-generated
+ * from JavaScript using <tt>eval</tt> function or <tt>Function</tt>
+ * or <tt>Script</tt> constructors.
+ */
+ public boolean isGeneratedScript();
+
+ /**
+ * Get array containing the line numbers that
+ * that can be passed to <code>DebugFrame.onLineChange()<code>.
+ * Note that line order in the resulting array is arbitrary
+ */
+ public int[] getLineNumbers();
+
+ public int getFunctionCount();
+
+ public DebuggableScript getFunction(int index);
+
+ public DebuggableScript getParent();
+
+}
diff --git a/src/org/mozilla/javascript/debug/Debugger.java b/src/org/mozilla/javascript/debug/Debugger.java
new file mode 100644
index 0000000..bfac153
--- /dev/null
+++ b/src/org/mozilla/javascript/debug/Debugger.java
@@ -0,0 +1,69 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript.debug;
+
+import org.mozilla.javascript.Context;
+
+/**
+Interface to implement if the application is interested in receiving debug
+information.
+*/
+public interface Debugger {
+
+/**
+Called when compilation of a particular function or script into internal
+bytecode is done.
+
+ at param cx current Context for this thread
+ at param fnOrScript object describing the function or script
+ at param source the function or script source
+*/
+ void handleCompilationDone(Context cx, DebuggableScript fnOrScript,
+ String source);
+
+/**
+Called when execution entered a particular function or script.
+
+ at return implementation of DebugFrame which receives debug information during
+ the function or script execution or null otherwise
+*/
+ DebugFrame getFrame(Context cx, DebuggableScript fnOrScript);
+}
diff --git a/src/org/mozilla/javascript/jdk13/VMBridge_jdk13.java b/src/org/mozilla/javascript/jdk13/VMBridge_jdk13.java
new file mode 100644
index 0000000..d3c40a3
--- /dev/null
+++ b/src/org/mozilla/javascript/jdk13/VMBridge_jdk13.java
@@ -0,0 +1,165 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.jdk13;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Member;
+import java.lang.reflect.Proxy;
+
+import org.mozilla.javascript.*;
+
+public class VMBridge_jdk13 extends VMBridge
+{
+ private ThreadLocal<Object[]> contextLocal = new ThreadLocal<Object[]>();
+
+ @Override
+ protected Object getThreadContextHelper()
+ {
+ // To make subsequent batch calls to getContext/setContext faster
+ // associate permanently one element array with contextLocal
+ // so getContext/setContext would need just to read/write the first
+ // array element.
+ // Note that it is necessary to use Object[], not Context[] to allow
+ // garbage collection of Rhino classes. For details see comments
+ // by Attila Szegedi in
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=281067#c5
+
+ Object[] storage = contextLocal.get();
+ if (storage == null) {
+ storage = new Object[1];
+ contextLocal.set(storage);
+ }
+ return storage;
+ }
+
+ @Override
+ protected Context getContext(Object contextHelper)
+ {
+ Object[] storage = (Object[])contextHelper;
+ return (Context)storage[0];
+ }
+
+ @Override
+ protected void setContext(Object contextHelper, Context cx)
+ {
+ Object[] storage = (Object[])contextHelper;
+ storage[0] = cx;
+ }
+
+ @Override
+ protected ClassLoader getCurrentThreadClassLoader()
+ {
+ return Thread.currentThread().getContextClassLoader();
+ }
+
+ @Override
+ protected boolean tryToMakeAccessible(Object accessibleObject)
+ {
+ if (!(accessibleObject instanceof AccessibleObject)) {
+ return false;
+ }
+ AccessibleObject accessible = (AccessibleObject)accessibleObject;
+ if (accessible.isAccessible()) {
+ return true;
+ }
+ try {
+ accessible.setAccessible(true);
+ } catch (Exception ex) { }
+
+ return accessible.isAccessible();
+ }
+
+ @Override
+ protected Object getInterfaceProxyHelper(ContextFactory cf,
+ Class<?>[] interfaces)
+ {
+ // XXX: How to handle interfaces array withclasses from different
+ // class loaders? Using cf.getApplicationClassLoader() ?
+ ClassLoader loader = interfaces[0].getClassLoader();
+ Class<?> cl = Proxy.getProxyClass(loader, interfaces);
+ Constructor<?> c;
+ try {
+ c = cl.getConstructor(new Class[] { InvocationHandler.class });
+ } catch (NoSuchMethodException ex) {
+ // Should not happen
+ throw Kit.initCause(new IllegalStateException(), ex);
+ }
+ return c;
+ }
+
+ @Override
+ protected Object newInterfaceProxy(Object proxyHelper,
+ final ContextFactory cf,
+ final InterfaceAdapter adapter,
+ final Object target,
+ final Scriptable topScope)
+ {
+ Constructor<?> c = (Constructor<?>)proxyHelper;
+
+ InvocationHandler handler = new InvocationHandler() {
+ public Object invoke(Object proxy,
+ Method method,
+ Object[] args)
+ {
+ return adapter.invoke(cf, target, topScope, method, args);
+ }
+ };
+ Object proxy;
+ try {
+ proxy = c.newInstance(new Object[] { handler });
+ } catch (InvocationTargetException ex) {
+ throw Context.throwAsScriptRuntimeEx(ex);
+ } catch (IllegalAccessException ex) {
+ // Shouls not happen
+ throw Kit.initCause(new IllegalStateException(), ex);
+ } catch (InstantiationException ex) {
+ // Shouls not happen
+ throw Kit.initCause(new IllegalStateException(), ex);
+ }
+ return proxy;
+ }
+
+ @Override
+ protected boolean isVarArgs(Member member) {
+ return false;
+ }
+}
diff --git a/src/org/mozilla/javascript/jdk15/VMBridge_jdk15.java b/src/org/mozilla/javascript/jdk15/VMBridge_jdk15.java
new file mode 100644
index 0000000..f29b773
--- /dev/null
+++ b/src/org/mozilla/javascript/jdk15/VMBridge_jdk15.java
@@ -0,0 +1,89 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.jdk15;
+
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Constructor;
+import java.util.Iterator;
+import org.mozilla.javascript.*;
+
+public class VMBridge_jdk15 extends org.mozilla.javascript.jdk13.VMBridge_jdk13
+{
+ public VMBridge_jdk15() throws SecurityException, InstantiationException {
+ try {
+ // Just try and see if we can access the isVarArgs method.
+ // We want to fail loading if the method does not exist
+ // so that we can load a bridge to an older JDK instead.
+ Method.class.getMethod("isVarArgs", (Class[]) null);
+ } catch (NoSuchMethodException e) {
+ // Throw a fitting exception that is handled by
+ // org.mozilla.javascript.Kit.newInstanceOrNull:
+ throw new InstantiationException(e.getMessage());
+ }
+ }
+
+ @Override
+ public boolean isVarArgs(Member member) {
+ if (member instanceof Method)
+ return ((Method) member).isVarArgs();
+ else if (member instanceof Constructor)
+ return ((Constructor<?>) member).isVarArgs();
+ else
+ return false;
+ }
+
+ /**
+ * If "obj" is a java.util.Iterator or a java.lang.Iterable, return a
+ * wrapping as a JavaScript Iterator. Otherwise, return null.
+ * This method is in VMBridge since Iterable is a JDK 1.5 addition.
+ */
+ @Override
+ public Iterator<?> getJavaIterator(Context cx, Scriptable scope, Object obj) {
+ if (obj instanceof Wrapper) {
+ Object unwrapped = ((Wrapper) obj).unwrap();
+ Iterator<?> iterator = null;
+ if (unwrapped instanceof Iterator)
+ iterator = (Iterator<?>) unwrapped;
+ if (unwrapped instanceof Iterable)
+ iterator = ((Iterable<?>)unwrapped).iterator();
+ return iterator;
+ }
+ return null;
+ }
+}
diff --git a/src/org/mozilla/javascript/optimizer/Block.java b/src/org/mozilla/javascript/optimizer/Block.java
new file mode 100644
index 0000000..16439d7
--- /dev/null
+++ b/src/org/mozilla/javascript/optimizer/Block.java
@@ -0,0 +1,619 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Roger Lawrence
+ * Cameron McCormack
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+class Block
+{
+
+ private static class FatBlock
+ {
+
+ private static Block[] reduceToArray(ObjToIntMap map)
+ {
+ Block[] result = null;
+ if (!map.isEmpty()) {
+ result = new Block[map.size()];
+ int i = 0;
+ ObjToIntMap.Iterator iter = map.newIterator();
+ for (iter.start(); !iter.done(); iter.next()) {
+ FatBlock fb = (FatBlock)(iter.getKey());
+ result[i++] = fb.realBlock;
+ }
+ }
+ return result;
+ }
+
+ void addSuccessor(FatBlock b) { successors.put(b, 0); }
+ void addPredecessor(FatBlock b) { predecessors.put(b, 0); }
+
+ Block[] getSuccessors() { return reduceToArray(successors); }
+ Block[] getPredecessors() { return reduceToArray(predecessors); }
+
+ // all the Blocks that come immediately after this
+ private ObjToIntMap successors = new ObjToIntMap();
+ // all the Blocks that come immediately before this
+ private ObjToIntMap predecessors = new ObjToIntMap();
+
+ Block realBlock;
+ }
+
+ Block(int startNodeIndex, int endNodeIndex)
+ {
+ itsStartNodeIndex = startNodeIndex;
+ itsEndNodeIndex = endNodeIndex;
+ }
+
+ static void runFlowAnalyzes(OptFunctionNode fn, Node[] statementNodes)
+ {
+ int paramCount = fn.fnode.getParamCount();
+ int varCount = fn.fnode.getParamAndVarCount();
+ int[] varTypes = new int[varCount];
+ // If the variable is a parameter, it could have any type.
+ for (int i = 0; i != paramCount; ++i) {
+ varTypes[i] = Optimizer.AnyType;
+ }
+ // If the variable is from a "var" statement, its typeEvent will be set
+ // when we see the setVar node.
+ for (int i = paramCount; i != varCount; ++i) {
+ varTypes[i] = Optimizer.NoType;
+ }
+
+ Block[] theBlocks = buildBlocks(statementNodes);
+
+ if (DEBUG) {
+ ++debug_blockCount;
+ System.out.println("-------------------"+fn.fnode.getFunctionName()+" "+debug_blockCount+"--------");
+ System.out.println(toString(theBlocks, statementNodes));
+ }
+
+ reachingDefDataFlow(fn, statementNodes, theBlocks, varTypes);
+ typeFlow(fn, statementNodes, theBlocks, varTypes);
+
+ if (DEBUG) {
+ for (int i = 0; i < theBlocks.length; i++) {
+ System.out.println("For block " + theBlocks[i].itsBlockID);
+ theBlocks[i].printLiveOnEntrySet(fn);
+ }
+ System.out.println("Variable Table, size = " + varCount);
+ for (int i = 0; i != varCount; i++) {
+ System.out.println("["+i+"] type: "+varTypes[i]);
+ }
+ }
+
+ for (int i = paramCount; i != varCount; i++) {
+ if (varTypes[i] == Optimizer.NumberType) {
+ fn.setIsNumberVar(i);
+ }
+ }
+
+ }
+
+ private static Block[] buildBlocks(Node[] statementNodes)
+ {
+ // a mapping from each target node to the block it begins
+ Map<Node,FatBlock> theTargetBlocks = new HashMap<Node,FatBlock>();
+ ObjArray theBlocks = new ObjArray();
+
+ // there's a block that starts at index 0
+ int beginNodeIndex = 0;
+
+ for (int i = 0; i < statementNodes.length; i++) {
+ switch (statementNodes[i].getType()) {
+ case Token.TARGET :
+ {
+ if (i != beginNodeIndex) {
+ FatBlock fb = newFatBlock(beginNodeIndex, i - 1);
+ if (statementNodes[beginNodeIndex].getType()
+ == Token.TARGET)
+ theTargetBlocks.put(statementNodes[beginNodeIndex], fb);
+ theBlocks.add(fb);
+ // start the next block at this node
+ beginNodeIndex = i;
+ }
+ }
+ break;
+ case Token.IFNE :
+ case Token.IFEQ :
+ case Token.GOTO :
+ {
+ FatBlock fb = newFatBlock(beginNodeIndex, i);
+ if (statementNodes[beginNodeIndex].getType()
+ == Token.TARGET)
+ theTargetBlocks.put(statementNodes[beginNodeIndex], fb);
+ theBlocks.add(fb);
+ // start the next block at the next node
+ beginNodeIndex = i + 1;
+ }
+ break;
+ }
+ }
+
+ if (beginNodeIndex != statementNodes.length) {
+ FatBlock fb = newFatBlock(beginNodeIndex, statementNodes.length - 1);
+ if (statementNodes[beginNodeIndex].getType() == Token.TARGET)
+ theTargetBlocks.put(statementNodes[beginNodeIndex], fb);
+ theBlocks.add(fb);
+ }
+
+ // build successor and predecessor links
+
+ for (int i = 0; i < theBlocks.size(); i++) {
+ FatBlock fb = (FatBlock)(theBlocks.get(i));
+
+ Node blockEndNode = statementNodes[fb.realBlock.itsEndNodeIndex];
+ int blockEndNodeType = blockEndNode.getType();
+
+ if ((blockEndNodeType != Token.GOTO)
+ && (i < (theBlocks.size() - 1))) {
+ FatBlock fallThruTarget = (FatBlock)(theBlocks.get(i + 1));
+ fb.addSuccessor(fallThruTarget);
+ fallThruTarget.addPredecessor(fb);
+ }
+
+
+ if ( (blockEndNodeType == Token.IFNE)
+ || (blockEndNodeType == Token.IFEQ)
+ || (blockEndNodeType == Token.GOTO) ) {
+ Node target = ((Node.Jump)blockEndNode).target;
+ FatBlock branchTargetBlock = theTargetBlocks.get(target);
+ target.putProp(Node.TARGETBLOCK_PROP,
+ branchTargetBlock.realBlock);
+ fb.addSuccessor(branchTargetBlock);
+ branchTargetBlock.addPredecessor(fb);
+ }
+ }
+
+ Block[] result = new Block[theBlocks.size()];
+
+ for (int i = 0; i < theBlocks.size(); i++) {
+ FatBlock fb = (FatBlock)(theBlocks.get(i));
+ Block b = fb.realBlock;
+ b.itsSuccessors = fb.getSuccessors();
+ b.itsPredecessors = fb.getPredecessors();
+ b.itsBlockID = i;
+ result[i] = b;
+ }
+
+ return result;
+ }
+
+ private static FatBlock newFatBlock(int startNodeIndex, int endNodeIndex)
+ {
+ FatBlock fb = new FatBlock();
+ fb.realBlock = new Block(startNodeIndex, endNodeIndex);
+ return fb;
+ }
+
+ private static String toString(Block[] blockList, Node[] statementNodes)
+ {
+ if (!DEBUG) return null;
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ pw.println(blockList.length + " Blocks");
+ for (int i = 0; i < blockList.length; i++) {
+ Block b = blockList[i];
+ pw.println("#" + b.itsBlockID);
+ pw.println("from " + b.itsStartNodeIndex
+ + " "
+ + statementNodes[b.itsStartNodeIndex].toString());
+ pw.println("thru " + b.itsEndNodeIndex
+ + " "
+ + statementNodes[b.itsEndNodeIndex].toString());
+ pw.print("Predecessors ");
+ if (b.itsPredecessors != null) {
+ for (int j = 0; j < b.itsPredecessors.length; j++)
+ pw.print(b.itsPredecessors[j].itsBlockID + " ");
+ pw.println();
+ }
+ else
+ pw.println("none");
+ pw.print("Successors ");
+ if (b.itsSuccessors != null) {
+ for (int j = 0; j < b.itsSuccessors.length; j++)
+ pw.print(b.itsSuccessors[j].itsBlockID + " ");
+ pw.println();
+ }
+ else
+ pw.println("none");
+ }
+ return sw.toString();
+ }
+
+ private static void reachingDefDataFlow(OptFunctionNode fn, Node[] statementNodes, Block theBlocks[], int[] varTypes)
+ {
+/*
+ initialize the liveOnEntry and liveOnExit sets, then discover the variables
+ that are def'd by each function, and those that are used before being def'd
+ (hence liveOnEntry)
+*/
+ for (int i = 0; i < theBlocks.length; i++) {
+ theBlocks[i].initLiveOnEntrySets(fn, statementNodes);
+ }
+/*
+ this visits every block starting at the last, re-adding the predecessors of
+ any block whose inputs change as a result of the dataflow.
+ REMIND, better would be to visit in CFG postorder
+*/
+ boolean visit[] = new boolean[theBlocks.length];
+ boolean doneOnce[] = new boolean[theBlocks.length];
+ int vIndex = theBlocks.length - 1;
+ boolean needRescan = false;
+ visit[vIndex] = true;
+ while (true) {
+ if (visit[vIndex] || !doneOnce[vIndex]) {
+ doneOnce[vIndex] = true;
+ visit[vIndex] = false;
+ if (theBlocks[vIndex].doReachedUseDataFlow()) {
+ Block pred[] = theBlocks[vIndex].itsPredecessors;
+ if (pred != null) {
+ for (int i = 0; i < pred.length; i++) {
+ int index = pred[i].itsBlockID;
+ visit[index] = true;
+ needRescan |= (index > vIndex);
+ }
+ }
+ }
+ }
+ if (vIndex == 0) {
+ if (needRescan) {
+ vIndex = theBlocks.length - 1;
+ needRescan = false;
+ }
+ else
+ break;
+ }
+ else
+ vIndex--;
+ }
+/*
+ if any variable is live on entry to block 0, we have to mark it as
+ not jRegable - since it means that someone is trying to access the
+ 'undefined'-ness of that variable.
+*/
+
+ theBlocks[0].markAnyTypeVariables(varTypes);
+ }
+
+ private static void typeFlow(OptFunctionNode fn, Node[] statementNodes, Block theBlocks[], int[] varTypes)
+ {
+ boolean visit[] = new boolean[theBlocks.length];
+ boolean doneOnce[] = new boolean[theBlocks.length];
+ int vIndex = 0;
+ boolean needRescan = false;
+ visit[vIndex] = true;
+ while (true) {
+ if (visit[vIndex] || !doneOnce[vIndex]) {
+ doneOnce[vIndex] = true;
+ visit[vIndex] = false;
+ if (theBlocks[vIndex].doTypeFlow(fn, statementNodes, varTypes))
+ {
+ Block succ[] = theBlocks[vIndex].itsSuccessors;
+ if (succ != null) {
+ for (int i = 0; i < succ.length; i++) {
+ int index = succ[i].itsBlockID;
+ visit[index] = true;
+ needRescan |= (index < vIndex);
+ }
+ }
+ }
+ }
+ if (vIndex == (theBlocks.length - 1)) {
+ if (needRescan) {
+ vIndex = 0;
+ needRescan = false;
+ }
+ else
+ break;
+ }
+ else
+ vIndex++;
+ }
+ }
+
+ private static boolean assignType(int[] varTypes, int index, int type)
+ {
+ return type != (varTypes[index] |= type);
+ }
+
+ private void markAnyTypeVariables(int[] varTypes)
+ {
+ for (int i = 0; i != varTypes.length; i++) {
+ if (itsLiveOnEntrySet.test(i)) {
+ assignType(varTypes, i, Optimizer.AnyType);
+ }
+ }
+
+ }
+
+ /*
+ We're tracking uses and defs - in order to
+ build the def set and to identify the last use
+ nodes.
+
+ The itsNotDefSet is built reversed then flipped later.
+
+ */
+ private void lookForVariableAccess(OptFunctionNode fn, Node n)
+ {
+ switch (n.getType()) {
+ case Token.DEC :
+ case Token.INC :
+ {
+ Node child = n.getFirstChild();
+ if (child.getType() == Token.GETVAR) {
+ int varIndex = fn.getVarIndex(child);
+ if (!itsNotDefSet.test(varIndex))
+ itsUseBeforeDefSet.set(varIndex);
+ itsNotDefSet.set(varIndex);
+ }
+ }
+ break;
+ case Token.SETVAR :
+ {
+ Node lhs = n.getFirstChild();
+ Node rhs = lhs.getNext();
+ lookForVariableAccess(fn, rhs);
+ itsNotDefSet.set(fn.getVarIndex(n));
+ }
+ break;
+ case Token.GETVAR :
+ {
+ int varIndex = fn.getVarIndex(n);
+ if (!itsNotDefSet.test(varIndex))
+ itsUseBeforeDefSet.set(varIndex);
+ }
+ break;
+ default :
+ Node child = n.getFirstChild();
+ while (child != null) {
+ lookForVariableAccess(fn, child);
+ child = child.getNext();
+ }
+ break;
+ }
+ }
+
+ /*
+ build the live on entry/exit sets.
+ Then walk the trees looking for defs/uses of variables
+ and build the def and useBeforeDef sets.
+ */
+ private void initLiveOnEntrySets(OptFunctionNode fn, Node[] statementNodes)
+ {
+ int listLength = fn.getVarCount();
+ itsUseBeforeDefSet = new DataFlowBitSet(listLength);
+ itsNotDefSet = new DataFlowBitSet(listLength);
+ itsLiveOnEntrySet = new DataFlowBitSet(listLength);
+ itsLiveOnExitSet = new DataFlowBitSet(listLength);
+ for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) {
+ Node n = statementNodes[i];
+ lookForVariableAccess(fn, n);
+ }
+ itsNotDefSet.not(); // truth in advertising
+ }
+
+ /*
+ the liveOnEntry of each successor is the liveOnExit for this block.
+ The liveOnEntry for this block is -
+ liveOnEntry = liveOnExit - defsInThisBlock + useBeforeDefsInThisBlock
+
+ */
+ private boolean doReachedUseDataFlow()
+ {
+ itsLiveOnExitSet.clear();
+ if (itsSuccessors != null)
+ for (int i = 0; i < itsSuccessors.length; i++)
+ itsLiveOnExitSet.or(itsSuccessors[i].itsLiveOnEntrySet);
+ return itsLiveOnEntrySet.df2(itsLiveOnExitSet,
+ itsUseBeforeDefSet, itsNotDefSet);
+ }
+
+ /*
+ the type of an expression is relatively unknown. Cases we can be sure
+ about are -
+ Literals,
+ Arithmetic operations - always return a Number
+ */
+ private static int findExpressionType(OptFunctionNode fn, Node n,
+ int[] varTypes)
+ {
+ switch (n.getType()) {
+ case Token.NUMBER:
+ return Optimizer.NumberType;
+
+ case Token.CALL:
+ case Token.NEW:
+ case Token.REF_CALL:
+ return Optimizer.AnyType;
+
+ case Token.GETELEM:
+ return Optimizer.AnyType;
+
+ case Token.GETVAR:
+ return varTypes[fn.getVarIndex(n)];
+
+ case Token.INC:
+ case Token.DEC:
+ case Token.MUL:
+ case Token.DIV:
+ case Token.MOD:
+ case Token.BITOR:
+ case Token.BITXOR:
+ case Token.BITAND:
+ case Token.LSH:
+ case Token.RSH:
+ case Token.URSH:
+ case Token.SUB:
+ case Token.POS:
+ case Token.NEG:
+ return Optimizer.NumberType;
+
+ case Token.ARRAYLIT:
+ case Token.OBJECTLIT:
+ return Optimizer.AnyType; // XXX: actually, we know it's not
+ // number, but no type yet for that
+
+ case Token.ADD: {
+ // if the lhs & rhs are known to be numbers, we can be sure that's
+ // the result, otherwise it could be a string.
+ Node child = n.getFirstChild();
+ int lType = findExpressionType(fn, child, varTypes);
+ int rType = findExpressionType(fn, child.getNext(), varTypes);
+ return lType | rType; // we're not distinguishing strings yet
+ }
+ }
+
+ Node child = n.getFirstChild();
+ if (child == null) {
+ return Optimizer.AnyType;
+ } else {
+ int result = Optimizer.NoType;
+ while (child != null) {
+ result |= findExpressionType(fn, child, varTypes);
+ child = child.getNext();
+ }
+ return result;
+ }
+ }
+
+ private static boolean findDefPoints(OptFunctionNode fn, Node n,
+ int[] varTypes)
+ {
+ boolean result = false;
+ Node child = n.getFirstChild();
+ switch (n.getType()) {
+ default :
+ while (child != null) {
+ result |= findDefPoints(fn, child, varTypes);
+ child = child.getNext();
+ }
+ break;
+ case Token.DEC :
+ case Token.INC :
+ if (child.getType() == Token.GETVAR) {
+ // theVar is a Number now
+ int i = fn.getVarIndex(child);
+ result |= assignType(varTypes, i, Optimizer.NumberType);
+ }
+ break;
+ case Token.SETPROP :
+ case Token.SETPROP_OP :
+ if (child.getType() == Token.GETVAR) {
+ int i = fn.getVarIndex(child);
+ assignType(varTypes, i, Optimizer.AnyType);
+ }
+ while (child != null) {
+ result |= findDefPoints(fn, child, varTypes);
+ child = child.getNext();
+ }
+ break;
+ case Token.SETVAR : {
+ Node rValue = child.getNext();
+ int theType = findExpressionType(fn, rValue, varTypes);
+ int i = fn.getVarIndex(n);
+ result |= assignType(varTypes, i, theType);
+ break;
+ }
+ }
+ return result;
+ }
+
+ private boolean doTypeFlow(OptFunctionNode fn, Node[] statementNodes,
+ int[] varTypes)
+ {
+ boolean changed = false;
+
+ for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) {
+ Node n = statementNodes[i];
+ if (n != null)
+ changed |= findDefPoints(fn, n, varTypes);
+ }
+
+ return changed;
+ }
+
+ private void printLiveOnEntrySet(OptFunctionNode fn)
+ {
+ if (DEBUG) {
+ for (int i = 0; i < fn.getVarCount(); i++) {
+ String name = fn.fnode.getParamOrVarName(i);
+ if (itsUseBeforeDefSet.test(i))
+ System.out.println(name + " is used before def'd");
+ if (itsNotDefSet.test(i))
+ System.out.println(name + " is not def'd");
+ if (itsLiveOnEntrySet.test(i))
+ System.out.println(name + " is live on entry");
+ if (itsLiveOnExitSet.test(i))
+ System.out.println(name + " is live on exit");
+ }
+ }
+ }
+
+ // all the Blocks that come immediately after this
+ private Block[] itsSuccessors;
+ // all the Blocks that come immediately before this
+ private Block[] itsPredecessors;
+
+ private int itsStartNodeIndex; // the Node at the start of the block
+ private int itsEndNodeIndex; // the Node at the end of the block
+
+ private int itsBlockID; // a unique index for each block
+
+// reaching def bit sets -
+ private DataFlowBitSet itsLiveOnEntrySet;
+ private DataFlowBitSet itsLiveOnExitSet;
+ private DataFlowBitSet itsUseBeforeDefSet;
+ private DataFlowBitSet itsNotDefSet;
+
+ static final boolean DEBUG = false;
+ private static int debug_blockCount;
+
+}
+
diff --git a/src/org/mozilla/javascript/optimizer/ClassCompiler.java b/src/org/mozilla/javascript/optimizer/ClassCompiler.java
new file mode 100644
index 0000000..35c9131
--- /dev/null
+++ b/src/org/mozilla/javascript/optimizer/ClassCompiler.java
@@ -0,0 +1,211 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+
+/**
+ * Generates class files from script sources.
+ *
+ * since 1.5 Release 5
+ * @author Igor Bukanov
+ */
+
+public class ClassCompiler
+{
+ /**
+ * Construct ClassCompiler that uses the specified compiler environment
+ * when generating classes.
+ */
+ public ClassCompiler(CompilerEnvirons compilerEnv)
+ {
+ if (compilerEnv == null) throw new IllegalArgumentException();
+ this.compilerEnv = compilerEnv;
+ this.mainMethodClassName = Codegen.DEFAULT_MAIN_METHOD_CLASS;
+ }
+
+ /**
+ * Set the class name to use for main method implementation.
+ * The class must have a method matching
+ * <tt>public static void main(Script sc, String[] args)</tt>, it will be
+ * called when <tt>main(String[] args)</tt> is called in the generated
+ * class. The class name should be fully qulified name and include the
+ * package name like in <tt>org.foo.Bar<tt>.
+ */
+ public void setMainMethodClass(String className)
+ {
+ // XXX Should this check for a valid class name?
+ mainMethodClassName = className;
+ }
+
+ /**
+ * Get the name of the class for main method implementation.
+ * @see #setMainMethodClass(String)
+ */
+ public String getMainMethodClass()
+ {
+ return mainMethodClassName;
+ }
+
+ /**
+ * Get the compiler environment the compiler uses.
+ */
+ public CompilerEnvirons getCompilerEnv()
+ {
+ return compilerEnv;
+ }
+
+ /**
+ * Get the class that the generated target will extend.
+ */
+ public Class<?> getTargetExtends()
+ {
+ return targetExtends;
+ }
+
+ /**
+ * Set the class that the generated target will extend.
+ *
+ * @param extendsClass the class it extends
+ */
+ public void setTargetExtends(Class<?> extendsClass)
+ {
+ targetExtends = extendsClass;
+ }
+
+ /**
+ * Get the interfaces that the generated target will implement.
+ */
+ public Class<?>[] getTargetImplements()
+ {
+ return targetImplements == null ? null : (Class[])targetImplements.clone();
+ }
+
+ /**
+ * Set the interfaces that the generated target will implement.
+ *
+ * @param implementsClasses an array of Class objects, one for each
+ * interface the target will extend
+ */
+ public void setTargetImplements(Class<?>[] implementsClasses)
+ {
+ targetImplements = implementsClasses == null ? null : (Class[])implementsClasses.clone();
+ }
+
+ /**
+ * Build class name for a auxiliary class generated by compiler.
+ * If the compiler needs to generate extra classes beyond the main class,
+ * it will call this function to build the auxiliary class name.
+ * The default implementation simply appends auxMarker to mainClassName
+ * but this can be overridden.
+ */
+ protected String makeAuxiliaryClassName(String mainClassName,
+ String auxMarker)
+ {
+ return mainClassName+auxMarker;
+ }
+
+ /**
+ * Compile JavaScript source into one or more Java class files.
+ * The first compiled class will have name mainClassName.
+ * If the results of {@link #getTargetExtends()} or
+ * {@link #getTargetImplements()} are not null, then the first compiled
+ * class will extend the specified super class and implement
+ * specified interfaces.
+ *
+ * @return array where elements with even indexes specifies class name
+ * and the following odd index gives class file body as byte[]
+ * array. The initial element of the array always holds
+ * mainClassName and array[1] holds its byte code.
+ */
+ public Object[] compileToClassFiles(String source,
+ String sourceLocation,
+ int lineno,
+ String mainClassName)
+ {
+ Parser p = new Parser(compilerEnv, compilerEnv.getErrorReporter());
+ ScriptOrFnNode tree = p.parse(source, sourceLocation, lineno);
+ String encodedSource = p.getEncodedSource();
+
+ Class<?> superClass = getTargetExtends();
+ Class<?>[] interfaces = getTargetImplements();
+ String scriptClassName;
+ boolean isPrimary = (interfaces == null && superClass == null);
+ if (isPrimary) {
+ scriptClassName = mainClassName;
+ } else {
+ scriptClassName = makeAuxiliaryClassName(mainClassName, "1");
+ }
+
+ Codegen codegen = new Codegen();
+ codegen.setMainMethodClass(mainMethodClassName);
+ byte[] scriptClassBytes
+ = codegen.compileToClassFile(compilerEnv, scriptClassName,
+ tree, encodedSource,
+ false);
+
+ if (isPrimary) {
+ return new Object[] { scriptClassName, scriptClassBytes };
+ }
+ int functionCount = tree.getFunctionCount();
+ ObjToIntMap functionNames = new ObjToIntMap(functionCount);
+ for (int i = 0; i != functionCount; ++i) {
+ FunctionNode ofn = tree.getFunctionNode(i);
+ String name = ofn.getFunctionName();
+ if (name != null && name.length() != 0) {
+ functionNames.put(name, ofn.getParamCount());
+ }
+ }
+ if (superClass == null) {
+ superClass = ScriptRuntime.ObjectClass;
+ }
+ byte[] mainClassBytes
+ = JavaAdapter.createAdapterCode(
+ functionNames, mainClassName,
+ superClass, interfaces, scriptClassName);
+
+ return new Object[] { mainClassName, mainClassBytes,
+ scriptClassName, scriptClassBytes };
+ }
+
+ private String mainMethodClassName;
+ private CompilerEnvirons compilerEnv;
+ private Class<?> targetExtends;
+ private Class<?>[] targetImplements;
+
+}
+
diff --git a/src/org/mozilla/javascript/optimizer/Codegen.java b/src/org/mozilla/javascript/optimizer/Codegen.java
new file mode 100644
index 0000000..a3663ae
--- /dev/null
+++ b/src/org/mozilla/javascript/optimizer/Codegen.java
@@ -0,0 +1,5048 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Kemal Bayram
+ * Igor Bukanov
+ * Bob Jervis
+ * Roger Lawrence
+ * Andi Vajda
+ * Hannes Wallnoefer
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+import org.mozilla.classfile.*;
+import java.util.*;
+import java.lang.reflect.Constructor;
+
+/**
+ * This class generates code for a given IR tree.
+ *
+ * @author Norris Boyd
+ * @author Roger Lawrence
+ */
+
+public class Codegen implements Evaluator
+{
+ public void captureStackInfo(RhinoException ex) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getSourcePositionFromStack(Context cx, int[] linep) {
+ throw new UnsupportedOperationException();
+ }
+
+ public String getPatchedStack(RhinoException ex, String nativeStackTrace) {
+ throw new UnsupportedOperationException();
+ }
+
+ public List<String> getScriptStack(RhinoException ex) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setEvalScriptFlag(Script script) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object compile(CompilerEnvirons compilerEnv,
+ ScriptOrFnNode tree,
+ String encodedSource,
+ boolean returnFunction)
+ {
+ int serial;
+ synchronized (globalLock) {
+ serial = ++globalSerialClassCounter;
+ }
+ String mainClassName = "org.mozilla.javascript.gen.c"+serial;
+
+ byte[] mainClassBytes = compileToClassFile(compilerEnv, mainClassName,
+ tree, encodedSource,
+ returnFunction);
+
+ return new Object[] { mainClassName, mainClassBytes };
+ }
+
+ public Script createScriptObject(Object bytecode,
+ Object staticSecurityDomain)
+ {
+ Class<?> cl = defineClass(bytecode, staticSecurityDomain);
+
+ Script script;
+ try {
+ script = (Script)cl.newInstance();
+ } catch (Exception ex) {
+ throw new RuntimeException
+ ("Unable to instantiate compiled class:"+ex.toString());
+ }
+ return script;
+ }
+
+ public Function createFunctionObject(Context cx, Scriptable scope,
+ Object bytecode,
+ Object staticSecurityDomain)
+ {
+ Class<?> cl = defineClass(bytecode, staticSecurityDomain);
+
+ NativeFunction f;
+ try {
+ Constructor<?>ctor = cl.getConstructors()[0];
+ Object[] initArgs = { scope, cx, new Integer(0) };
+ f = (NativeFunction)ctor.newInstance(initArgs);
+ } catch (Exception ex) {
+ throw new RuntimeException
+ ("Unable to instantiate compiled class:"+ex.toString());
+ }
+ return f;
+ }
+
+ private Class<?> defineClass(Object bytecode,
+ Object staticSecurityDomain)
+ {
+ Object[] nameBytesPair = (Object[])bytecode;
+ String className = (String)nameBytesPair[0];
+ byte[] classBytes = (byte[])nameBytesPair[1];
+
+ // The generated classes in this case refer only to Rhino classes
+ // which must be accessible through this class loader
+ ClassLoader rhinoLoader = getClass().getClassLoader();
+ GeneratedClassLoader loader;
+ loader = SecurityController.createLoader(rhinoLoader,
+ staticSecurityDomain);
+ Exception e;
+ try {
+ Class<?> cl = loader.defineClass(className, classBytes);
+ loader.linkClass(cl);
+ return cl;
+ } catch (SecurityException x) {
+ e = x;
+ } catch (IllegalArgumentException x) {
+ e = x;
+ }
+ throw new RuntimeException("Malformed optimizer package " + e);
+ }
+
+ byte[] compileToClassFile(CompilerEnvirons compilerEnv,
+ String mainClassName,
+ ScriptOrFnNode scriptOrFn,
+ String encodedSource,
+ boolean returnFunction)
+ {
+ this.compilerEnv = compilerEnv;
+
+ transform(scriptOrFn);
+
+ if (Token.printTrees) {
+ System.out.println(scriptOrFn.toStringTree(scriptOrFn));
+ }
+
+ if (returnFunction) {
+ scriptOrFn = scriptOrFn.getFunctionNode(0);
+ }
+
+ initScriptOrFnNodesData(scriptOrFn);
+
+ this.mainClassName = mainClassName;
+ this.mainClassSignature
+ = ClassFileWriter.classNameToSignature(mainClassName);
+
+ try {
+ return generateCode(encodedSource);
+ } catch (ClassFileWriter.ClassFileFormatException e) {
+ throw reportClassFileFormatException(scriptOrFn, e.getMessage());
+ }
+ }
+
+ private RuntimeException reportClassFileFormatException(
+ ScriptOrFnNode scriptOrFn,
+ String message)
+ {
+ String msg = scriptOrFn instanceof FunctionNode
+ ? ScriptRuntime.getMessage2("msg.while.compiling.fn",
+ ((FunctionNode)scriptOrFn).getFunctionName(), message)
+ : ScriptRuntime.getMessage1("msg.while.compiling.script", message);
+ return Context.reportRuntimeError(msg, scriptOrFn.getSourceName(),
+ scriptOrFn.getLineno(), null, 0);
+ }
+
+ private void transform(ScriptOrFnNode tree)
+ {
+ initOptFunctions_r(tree);
+
+ int optLevel = compilerEnv.getOptimizationLevel();
+
+ Map<String,OptFunctionNode> possibleDirectCalls = null;
+ if (optLevel > 0) {
+ /*
+ * Collect all of the contained functions into a hashtable
+ * so that the call optimizer can access the class name & parameter
+ * count for any call it encounters
+ */
+ if (tree.getType() == Token.SCRIPT) {
+ int functionCount = tree.getFunctionCount();
+ for (int i = 0; i != functionCount; ++i) {
+ OptFunctionNode ofn = OptFunctionNode.get(tree, i);
+ if (ofn.fnode.getFunctionType()
+ == FunctionNode.FUNCTION_STATEMENT)
+ {
+ String name = ofn.fnode.getFunctionName();
+ if (name.length() != 0) {
+ if (possibleDirectCalls == null) {
+ possibleDirectCalls = new HashMap<String,OptFunctionNode>();
+ }
+ possibleDirectCalls.put(name, ofn);
+ }
+ }
+ }
+ }
+ }
+
+ if (possibleDirectCalls != null) {
+ directCallTargets = new ObjArray();
+ }
+
+ OptTransformer ot = new OptTransformer(possibleDirectCalls,
+ directCallTargets);
+ ot.transform(tree);
+
+ if (optLevel > 0) {
+ (new Optimizer()).optimize(tree);
+ }
+ }
+
+ private static void initOptFunctions_r(ScriptOrFnNode scriptOrFn)
+ {
+ for (int i = 0, N = scriptOrFn.getFunctionCount(); i != N; ++i) {
+ FunctionNode fn = scriptOrFn.getFunctionNode(i);
+ new OptFunctionNode(fn);
+ initOptFunctions_r(fn);
+ }
+ }
+
+ private void initScriptOrFnNodesData(ScriptOrFnNode scriptOrFn)
+ {
+ ObjArray x = new ObjArray();
+ collectScriptOrFnNodes_r(scriptOrFn, x);
+
+ int count = x.size();
+ scriptOrFnNodes = new ScriptOrFnNode[count];
+ x.toArray(scriptOrFnNodes);
+
+ scriptOrFnIndexes = new ObjToIntMap(count);
+ for (int i = 0; i != count; ++i) {
+ scriptOrFnIndexes.put(scriptOrFnNodes[i], i);
+ }
+ }
+
+ private static void collectScriptOrFnNodes_r(ScriptOrFnNode n,
+ ObjArray x)
+ {
+ x.add(n);
+ int nestedCount = n.getFunctionCount();
+ for (int i = 0; i != nestedCount; ++i) {
+ collectScriptOrFnNodes_r(n.getFunctionNode(i), x);
+ }
+ }
+
+ private byte[] generateCode(String encodedSource)
+ {
+ boolean hasScript = (scriptOrFnNodes[0].getType() == Token.SCRIPT);
+ boolean hasFunctions = (scriptOrFnNodes.length > 1 || !hasScript);
+
+ String sourceFile = null;
+ if (compilerEnv.isGenerateDebugInfo()) {
+ sourceFile = scriptOrFnNodes[0].getSourceName();
+ }
+
+ ClassFileWriter cfw = new ClassFileWriter(mainClassName,
+ SUPER_CLASS_NAME,
+ sourceFile);
+ cfw.addField(ID_FIELD_NAME, "I",
+ ClassFileWriter.ACC_PRIVATE);
+ cfw.addField(DIRECT_CALL_PARENT_FIELD, mainClassSignature,
+ ClassFileWriter.ACC_PRIVATE);
+ cfw.addField(REGEXP_ARRAY_FIELD_NAME, REGEXP_ARRAY_FIELD_TYPE,
+ ClassFileWriter.ACC_PRIVATE);
+
+ if (hasFunctions) {
+ generateFunctionConstructor(cfw);
+ }
+
+ if (hasScript) {
+ cfw.addInterface("org/mozilla/javascript/Script");
+ generateScriptCtor(cfw);
+ generateMain(cfw);
+ generateExecute(cfw);
+ }
+
+ generateCallMethod(cfw);
+ generateResumeGenerator(cfw);
+
+ generateNativeFunctionOverrides(cfw, encodedSource);
+
+ int count = scriptOrFnNodes.length;
+ for (int i = 0; i != count; ++i) {
+ ScriptOrFnNode n = scriptOrFnNodes[i];
+
+ BodyCodegen bodygen = new BodyCodegen();
+ bodygen.cfw = cfw;
+ bodygen.codegen = this;
+ bodygen.compilerEnv = compilerEnv;
+ bodygen.scriptOrFn = n;
+ bodygen.scriptOrFnIndex = i;
+
+ try {
+ bodygen.generateBodyCode();
+ } catch (ClassFileWriter.ClassFileFormatException e) {
+ throw reportClassFileFormatException(n, e.getMessage());
+ }
+
+ if (n.getType() == Token.FUNCTION) {
+ OptFunctionNode ofn = OptFunctionNode.get(n);
+ generateFunctionInit(cfw, ofn);
+ if (ofn.isTargetOfDirectCall()) {
+ emitDirectConstructor(cfw, ofn);
+ }
+ }
+ }
+
+ if (directCallTargets != null) {
+ int N = directCallTargets.size();
+ for (int j = 0; j != N; ++j) {
+ cfw.addField(getDirectTargetFieldName(j),
+ mainClassSignature,
+ ClassFileWriter.ACC_PRIVATE);
+ }
+ }
+
+ emitRegExpInit(cfw);
+ emitConstantDudeInitializers(cfw);
+
+ return cfw.toByteArray();
+ }
+
+ private void emitDirectConstructor(ClassFileWriter cfw,
+ OptFunctionNode ofn)
+ {
+/*
+ we generate ..
+ Scriptable directConstruct(<directCallArgs>) {
+ Scriptable newInstance = createObject(cx, scope);
+ Object val = <body-name>(cx, scope, newInstance, <directCallArgs>);
+ if (val instanceof Scriptable) {
+ return (Scriptable) val;
+ }
+ return newInstance;
+ }
+*/
+ cfw.startMethod(getDirectCtorName(ofn.fnode),
+ getBodyMethodSignature(ofn.fnode),
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+
+ int argCount = ofn.fnode.getParamCount();
+ int firstLocal = (4 + argCount * 3) + 1;
+
+ cfw.addALoad(0); // this
+ cfw.addALoad(1); // cx
+ cfw.addALoad(2); // scope
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
+ "org/mozilla/javascript/BaseFunction",
+ "createObject",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(firstLocal);
+
+ cfw.addALoad(0);
+ cfw.addALoad(1);
+ cfw.addALoad(2);
+ cfw.addALoad(firstLocal);
+ for (int i = 0; i < argCount; i++) {
+ cfw.addALoad(4 + (i * 3));
+ cfw.addDLoad(5 + (i * 3));
+ }
+ cfw.addALoad(4 + argCount * 3);
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ mainClassName,
+ getBodyMethodName(ofn.fnode),
+ getBodyMethodSignature(ofn.fnode));
+ int exitLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.DUP); // make a copy of direct call result
+ cfw.add(ByteCode.INSTANCEOF, "org/mozilla/javascript/Scriptable");
+ cfw.add(ByteCode.IFEQ, exitLabel);
+ // cast direct call result
+ cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/Scriptable");
+ cfw.add(ByteCode.ARETURN);
+ cfw.markLabel(exitLabel);
+
+ cfw.addALoad(firstLocal);
+ cfw.add(ByteCode.ARETURN);
+
+ cfw.stopMethod((short)(firstLocal + 1));
+ }
+
+ static boolean isGenerator(ScriptOrFnNode node)
+ {
+ return (node.getType() == Token.FUNCTION ) &&
+ ((FunctionNode)node).isGenerator();
+ }
+
+ // How dispatch to generators works:
+ // Two methods are generated corresponding to a user-written generator.
+ // One of these creates a generator object (NativeGenerator), which is
+ // returned to the user. The other method contains all of the body code
+ // of the generator.
+ // When a user calls a generator, the call() method dispatches control to
+ // to the method that creates the NativeGenerator object. Subsequently when
+ // the user invokes .next(), .send() or any such method on the generator
+ // object, the resumeGenerator() below dispatches the call to the
+ // method corresponding to the generator body. As a matter of convention
+ // the generator body is given the name of the generator activation function
+ // appended by "_gen".
+ private void generateResumeGenerator(ClassFileWriter cfw)
+ {
+ boolean hasGenerators = false;
+ for (int i=0; i < scriptOrFnNodes.length; i++) {
+ if (isGenerator(scriptOrFnNodes[i]))
+ hasGenerators = true;
+ }
+
+ // if there are no generators defined, we don't implement a
+ // resumeGenerator(). The base class provides a default implementation.
+ if (!hasGenerators)
+ return;
+
+ cfw.startMethod("resumeGenerator",
+ "(Lorg/mozilla/javascript/Context;" +
+ "Lorg/mozilla/javascript/Scriptable;" +
+ "ILjava/lang/Object;" +
+ "Ljava/lang/Object;)Ljava/lang/Object;",
+ (short)(ClassFileWriter.ACC_PUBLIC
+ | ClassFileWriter.ACC_FINAL));
+
+ // load arguments for dispatch to the corresponding *_gen method
+ cfw.addALoad(0);
+ cfw.addALoad(1);
+ cfw.addALoad(2);
+ cfw.addALoad(4);
+ cfw.addALoad(5);
+ cfw.addILoad(3);
+
+ cfw.addLoadThis();
+ cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");
+
+ int startSwitch = cfw.addTableSwitch(0, scriptOrFnNodes.length - 1);
+ cfw.markTableSwitchDefault(startSwitch);
+ int endlabel = cfw.acquireLabel();
+
+ for (int i = 0; i < scriptOrFnNodes.length; i++) {
+ ScriptOrFnNode n = scriptOrFnNodes[i];
+ cfw.markTableSwitchCase(startSwitch, i, (short)6);
+ if (isGenerator(n)) {
+ String type = "(" +
+ mainClassSignature +
+ "Lorg/mozilla/javascript/Context;" +
+ "Lorg/mozilla/javascript/Scriptable;" +
+ "Ljava/lang/Object;" +
+ "Ljava/lang/Object;I)Ljava/lang/Object;";
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ mainClassName,
+ getBodyMethodName(n) + "_gen",
+ type);
+ cfw.add(ByteCode.ARETURN);
+ } else {
+ cfw.add(ByteCode.GOTO, endlabel);
+ }
+ }
+
+ cfw.markLabel(endlabel);
+ pushUndefined(cfw);
+ cfw.add(ByteCode.ARETURN);
+
+
+ // this method uses as many locals as there are arguments (hence 6)
+ cfw.stopMethod((short)6);
+ }
+
+ private void generateCallMethod(ClassFileWriter cfw)
+ {
+ cfw.startMethod("call",
+ "(Lorg/mozilla/javascript/Context;" +
+ "Lorg/mozilla/javascript/Scriptable;" +
+ "Lorg/mozilla/javascript/Scriptable;" +
+ "[Ljava/lang/Object;)Ljava/lang/Object;",
+ (short)(ClassFileWriter.ACC_PUBLIC
+ | ClassFileWriter.ACC_FINAL));
+
+ // Generate code for:
+ // if (!ScriptRuntime.hasTopCall(cx)) {
+ // return ScriptRuntime.doTopCall(this, cx, scope, thisObj, args);
+ // }
+
+ int nonTopCallLabel = cfw.acquireLabel();
+ cfw.addALoad(1); //cx
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "hasTopCall",
+ "(Lorg/mozilla/javascript/Context;"
+ +")Z");
+ cfw.add(ByteCode.IFNE, nonTopCallLabel);
+ cfw.addALoad(0);
+ cfw.addALoad(1);
+ cfw.addALoad(2);
+ cfw.addALoad(3);
+ cfw.addALoad(4);
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "doTopCall",
+ "(Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Ljava/lang/Object;");
+ cfw.add(ByteCode.ARETURN);
+ cfw.markLabel(nonTopCallLabel);
+
+ // Now generate switch to call the real methods
+ cfw.addALoad(0);
+ cfw.addALoad(1);
+ cfw.addALoad(2);
+ cfw.addALoad(3);
+ cfw.addALoad(4);
+
+ int end = scriptOrFnNodes.length;
+ boolean generateSwitch = (2 <= end);
+
+ int switchStart = 0;
+ int switchStackTop = 0;
+ if (generateSwitch) {
+ cfw.addLoadThis();
+ cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");
+ // do switch from (1, end - 1) mapping 0 to
+ // the default case
+ switchStart = cfw.addTableSwitch(1, end - 1);
+ }
+
+ for (int i = 0; i != end; ++i) {
+ ScriptOrFnNode n = scriptOrFnNodes[i];
+ if (generateSwitch) {
+ if (i == 0) {
+ cfw.markTableSwitchDefault(switchStart);
+ switchStackTop = cfw.getStackTop();
+ } else {
+ cfw.markTableSwitchCase(switchStart, i - 1,
+ switchStackTop);
+ }
+ }
+ if (n.getType() == Token.FUNCTION) {
+ OptFunctionNode ofn = OptFunctionNode.get(n);
+ if (ofn.isTargetOfDirectCall()) {
+ int pcount = ofn.fnode.getParamCount();
+ if (pcount != 0) {
+ // loop invariant:
+ // stack top == arguments array from addALoad4()
+ for (int p = 0; p != pcount; ++p) {
+ cfw.add(ByteCode.ARRAYLENGTH);
+ cfw.addPush(p);
+ int undefArg = cfw.acquireLabel();
+ int beyond = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ICMPLE, undefArg);
+ // get array[p]
+ cfw.addALoad(4);
+ cfw.addPush(p);
+ cfw.add(ByteCode.AALOAD);
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(undefArg);
+ pushUndefined(cfw);
+ cfw.markLabel(beyond);
+ // Only one push
+ cfw.adjustStackTop(-1);
+ cfw.addPush(0.0);
+ // restore invariant
+ cfw.addALoad(4);
+ }
+ }
+ }
+ }
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ mainClassName,
+ getBodyMethodName(n),
+ getBodyMethodSignature(n));
+ cfw.add(ByteCode.ARETURN);
+ }
+ cfw.stopMethod((short)5);
+ // 5: this, cx, scope, js this, args[]
+ }
+
+ private void generateMain(ClassFileWriter cfw)
+ {
+ cfw.startMethod("main", "([Ljava/lang/String;)V",
+ (short)(ClassFileWriter.ACC_PUBLIC
+ | ClassFileWriter.ACC_STATIC));
+
+ // load new ScriptImpl()
+ cfw.add(ByteCode.NEW, cfw.getClassName());
+ cfw.add(ByteCode.DUP);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, cfw.getClassName(),
+ "<init>", "()V");
+ // load 'args'
+ cfw.add(ByteCode.ALOAD_0);
+ // Call mainMethodClass.main(Script script, String[] args)
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ mainMethodClass,
+ "main",
+ "(Lorg/mozilla/javascript/Script;[Ljava/lang/String;)V");
+ cfw.add(ByteCode.RETURN);
+ // 1 = String[] args
+ cfw.stopMethod((short)1);
+ }
+
+ private void generateExecute(ClassFileWriter cfw)
+ {
+ cfw.startMethod("exec",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;",
+ (short)(ClassFileWriter.ACC_PUBLIC
+ | ClassFileWriter.ACC_FINAL));
+
+ final int CONTEXT_ARG = 1;
+ final int SCOPE_ARG = 2;
+
+ cfw.addLoadThis();
+ cfw.addALoad(CONTEXT_ARG);
+ cfw.addALoad(SCOPE_ARG);
+ cfw.add(ByteCode.DUP);
+ cfw.add(ByteCode.ACONST_NULL);
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
+ cfw.getClassName(),
+ "call",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Ljava/lang/Object;");
+
+ cfw.add(ByteCode.ARETURN);
+ // 3 = this + context + scope
+ cfw.stopMethod((short)3);
+ }
+
+ private void generateScriptCtor(ClassFileWriter cfw)
+ {
+ cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC);
+
+ cfw.addLoadThis();
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME,
+ "<init>", "()V");
+ // set id to 0
+ cfw.addLoadThis();
+ cfw.addPush(0);
+ cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");
+
+ cfw.add(ByteCode.RETURN);
+ // 1 parameter = this
+ cfw.stopMethod((short)1);
+ }
+
+ private void generateFunctionConstructor(ClassFileWriter cfw)
+ {
+ final int SCOPE_ARG = 1;
+ final int CONTEXT_ARG = 2;
+ final int ID_ARG = 3;
+
+ cfw.startMethod("<init>", FUNCTION_CONSTRUCTOR_SIGNATURE,
+ ClassFileWriter.ACC_PUBLIC);
+ cfw.addALoad(0);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME,
+ "<init>", "()V");
+
+ cfw.addLoadThis();
+ cfw.addILoad(ID_ARG);
+ cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");
+
+ cfw.addLoadThis();
+ cfw.addALoad(CONTEXT_ARG);
+ cfw.addALoad(SCOPE_ARG);
+
+ int start = (scriptOrFnNodes[0].getType() == Token.SCRIPT) ? 1 : 0;
+ int end = scriptOrFnNodes.length;
+ if (start == end) throw badTree();
+ boolean generateSwitch = (2 <= end - start);
+
+ int switchStart = 0;
+ int switchStackTop = 0;
+ if (generateSwitch) {
+ cfw.addILoad(ID_ARG);
+ // do switch from (start + 1, end - 1) mapping start to
+ // the default case
+ switchStart = cfw.addTableSwitch(start + 1, end - 1);
+ }
+
+ for (int i = start; i != end; ++i) {
+ if (generateSwitch) {
+ if (i == start) {
+ cfw.markTableSwitchDefault(switchStart);
+ switchStackTop = cfw.getStackTop();
+ } else {
+ cfw.markTableSwitchCase(switchStart, i - 1 - start,
+ switchStackTop);
+ }
+ }
+ OptFunctionNode ofn = OptFunctionNode.get(scriptOrFnNodes[i]);
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
+ mainClassName,
+ getFunctionInitMethodName(ofn),
+ FUNCTION_INIT_SIGNATURE);
+ cfw.add(ByteCode.RETURN);
+ }
+
+ // 4 = this + scope + context + id
+ cfw.stopMethod((short)4);
+ }
+
+ private void generateFunctionInit(ClassFileWriter cfw,
+ OptFunctionNode ofn)
+ {
+ final int CONTEXT_ARG = 1;
+ final int SCOPE_ARG = 2;
+ cfw.startMethod(getFunctionInitMethodName(ofn),
+ FUNCTION_INIT_SIGNATURE,
+ (short)(ClassFileWriter.ACC_PRIVATE
+ | ClassFileWriter.ACC_FINAL));
+
+ // Call NativeFunction.initScriptFunction
+ cfw.addLoadThis();
+ cfw.addALoad(CONTEXT_ARG);
+ cfw.addALoad(SCOPE_ARG);
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
+ "org/mozilla/javascript/NativeFunction",
+ "initScriptFunction",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")V");
+
+ // precompile all regexp literals
+ int regexpCount = ofn.fnode.getRegexpCount();
+ if (regexpCount != 0) {
+ cfw.addLoadThis();
+ pushRegExpArray(cfw, ofn.fnode, CONTEXT_ARG, SCOPE_ARG);
+ cfw.add(ByteCode.PUTFIELD, mainClassName,
+ REGEXP_ARRAY_FIELD_NAME, REGEXP_ARRAY_FIELD_TYPE);
+ }
+
+ cfw.add(ByteCode.RETURN);
+ // 3 = (scriptThis/functionRef) + scope + context
+ cfw.stopMethod((short)3);
+ }
+
+ private void generateNativeFunctionOverrides(ClassFileWriter cfw,
+ String encodedSource)
+ {
+ // Override NativeFunction.getLanguageVersion() with
+ // public int getLanguageVersion() { return <version-constant>; }
+
+ cfw.startMethod("getLanguageVersion", "()I",
+ ClassFileWriter.ACC_PUBLIC);
+
+ cfw.addPush(compilerEnv.getLanguageVersion());
+ cfw.add(ByteCode.IRETURN);
+
+ // 1: this and no argument or locals
+ cfw.stopMethod((short)1);
+
+ // The rest of NativeFunction overrides require specific code for each
+ // script/function id
+
+ final int Do_getFunctionName = 0;
+ final int Do_getParamCount = 1;
+ final int Do_getParamAndVarCount = 2;
+ final int Do_getParamOrVarName = 3;
+ final int Do_getEncodedSource = 4;
+ final int Do_getParamOrVarConst = 5;
+ final int SWITCH_COUNT = 6;
+
+ for (int methodIndex = 0; methodIndex != SWITCH_COUNT; ++methodIndex) {
+ if (methodIndex == Do_getEncodedSource && encodedSource == null) {
+ continue;
+ }
+
+ // Generate:
+ // prologue;
+ // switch over function id to implement function-specific action
+ // epilogue
+
+ short methodLocals;
+ switch (methodIndex) {
+ case Do_getFunctionName:
+ methodLocals = 1; // Only this
+ cfw.startMethod("getFunctionName", "()Ljava/lang/String;",
+ ClassFileWriter.ACC_PUBLIC);
+ break;
+ case Do_getParamCount:
+ methodLocals = 1; // Only this
+ cfw.startMethod("getParamCount", "()I",
+ ClassFileWriter.ACC_PUBLIC);
+ break;
+ case Do_getParamAndVarCount:
+ methodLocals = 1; // Only this
+ cfw.startMethod("getParamAndVarCount", "()I",
+ ClassFileWriter.ACC_PUBLIC);
+ break;
+ case Do_getParamOrVarName:
+ methodLocals = 1 + 1; // this + paramOrVarIndex
+ cfw.startMethod("getParamOrVarName", "(I)Ljava/lang/String;",
+ ClassFileWriter.ACC_PUBLIC);
+ break;
+ case Do_getParamOrVarConst:
+ methodLocals = 1 + 1 + 1; // this + paramOrVarName
+ cfw.startMethod("getParamOrVarConst", "(I)Z",
+ ClassFileWriter.ACC_PUBLIC);
+ break;
+ case Do_getEncodedSource:
+ methodLocals = 1; // Only this
+ cfw.startMethod("getEncodedSource", "()Ljava/lang/String;",
+ ClassFileWriter.ACC_PUBLIC);
+ cfw.addPush(encodedSource);
+ break;
+ default:
+ throw Kit.codeBug();
+ }
+
+ int count = scriptOrFnNodes.length;
+
+ int switchStart = 0;
+ int switchStackTop = 0;
+ if (count > 1) {
+ // Generate switch but only if there is more then one
+ // script/function
+ cfw.addLoadThis();
+ cfw.add(ByteCode.GETFIELD, cfw.getClassName(),
+ ID_FIELD_NAME, "I");
+
+ // do switch from 1 .. count - 1 mapping 0 to the default case
+ switchStart = cfw.addTableSwitch(1, count - 1);
+ }
+
+ for (int i = 0; i != count; ++i) {
+ ScriptOrFnNode n = scriptOrFnNodes[i];
+ if (i == 0) {
+ if (count > 1) {
+ cfw.markTableSwitchDefault(switchStart);
+ switchStackTop = cfw.getStackTop();
+ }
+ } else {
+ cfw.markTableSwitchCase(switchStart, i - 1,
+ switchStackTop);
+ }
+
+ // Impelemnet method-specific switch code
+ switch (methodIndex) {
+ case Do_getFunctionName:
+ // Push function name
+ if (n.getType() == Token.SCRIPT) {
+ cfw.addPush("");
+ } else {
+ String name = ((FunctionNode)n).getFunctionName();
+ cfw.addPush(name);
+ }
+ cfw.add(ByteCode.ARETURN);
+ break;
+
+ case Do_getParamCount:
+ // Push number of defined parameters
+ cfw.addPush(n.getParamCount());
+ cfw.add(ByteCode.IRETURN);
+ break;
+
+ case Do_getParamAndVarCount:
+ // Push number of defined parameters and declared variables
+ cfw.addPush(n.getParamAndVarCount());
+ cfw.add(ByteCode.IRETURN);
+ break;
+
+ case Do_getParamOrVarName:
+ // Push name of parameter using another switch
+ // over paramAndVarCount
+ int paramAndVarCount = n.getParamAndVarCount();
+ if (paramAndVarCount == 0) {
+ // The runtime should never call the method in this
+ // case but to make bytecode verifier happy return null
+ // as throwing execption takes more code
+ cfw.add(ByteCode.ACONST_NULL);
+ cfw.add(ByteCode.ARETURN);
+ } else if (paramAndVarCount == 1) {
+ // As above do not check for valid index but always
+ // return the name of the first param
+ cfw.addPush(n.getParamOrVarName(0));
+ cfw.add(ByteCode.ARETURN);
+ } else {
+ // Do switch over getParamOrVarName
+ cfw.addILoad(1); // param or var index
+ // do switch from 1 .. paramAndVarCount - 1 mapping 0
+ // to the default case
+ int paramSwitchStart = cfw.addTableSwitch(
+ 1, paramAndVarCount - 1);
+ for (int j = 0; j != paramAndVarCount; ++j) {
+ if (cfw.getStackTop() != 0) Kit.codeBug();
+ String s = n.getParamOrVarName(j);
+ if (j == 0) {
+ cfw.markTableSwitchDefault(paramSwitchStart);
+ } else {
+ cfw.markTableSwitchCase(paramSwitchStart, j - 1,
+ 0);
+ }
+ cfw.addPush(s);
+ cfw.add(ByteCode.ARETURN);
+ }
+ }
+ break;
+
+ case Do_getParamOrVarConst:
+ // Push name of parameter using another switch
+ // over paramAndVarCount
+ paramAndVarCount = n.getParamAndVarCount();
+ boolean [] constness = n.getParamAndVarConst();
+ if (paramAndVarCount == 0) {
+ // The runtime should never call the method in this
+ // case but to make bytecode verifier happy return null
+ // as throwing execption takes more code
+ cfw.add(ByteCode.ICONST_0);
+ cfw.add(ByteCode.IRETURN);
+ } else if (paramAndVarCount == 1) {
+ // As above do not check for valid index but always
+ // return the name of the first param
+ cfw.addPush(constness[0]);
+ cfw.add(ByteCode.IRETURN);
+ } else {
+ // Do switch over getParamOrVarName
+ cfw.addILoad(1); // param or var index
+ // do switch from 1 .. paramAndVarCount - 1 mapping 0
+ // to the default case
+ int paramSwitchStart = cfw.addTableSwitch(
+ 1, paramAndVarCount - 1);
+ for (int j = 0; j != paramAndVarCount; ++j) {
+ if (cfw.getStackTop() != 0) Kit.codeBug();
+ if (j == 0) {
+ cfw.markTableSwitchDefault(paramSwitchStart);
+ } else {
+ cfw.markTableSwitchCase(paramSwitchStart, j - 1,
+ 0);
+ }
+ cfw.addPush(constness[j]);
+ cfw.add(ByteCode.IRETURN);
+ }
+ }
+ break;
+
+ case Do_getEncodedSource:
+ // Push number encoded source start and end
+ // to prepare for encodedSource.substring(start, end)
+ cfw.addPush(n.getEncodedSourceStart());
+ cfw.addPush(n.getEncodedSourceEnd());
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
+ "java/lang/String",
+ "substring",
+ "(II)Ljava/lang/String;");
+ cfw.add(ByteCode.ARETURN);
+ break;
+
+ default:
+ throw Kit.codeBug();
+ }
+ }
+
+ cfw.stopMethod(methodLocals);
+ }
+ }
+
+ private void emitRegExpInit(ClassFileWriter cfw)
+ {
+ // precompile all regexp literals
+
+ int totalRegCount = 0;
+ for (int i = 0; i != scriptOrFnNodes.length; ++i) {
+ totalRegCount += scriptOrFnNodes[i].getRegexpCount();
+ }
+ if (totalRegCount == 0) {
+ return;
+ }
+
+ cfw.startMethod(REGEXP_INIT_METHOD_NAME, REGEXP_INIT_METHOD_SIGNATURE,
+ (short)(ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_PRIVATE
+ | ClassFileWriter.ACC_SYNCHRONIZED));
+ cfw.addField("_reInitDone", "Z",
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+ cfw.add(ByteCode.GETSTATIC, mainClassName, "_reInitDone", "Z");
+ int doInit = cfw.acquireLabel();
+ cfw.add(ByteCode.IFEQ, doInit);
+ cfw.add(ByteCode.RETURN);
+ cfw.markLabel(doInit);
+
+ for (int i = 0; i != scriptOrFnNodes.length; ++i) {
+ ScriptOrFnNode n = scriptOrFnNodes[i];
+ int regCount = n.getRegexpCount();
+ for (int j = 0; j != regCount; ++j) {
+ String reFieldName = getCompiledRegexpName(n, j);
+ String reFieldType = "Ljava/lang/Object;";
+ String reString = n.getRegexpString(j);
+ String reFlags = n.getRegexpFlags(j);
+ cfw.addField(reFieldName, reFieldType,
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+ cfw.addALoad(0); // proxy
+ cfw.addALoad(1); // context
+ cfw.addPush(reString);
+ if (reFlags == null) {
+ cfw.add(ByteCode.ACONST_NULL);
+ } else {
+ cfw.addPush(reFlags);
+ }
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/RegExpProxy",
+ "compileRegExp",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Ljava/lang/String;Ljava/lang/String;"
+ +")Ljava/lang/Object;");
+ cfw.add(ByteCode.PUTSTATIC, mainClassName,
+ reFieldName, reFieldType);
+ }
+ }
+
+ cfw.addPush(1);
+ cfw.add(ByteCode.PUTSTATIC, mainClassName, "_reInitDone", "Z");
+ cfw.add(ByteCode.RETURN);
+ cfw.stopMethod((short)2);
+ }
+
+ private void emitConstantDudeInitializers(ClassFileWriter cfw)
+ {
+ int N = itsConstantListSize;
+ if (N == 0)
+ return;
+
+ cfw.startMethod("<clinit>", "()V",
+ (short)(ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_FINAL));
+
+ double[] array = itsConstantList;
+ for (int i = 0; i != N; ++i) {
+ double num = array[i];
+ String constantName = "_k" + i;
+ String constantType = getStaticConstantWrapperType(num);
+ cfw.addField(constantName, constantType,
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+ int inum = (int)num;
+ if (inum == num) {
+ cfw.add(ByteCode.NEW, "java/lang/Integer");
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(inum);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Integer",
+ "<init>", "(I)V");
+ } else {
+ cfw.addPush(num);
+ addDoubleWrap(cfw);
+ }
+ cfw.add(ByteCode.PUTSTATIC, mainClassName,
+ constantName, constantType);
+ }
+
+ cfw.add(ByteCode.RETURN);
+ cfw.stopMethod((short)0);
+ }
+
+ void pushRegExpArray(ClassFileWriter cfw, ScriptOrFnNode n,
+ int contextArg, int scopeArg)
+ {
+ int regexpCount = n.getRegexpCount();
+ if (regexpCount == 0) throw badTree();
+
+ cfw.addPush(regexpCount);
+ cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
+
+ cfw.addALoad(contextArg);
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "checkRegExpProxy",
+ "(Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/RegExpProxy;");
+ // Stack: proxy, array
+ cfw.add(ByteCode.DUP);
+ cfw.addALoad(contextArg);
+ cfw.addInvoke(ByteCode.INVOKESTATIC, mainClassName,
+ REGEXP_INIT_METHOD_NAME, REGEXP_INIT_METHOD_SIGNATURE);
+ for (int i = 0; i != regexpCount; ++i) {
+ // Stack: proxy, array
+ cfw.add(ByteCode.DUP2);
+ cfw.addALoad(contextArg);
+ cfw.addALoad(scopeArg);
+ cfw.add(ByteCode.GETSTATIC, mainClassName,
+ getCompiledRegexpName(n, i), "Ljava/lang/Object;");
+ // Stack: compiledRegExp, scope, cx, proxy, array, proxy, array
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/RegExpProxy",
+ "wrapRegExp",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/Object;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ // Stack: wrappedRegExp, array, proxy, array
+ cfw.addPush(i);
+ cfw.add(ByteCode.SWAP);
+ cfw.add(ByteCode.AASTORE);
+ // Stack: proxy, array
+ }
+ // remove proxy
+ cfw.add(ByteCode.POP);
+ }
+
+ void pushNumberAsObject(ClassFileWriter cfw, double num)
+ {
+ if (num == 0.0) {
+ if (1 / num > 0) {
+ // +0.0
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/optimizer/OptRuntime",
+ "zeroObj", "Ljava/lang/Double;");
+ } else {
+ cfw.addPush(num);
+ addDoubleWrap(cfw);
+ }
+
+ } else if (num == 1.0) {
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/optimizer/OptRuntime",
+ "oneObj", "Ljava/lang/Double;");
+ return;
+
+ } else if (num == -1.0) {
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/optimizer/OptRuntime",
+ "minusOneObj", "Ljava/lang/Double;");
+
+ } else if (num != num) {
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "NaNobj", "Ljava/lang/Double;");
+
+ } else if (itsConstantListSize >= 2000) {
+ // There appears to be a limit in the JVM on either the number
+ // of static fields in a class or the size of the class
+ // initializer. Either way, we can't have any more than 2000
+ // statically init'd constants.
+ cfw.addPush(num);
+ addDoubleWrap(cfw);
+
+ } else {
+ int N = itsConstantListSize;
+ int index = 0;
+ if (N == 0) {
+ itsConstantList = new double[64];
+ } else {
+ double[] array = itsConstantList;
+ while (index != N && array[index] != num) {
+ ++index;
+ }
+ if (N == array.length) {
+ array = new double[N * 2];
+ System.arraycopy(itsConstantList, 0, array, 0, N);
+ itsConstantList = array;
+ }
+ }
+ if (index == N) {
+ itsConstantList[N] = num;
+ itsConstantListSize = N + 1;
+ }
+ String constantName = "_k" + index;
+ String constantType = getStaticConstantWrapperType(num);
+ cfw.add(ByteCode.GETSTATIC, mainClassName,
+ constantName, constantType);
+ }
+ }
+
+ private static void addDoubleWrap(ClassFileWriter cfw)
+ {
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/optimizer/OptRuntime",
+ "wrapDouble", "(D)Ljava/lang/Double;");
+ }
+
+ private static String getStaticConstantWrapperType(double num)
+ {
+ int inum = (int)num;
+ if (inum == num) {
+ return "Ljava/lang/Integer;";
+ } else {
+ return "Ljava/lang/Double;";
+ }
+ }
+ static void pushUndefined(ClassFileWriter cfw)
+ {
+ cfw.add(ByteCode.GETSTATIC, "org/mozilla/javascript/Undefined",
+ "instance", "Ljava/lang/Object;");
+ }
+
+ int getIndex(ScriptOrFnNode n)
+ {
+ return scriptOrFnIndexes.getExisting(n);
+ }
+
+ static String getDirectTargetFieldName(int i)
+ {
+ return "_dt" + i;
+ }
+
+ String getDirectCtorName(ScriptOrFnNode n)
+ {
+ return "_n"+getIndex(n);
+ }
+
+ String getBodyMethodName(ScriptOrFnNode n)
+ {
+ return "_c"+getIndex(n);
+ }
+
+ String getBodyMethodSignature(ScriptOrFnNode n)
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append('(');
+ sb.append(mainClassSignature);
+ sb.append("Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;");
+ if (n.getType() == Token.FUNCTION) {
+ OptFunctionNode ofn = OptFunctionNode.get(n);
+ if (ofn.isTargetOfDirectCall()) {
+ int pCount = ofn.fnode.getParamCount();
+ for (int i = 0; i != pCount; i++) {
+ sb.append("Ljava/lang/Object;D");
+ }
+ }
+ }
+ sb.append("[Ljava/lang/Object;)Ljava/lang/Object;");
+ return sb.toString();
+ }
+
+ String getFunctionInitMethodName(OptFunctionNode ofn)
+ {
+ return "_i"+getIndex(ofn.fnode);
+ }
+
+ String getCompiledRegexpName(ScriptOrFnNode n, int regexpIndex)
+ {
+ return "_re"+getIndex(n)+"_"+regexpIndex;
+ }
+
+ static RuntimeException badTree()
+ {
+ throw new RuntimeException("Bad tree in codegen");
+ }
+
+ void setMainMethodClass(String className)
+ {
+ mainMethodClass = className;
+ }
+
+ static final String DEFAULT_MAIN_METHOD_CLASS
+ = "org.mozilla.javascript.optimizer.OptRuntime";
+
+ private static final String SUPER_CLASS_NAME
+ = "org.mozilla.javascript.NativeFunction";
+
+ static final String DIRECT_CALL_PARENT_FIELD = "_dcp";
+ private static final String ID_FIELD_NAME = "_id";
+
+ private static final String REGEXP_INIT_METHOD_NAME = "_reInit";
+ private static final String REGEXP_INIT_METHOD_SIGNATURE
+ = "(Lorg/mozilla/javascript/RegExpProxy;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")V";
+ static final String REGEXP_ARRAY_FIELD_NAME = "_re";
+ static final String REGEXP_ARRAY_FIELD_TYPE = "[Ljava/lang/Object;";
+
+ static final String FUNCTION_INIT_SIGNATURE
+ = "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")V";
+
+ static final String FUNCTION_CONSTRUCTOR_SIGNATURE
+ = "(Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Context;I)V";
+
+ private static final Object globalLock = new Object();
+ private static int globalSerialClassCounter;
+
+ private CompilerEnvirons compilerEnv;
+
+ private ObjArray directCallTargets;
+ ScriptOrFnNode[] scriptOrFnNodes;
+ private ObjToIntMap scriptOrFnIndexes;
+
+ private String mainMethodClass = DEFAULT_MAIN_METHOD_CLASS;
+
+ String mainClassName;
+ String mainClassSignature;
+
+ private double[] itsConstantList;
+ private int itsConstantListSize;
+}
+
+
+class BodyCodegen
+{
+ void generateBodyCode()
+ {
+ isGenerator = Codegen.isGenerator(scriptOrFn);
+
+ // generate the body of the current function or script object
+ initBodyGeneration();
+
+ if (isGenerator) {
+
+ // All functions in the generated bytecode have a unique name. Every
+ // generator has a unique prefix followed by _gen
+ String type = "(" +
+ codegen.mainClassSignature +
+ "Lorg/mozilla/javascript/Context;" +
+ "Lorg/mozilla/javascript/Scriptable;" +
+ "Ljava/lang/Object;" +
+ "Ljava/lang/Object;I)Ljava/lang/Object;";
+ cfw.startMethod(codegen.getBodyMethodName(scriptOrFn) + "_gen",
+ type,
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+ } else {
+ cfw.startMethod(codegen.getBodyMethodName(scriptOrFn),
+ codegen.getBodyMethodSignature(scriptOrFn),
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+ }
+
+ generatePrologue();
+ Node treeTop;
+ if (fnCurrent != null) {
+ treeTop = scriptOrFn.getLastChild();
+ } else {
+ treeTop = scriptOrFn;
+ }
+ generateStatement(treeTop);
+ generateEpilogue();
+
+ cfw.stopMethod((short)(localsMax + 1));
+
+ if (isGenerator) {
+ // generate the user visible method which when invoked will
+ // return a generator object
+ generateGenerator();
+ }
+ }
+
+ // This creates a the user-facing function that returns a NativeGenerator
+ // object.
+ private void generateGenerator()
+ {
+ cfw.startMethod(codegen.getBodyMethodName(scriptOrFn),
+ codegen.getBodyMethodSignature(scriptOrFn),
+ (short)(ClassFileWriter.ACC_STATIC
+ | ClassFileWriter.ACC_PRIVATE));
+
+ initBodyGeneration();
+ argsLocal = firstFreeLocal++;
+ localsMax = firstFreeLocal;
+
+ // get top level scope
+ if (fnCurrent != null && !inDirectCallFunction
+ && (!compilerEnv.isUseDynamicScope()
+ || fnCurrent.fnode.getIgnoreDynamicScope()))
+ {
+ // Unless we're either in a direct call or using dynamic scope,
+ // use the enclosing scope of the function as our variable object.
+ cfw.addALoad(funObjLocal);
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/Scriptable",
+ "getParentScope",
+ "()Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+ }
+
+ // generators are forced to have an activation record
+ cfw.addALoad(funObjLocal);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(argsLocal);
+ addScriptRuntimeInvoke("createFunctionActivation",
+ "(Lorg/mozilla/javascript/NativeFunction;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+
+ // create a function object
+ cfw.add(ByteCode.NEW, codegen.mainClassName);
+ // Call function constructor
+ cfw.add(ByteCode.DUP);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(contextLocal); // load 'cx'
+ cfw.addPush(scriptOrFnIndex);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, codegen.mainClassName,
+ "<init>", Codegen.FUNCTION_CONSTRUCTOR_SIGNATURE);
+
+ // Init mainScript field
+ cfw.add(ByteCode.DUP);
+ if (isTopLevel) Kit.codeBug(); // Only functions can be generators
+ cfw.add(ByteCode.ALOAD_0);
+ cfw.add(ByteCode.GETFIELD,
+ codegen.mainClassName,
+ Codegen.DIRECT_CALL_PARENT_FIELD,
+ codegen.mainClassSignature);
+ cfw.add(ByteCode.PUTFIELD,
+ codegen.mainClassName,
+ Codegen.DIRECT_CALL_PARENT_FIELD,
+ codegen.mainClassSignature);
+
+ generateNestedFunctionInits();
+
+ // create the NativeGenerator object that we return
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(thisObjLocal);
+ cfw.addLoadConstant(maxLocals);
+ cfw.addLoadConstant(maxStack);
+ addOptRuntimeInvoke("createNativeGenerator",
+ "(Lorg/mozilla/javascript/NativeFunction;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;II"
+ +")Lorg/mozilla/javascript/Scriptable;");
+
+ cfw.add(ByteCode.ARETURN);
+ cfw.stopMethod((short)(localsMax + 1));
+ }
+
+ private void generateNestedFunctionInits()
+ {
+ int functionCount = scriptOrFn.getFunctionCount();
+ for (int i = 0; i != functionCount; i++) {
+ OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, i);
+ if (ofn.fnode.getFunctionType()
+ == FunctionNode.FUNCTION_STATEMENT)
+ {
+ visitFunction(ofn, FunctionNode.FUNCTION_STATEMENT);
+ }
+ }
+ }
+
+ private void initBodyGeneration()
+ {
+ isTopLevel = (scriptOrFn == codegen.scriptOrFnNodes[0]);
+
+ varRegisters = null;
+ if (scriptOrFn.getType() == Token.FUNCTION) {
+ fnCurrent = OptFunctionNode.get(scriptOrFn);
+ hasVarsInRegs = !fnCurrent.fnode.requiresActivation();
+ if (hasVarsInRegs) {
+ int n = fnCurrent.fnode.getParamAndVarCount();
+ if (n != 0) {
+ varRegisters = new short[n];
+ }
+ }
+ inDirectCallFunction = fnCurrent.isTargetOfDirectCall();
+ if (inDirectCallFunction && !hasVarsInRegs) Codegen.badTree();
+ } else {
+ fnCurrent = null;
+ hasVarsInRegs = false;
+ inDirectCallFunction = false;
+ }
+
+ locals = new int[MAX_LOCALS];
+
+ funObjLocal = 0;
+ contextLocal = 1;
+ variableObjectLocal = 2;
+ thisObjLocal = 3;
+ localsMax = (short) 4; // number of parms + "this"
+ firstFreeLocal = 4;
+
+ popvLocal = -1;
+ argsLocal = -1;
+ itsZeroArgArray = -1;
+ itsOneArgArray = -1;
+ scriptRegexpLocal = -1;
+ epilogueLabel = -1;
+ enterAreaStartLabel = -1;
+ generatorStateLocal = -1;
+ }
+
+ /**
+ * Generate the prologue for a function or script.
+ */
+ private void generatePrologue()
+ {
+ if (inDirectCallFunction) {
+ int directParameterCount = scriptOrFn.getParamCount();
+ // 0 is reserved for function Object 'this'
+ // 1 is reserved for context
+ // 2 is reserved for parentScope
+ // 3 is reserved for script 'this'
+ if (firstFreeLocal != 4) Kit.codeBug();
+ for (int i = 0; i != directParameterCount; ++i) {
+ varRegisters[i] = firstFreeLocal;
+ // 3 is 1 for Object parm and 2 for double parm
+ firstFreeLocal += 3;
+ }
+ if (!fnCurrent.getParameterNumberContext()) {
+ // make sure that all parameters are objects
+ itsForcedObjectParameters = true;
+ for (int i = 0; i != directParameterCount; ++i) {
+ short reg = varRegisters[i];
+ cfw.addALoad(reg);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ int isObjectLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPNE, isObjectLabel);
+ cfw.addDLoad(reg + 1);
+ addDoubleWrap();
+ cfw.addAStore(reg);
+ cfw.markLabel(isObjectLabel);
+ }
+ }
+ }
+
+ if (fnCurrent != null && !inDirectCallFunction
+ && (!compilerEnv.isUseDynamicScope()
+ || fnCurrent.fnode.getIgnoreDynamicScope()))
+ {
+ // Unless we're either in a direct call or using dynamic scope,
+ // use the enclosing scope of the function as our variable object.
+ cfw.addALoad(funObjLocal);
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/Scriptable",
+ "getParentScope",
+ "()Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+ }
+
+ // reserve 'args[]'
+ argsLocal = firstFreeLocal++;
+ localsMax = firstFreeLocal;
+
+ // Generate Generator specific prelude
+ if (isGenerator) {
+
+ // reserve 'args[]'
+ operationLocal = firstFreeLocal++;
+ localsMax = firstFreeLocal;
+
+ // Local 3 is a reference to a GeneratorState object. The rest
+ // of codegen expects local 3 to be a reference to the thisObj.
+ // So move the value in local 3 to generatorStateLocal, and load
+ // the saved thisObj from the GeneratorState object.
+ cfw.addALoad(thisObjLocal);
+ generatorStateLocal = firstFreeLocal++;
+ localsMax = firstFreeLocal;
+ cfw.add(ByteCode.CHECKCAST, OptRuntime.GeneratorState.CLASS_NAME);
+ cfw.add(ByteCode.DUP);
+ cfw.addAStore(generatorStateLocal);
+ cfw.add(ByteCode.GETFIELD,
+ OptRuntime.GeneratorState.CLASS_NAME,
+ OptRuntime.GeneratorState.thisObj_NAME,
+ OptRuntime.GeneratorState.thisObj_TYPE);
+ cfw.addAStore(thisObjLocal);
+
+ if (epilogueLabel == -1) {
+ epilogueLabel = cfw.acquireLabel();
+ }
+
+ ArrayList<Node> targets = ((FunctionNode)scriptOrFn).getResumptionPoints();
+ if (targets != null) {
+ // get resumption point
+ generateGetGeneratorResumptionPoint();
+
+ // generate dispatch table
+ generatorSwitch = cfw.addTableSwitch(0,
+ targets.size() + GENERATOR_START);
+ generateCheckForThrowOrClose(-1, false, GENERATOR_START);
+ }
+ }
+
+ if (fnCurrent == null) {
+ // See comments in case Token.REGEXP
+ if (scriptOrFn.getRegexpCount() != 0) {
+ scriptRegexpLocal = getNewWordLocal();
+ codegen.pushRegExpArray(cfw, scriptOrFn, contextLocal,
+ variableObjectLocal);
+ cfw.addAStore(scriptRegexpLocal);
+ }
+ }
+
+ if (compilerEnv.isGenerateObserverCount())
+ saveCurrentCodeOffset();
+
+ if (hasVarsInRegs) {
+ // No need to create activation. Pad arguments if need be.
+ int parmCount = scriptOrFn.getParamCount();
+ if (parmCount > 0 && !inDirectCallFunction) {
+ // Set up args array
+ // check length of arguments, pad if need be
+ cfw.addALoad(argsLocal);
+ cfw.add(ByteCode.ARRAYLENGTH);
+ cfw.addPush(parmCount);
+ int label = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ICMPGE, label);
+ cfw.addALoad(argsLocal);
+ cfw.addPush(parmCount);
+ addScriptRuntimeInvoke("padArguments",
+ "([Ljava/lang/Object;I"
+ +")[Ljava/lang/Object;");
+ cfw.addAStore(argsLocal);
+ cfw.markLabel(label);
+ }
+
+ int paramCount = fnCurrent.fnode.getParamCount();
+ int varCount = fnCurrent.fnode.getParamAndVarCount();
+ boolean [] constDeclarations = fnCurrent.fnode.getParamAndVarConst();
+
+ // REMIND - only need to initialize the vars that don't get a value
+ // before the next call and are used in the function
+ short firstUndefVar = -1;
+ for (int i = 0; i != varCount; ++i) {
+ short reg = -1;
+ if (i < paramCount) {
+ if (!inDirectCallFunction) {
+ reg = getNewWordLocal();
+ cfw.addALoad(argsLocal);
+ cfw.addPush(i);
+ cfw.add(ByteCode.AALOAD);
+ cfw.addAStore(reg);
+ }
+ } else if (fnCurrent.isNumberVar(i)) {
+ reg = getNewWordPairLocal(constDeclarations[i]);
+ cfw.addPush(0.0);
+ cfw.addDStore(reg);
+ } else {
+ reg = getNewWordLocal(constDeclarations[i]);
+ if (firstUndefVar == -1) {
+ Codegen.pushUndefined(cfw);
+ firstUndefVar = reg;
+ } else {
+ cfw.addALoad(firstUndefVar);
+ }
+ cfw.addAStore(reg);
+ }
+ if (reg >= 0) {
+ if (constDeclarations[i]) {
+ cfw.addPush(0);
+ cfw.addIStore(reg + (fnCurrent.isNumberVar(i) ? 2 : 1));
+ }
+ varRegisters[i] = reg;
+ }
+
+ // Add debug table entry if we're generating debug info
+ if (compilerEnv.isGenerateDebugInfo()) {
+ String name = fnCurrent.fnode.getParamOrVarName(i);
+ String type = fnCurrent.isNumberVar(i)
+ ? "D" : "Ljava/lang/Object;";
+ int startPC = cfw.getCurrentCodeOffset();
+ if (reg < 0) {
+ reg = varRegisters[i];
+ }
+ cfw.addVariableDescriptor(name, type, startPC, reg);
+ }
+ }
+
+ // Skip creating activation object.
+ return;
+ }
+
+ // skip creating activation object for the body of a generator. The
+ // activation record required by a generator has already been created
+ // in generateGenerator().
+ if (isGenerator)
+ return;
+
+
+ String debugVariableName;
+ if (fnCurrent != null) {
+ debugVariableName = "activation";
+ cfw.addALoad(funObjLocal);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(argsLocal);
+ addScriptRuntimeInvoke("createFunctionActivation",
+ "(Lorg/mozilla/javascript/NativeFunction;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke("enterActivationFunction",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")V");
+ } else {
+ debugVariableName = "global";
+ cfw.addALoad(funObjLocal);
+ cfw.addALoad(thisObjLocal);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addPush(0); // false to indicate it is not eval script
+ addScriptRuntimeInvoke("initScript",
+ "(Lorg/mozilla/javascript/NativeFunction;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Z"
+ +")V");
+ }
+
+ enterAreaStartLabel = cfw.acquireLabel();
+ epilogueLabel = cfw.acquireLabel();
+ cfw.markLabel(enterAreaStartLabel);
+
+ generateNestedFunctionInits();
+
+ // default is to generate debug info
+ if (compilerEnv.isGenerateDebugInfo()) {
+ cfw.addVariableDescriptor(debugVariableName,
+ "Lorg/mozilla/javascript/Scriptable;",
+ cfw.getCurrentCodeOffset(), variableObjectLocal);
+ }
+
+ if (fnCurrent == null) {
+ // OPT: use dataflow to prove that this assignment is dead
+ popvLocal = getNewWordLocal();
+ Codegen.pushUndefined(cfw);
+ cfw.addAStore(popvLocal);
+
+ int linenum = scriptOrFn.getEndLineno();
+ if (linenum != -1)
+ cfw.addLineNumberEntry((short)linenum);
+
+ } else {
+ if (fnCurrent.itsContainsCalls0) {
+ itsZeroArgArray = getNewWordLocal();
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "emptyArgs", "[Ljava/lang/Object;");
+ cfw.addAStore(itsZeroArgArray);
+ }
+ if (fnCurrent.itsContainsCalls1) {
+ itsOneArgArray = getNewWordLocal();
+ cfw.addPush(1);
+ cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
+ cfw.addAStore(itsOneArgArray);
+ }
+ }
+ }
+
+ private void generateGetGeneratorResumptionPoint()
+ {
+ cfw.addALoad(generatorStateLocal);
+ cfw.add(ByteCode.GETFIELD,
+ OptRuntime.GeneratorState.CLASS_NAME,
+ OptRuntime.GeneratorState.resumptionPoint_NAME,
+ OptRuntime.GeneratorState.resumptionPoint_TYPE);
+ }
+
+ private void generateSetGeneratorResumptionPoint(int nextState)
+ {
+ cfw.addALoad(generatorStateLocal);
+ cfw.addLoadConstant(nextState);
+ cfw.add(ByteCode.PUTFIELD,
+ OptRuntime.GeneratorState.CLASS_NAME,
+ OptRuntime.GeneratorState.resumptionPoint_NAME,
+ OptRuntime.GeneratorState.resumptionPoint_TYPE);
+ }
+
+ private void generateGetGeneratorStackState()
+ {
+ cfw.addALoad(generatorStateLocal);
+ addOptRuntimeInvoke("getGeneratorStackState",
+ "(Ljava/lang/Object;)[Ljava/lang/Object;");
+ }
+
+ private void generateEpilogue()
+ {
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ if (isGenerator) {
+ // generate locals initialization
+ Map<Node,int[]> liveLocals = ((FunctionNode)scriptOrFn).getLiveLocals();
+ if (liveLocals != null) {
+ ArrayList<Node> nodes = ((FunctionNode)scriptOrFn).getResumptionPoints();
+ for (int i = 0; i < nodes.size(); i++) {
+ Node node = nodes.get(i);
+ int[] live = liveLocals.get(node);
+ if (live != null) {
+ cfw.markTableSwitchCase(generatorSwitch,
+ getNextGeneratorState(node));
+ generateGetGeneratorLocalsState();
+ for (int j = 0; j < live.length; j++) {
+ cfw.add(ByteCode.DUP);
+ cfw.addLoadConstant(j);
+ cfw.add(ByteCode.AALOAD);
+ cfw.addAStore(live[j]);
+ }
+ cfw.add(ByteCode.POP);
+ cfw.add(ByteCode.GOTO, getTargetLabel(node));
+ }
+ }
+ }
+
+ // generate dispatch tables for finally
+ if (finallys != null) {
+ for (Node n: finallys.keySet()) {
+ if (n.getType() == Token.FINALLY) {
+ FinallyReturnPoint ret = finallys.get(n);
+ // the finally will jump here
+ cfw.markLabel(ret.tableLabel, (short)1);
+
+ // start generating a dispatch table
+ int startSwitch = cfw.addTableSwitch(0,
+ ret.jsrPoints.size() - 1);
+ int c = 0;
+ cfw.markTableSwitchDefault(startSwitch);
+ for (int i = 0; i < ret.jsrPoints.size(); i++) {
+ // generate gotos back to the JSR location
+ cfw.markTableSwitchCase(startSwitch, c);
+ cfw.add(ByteCode.GOTO,
+ ret.jsrPoints.get(i).intValue());
+ c++;
+ }
+ }
+ }
+ }
+ }
+
+ if (epilogueLabel != -1) {
+ cfw.markLabel(epilogueLabel);
+ }
+
+ if (hasVarsInRegs) {
+ cfw.add(ByteCode.ARETURN);
+ return;
+ } else if (isGenerator) {
+ if (((FunctionNode)scriptOrFn).getResumptionPoints() != null) {
+ cfw.markTableSwitchDefault(generatorSwitch);
+ }
+
+ // change state for re-entry
+ generateSetGeneratorResumptionPoint(GENERATOR_TERMINATE);
+
+ // throw StopIteration
+ cfw.addALoad(variableObjectLocal);
+ addOptRuntimeInvoke("throwStopIteration",
+ "(Ljava/lang/Object;)V");
+
+ Codegen.pushUndefined(cfw);
+ cfw.add(ByteCode.ARETURN);
+
+ } else if (fnCurrent == null) {
+ cfw.addALoad(popvLocal);
+ cfw.add(ByteCode.ARETURN);
+ } else {
+ generateActivationExit();
+ cfw.add(ByteCode.ARETURN);
+
+ // Generate catch block to catch all and rethrow to call exit code
+ // under exception propagation as well.
+
+ int finallyHandler = cfw.acquireLabel();
+ cfw.markHandler(finallyHandler);
+ short exceptionObject = getNewWordLocal();
+ cfw.addAStore(exceptionObject);
+
+ // Duplicate generateActivationExit() in the catch block since it
+ // takes less space then full-featured ByteCode.JSR/ByteCode.RET
+ generateActivationExit();
+
+ cfw.addALoad(exceptionObject);
+ releaseWordLocal(exceptionObject);
+ // rethrow
+ cfw.add(ByteCode.ATHROW);
+
+ // mark the handler
+ cfw.addExceptionHandler(enterAreaStartLabel, epilogueLabel,
+ finallyHandler, null); // catch any
+ }
+ }
+
+ private void generateGetGeneratorLocalsState() {
+ cfw.addALoad(generatorStateLocal);
+ addOptRuntimeInvoke("getGeneratorLocalsState",
+ "(Ljava/lang/Object;)[Ljava/lang/Object;");
+ }
+
+ private void generateActivationExit()
+ {
+ if (fnCurrent == null || hasVarsInRegs) throw Kit.codeBug();
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("exitActivationFunction",
+ "(Lorg/mozilla/javascript/Context;)V");
+ }
+
+ private void generateStatement(Node node)
+ {
+ updateLineNumber(node);
+ int type = node.getType();
+ Node child = node.getFirstChild();
+ switch (type) {
+ case Token.LOOP:
+ case Token.LABEL:
+ case Token.WITH:
+ case Token.SCRIPT:
+ case Token.BLOCK:
+ case Token.EMPTY:
+ // no-ops.
+ if (compilerEnv.isGenerateObserverCount()) {
+ // Need to add instruction count even for no-ops to catch
+ // cases like while (1) {}
+ addInstructionCount(1);
+ }
+ while (child != null) {
+ generateStatement(child);
+ child = child.getNext();
+ }
+ break;
+
+ case Token.LOCAL_BLOCK: {
+ int local = getNewWordLocal();
+ if (isGenerator) {
+ cfw.add(ByteCode.ACONST_NULL);
+ cfw.addAStore(local);
+ }
+ node.putIntProp(Node.LOCAL_PROP, local);
+ while (child != null) {
+ generateStatement(child);
+ child = child.getNext();
+ }
+ releaseWordLocal((short)local);
+ node.removeProp(Node.LOCAL_PROP);
+ break;
+ }
+
+ case Token.FUNCTION: {
+ int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
+ OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, fnIndex);
+ int t = ofn.fnode.getFunctionType();
+ if (t == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) {
+ visitFunction(ofn, t);
+ } else {
+ if (t != FunctionNode.FUNCTION_STATEMENT) {
+ throw Codegen.badTree();
+ }
+ }
+ break;
+ }
+
+ case Token.TRY:
+ visitTryCatchFinally((Node.Jump)node, child);
+ break;
+
+ case Token.CATCH_SCOPE:
+ {
+ // nothing stays on the stack on entry into a catch scope
+ cfw.setStackTop((short) 0);
+
+ int local = getLocalBlockRegister(node);
+ int scopeIndex
+ = node.getExistingIntProp(Node.CATCH_SCOPE_PROP);
+
+ String name = child.getString(); // name of exception
+ child = child.getNext();
+ generateExpression(child, node); // load expression object
+ if (scopeIndex == 0) {
+ cfw.add(ByteCode.ACONST_NULL);
+ } else {
+ // Load previous catch scope object
+ cfw.addALoad(local);
+ }
+ cfw.addPush(name);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+
+ addScriptRuntimeInvoke(
+ "newCatchScope",
+ "(Ljava/lang/Throwable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(local);
+ }
+ break;
+
+ case Token.THROW:
+ generateExpression(child, node);
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ generateThrowJavaScriptException();
+ break;
+
+ case Token.RETHROW:
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ cfw.addALoad(getLocalBlockRegister(node));
+ cfw.add(ByteCode.ATHROW);
+ break;
+
+ case Token.RETURN_RESULT:
+ case Token.RETURN:
+ if (!isGenerator) {
+ if (child != null) {
+ generateExpression(child, node);
+ } else if (type == Token.RETURN) {
+ Codegen.pushUndefined(cfw);
+ } else {
+ if (popvLocal < 0) throw Codegen.badTree();
+ cfw.addALoad(popvLocal);
+ }
+ }
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ if (epilogueLabel == -1) {
+ if (!hasVarsInRegs) throw Codegen.badTree();
+ epilogueLabel = cfw.acquireLabel();
+ }
+ cfw.add(ByteCode.GOTO, epilogueLabel);
+ break;
+
+ case Token.SWITCH:
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ visitSwitch((Node.Jump)node, child);
+ break;
+
+ case Token.ENTERWITH:
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke(
+ "enterWith",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+ incReferenceWordLocal(variableObjectLocal);
+ break;
+
+ case Token.LEAVEWITH:
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke(
+ "leaveWith",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+ decReferenceWordLocal(variableObjectLocal);
+ break;
+
+ case Token.ENUM_INIT_KEYS:
+ case Token.ENUM_INIT_VALUES:
+ case Token.ENUM_INIT_ARRAY:
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ int enumType = type == Token.ENUM_INIT_KEYS
+ ? ScriptRuntime.ENUMERATE_KEYS :
+ type == Token.ENUM_INIT_VALUES
+ ? ScriptRuntime.ENUMERATE_VALUES :
+ ScriptRuntime.ENUMERATE_ARRAY;
+ cfw.addPush(enumType);
+ addScriptRuntimeInvoke("enumInit",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I"
+ +")Ljava/lang/Object;");
+ cfw.addAStore(getLocalBlockRegister(node));
+ break;
+
+ case Token.EXPR_VOID:
+ if (child.getType() == Token.SETVAR) {
+ /* special case this so as to avoid unnecessary
+ load's & pop's */
+ visitSetVar(child, child.getFirstChild(), false);
+ }
+ else if (child.getType() == Token.SETCONSTVAR) {
+ /* special case this so as to avoid unnecessary
+ load's & pop's */
+ visitSetConstVar(child, child.getFirstChild(), false);
+ }
+ else if (child.getType() == Token.YIELD) {
+ generateYieldPoint(child, false);
+ }
+ else {
+ generateExpression(child, node);
+ if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1)
+ cfw.add(ByteCode.POP2);
+ else
+ cfw.add(ByteCode.POP);
+ }
+ break;
+
+ case Token.EXPR_RESULT:
+ generateExpression(child, node);
+ if (popvLocal < 0) {
+ popvLocal = getNewWordLocal();
+ }
+ cfw.addAStore(popvLocal);
+ break;
+
+ case Token.TARGET:
+ {
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ int label = getTargetLabel(node);
+ cfw.markLabel(label);
+ if (compilerEnv.isGenerateObserverCount())
+ saveCurrentCodeOffset();
+ }
+ break;
+
+ case Token.JSR:
+ case Token.GOTO:
+ case Token.IFEQ:
+ case Token.IFNE:
+ if (compilerEnv.isGenerateObserverCount())
+ addInstructionCount();
+ visitGoto((Node.Jump)node, type, child);
+ break;
+
+ case Token.FINALLY:
+ {
+ if (compilerEnv.isGenerateObserverCount())
+ saveCurrentCodeOffset();
+ // there is exactly one value on the stack when enterring
+ // finally blocks: the return address (or its int encoding)
+ cfw.setStackTop((short)1);
+
+ // Save return address in a new local
+ int finallyRegister = getNewWordLocal();
+ if (isGenerator)
+ generateIntegerWrap();
+ cfw.addAStore(finallyRegister);
+
+ while (child != null) {
+ generateStatement(child);
+ child = child.getNext();
+ }
+ if (isGenerator) {
+ cfw.addALoad(finallyRegister);
+ cfw.add(ByteCode.CHECKCAST, "java/lang/Integer");
+ generateIntegerUnwrap();
+ FinallyReturnPoint ret = finallys.get(node);
+ ret.tableLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.GOTO, ret.tableLabel);
+ } else {
+ cfw.add(ByteCode.RET, finallyRegister);
+ }
+ releaseWordLocal((short)finallyRegister);
+ }
+ break;
+
+ case Token.DEBUGGER:
+ break;
+
+ default:
+ throw Codegen.badTree();
+ }
+
+ }
+
+ private void generateIntegerWrap()
+ {
+ cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/Integer", "valueOf",
+ "(I)Ljava/lang/Integer;");
+ }
+
+
+ private void generateIntegerUnwrap()
+ {
+ cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/Integer",
+ "intValue", "()I");
+ }
+
+
+ private void generateThrowJavaScriptException()
+ {
+ cfw.add(ByteCode.NEW,
+ "org/mozilla/javascript/JavaScriptException");
+ cfw.add(ByteCode.DUP_X1);
+ cfw.add(ByteCode.SWAP);
+ cfw.addPush(scriptOrFn.getSourceName());
+ cfw.addPush(itsLineNumber);
+ cfw.addInvoke(
+ ByteCode.INVOKESPECIAL,
+ "org/mozilla/javascript/JavaScriptException",
+ "<init>",
+ "(Ljava/lang/Object;Ljava/lang/String;I)V");
+ cfw.add(ByteCode.ATHROW);
+ }
+
+ private int getNextGeneratorState(Node node)
+ {
+ int nodeIndex = ((FunctionNode)scriptOrFn).getResumptionPoints()
+ .indexOf(node);
+ return nodeIndex + GENERATOR_YIELD_START;
+ }
+
+ private void generateExpression(Node node, Node parent)
+ {
+ int type = node.getType();
+ Node child = node.getFirstChild();
+ switch (type) {
+ case Token.USE_STACK:
+ break;
+
+ case Token.FUNCTION:
+ if (fnCurrent != null || parent.getType() != Token.SCRIPT) {
+ int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP);
+ OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn,
+ fnIndex);
+ int t = ofn.fnode.getFunctionType();
+ if (t != FunctionNode.FUNCTION_EXPRESSION) {
+ throw Codegen.badTree();
+ }
+ visitFunction(ofn, t);
+ }
+ break;
+
+ case Token.NAME:
+ {
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addPush(node.getString());
+ addScriptRuntimeInvoke(
+ "name",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +")Ljava/lang/Object;");
+ }
+ break;
+
+ case Token.CALL:
+ case Token.NEW:
+ {
+ int specialType = node.getIntProp(Node.SPECIALCALL_PROP,
+ Node.NON_SPECIALCALL);
+ if (specialType == Node.NON_SPECIALCALL) {
+ OptFunctionNode target;
+ target = (OptFunctionNode)node.getProp(
+ Node.DIRECTCALL_PROP);
+
+ if (target != null) {
+ visitOptimizedCall(node, target, type, child);
+ } else if (type == Token.CALL) {
+ visitStandardCall(node, child);
+ } else {
+ visitStandardNew(node, child);
+ }
+ } else {
+ visitSpecialCall(node, type, specialType, child);
+ }
+ }
+ break;
+
+ case Token.REF_CALL:
+ generateFunctionAndThisObj(child, node);
+ // stack: ... functionObj thisObj
+ child = child.getNext();
+ generateCallArgArray(node, child, false);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "callRef",
+ "(Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/Ref;");
+ break;
+
+ case Token.NUMBER:
+ {
+ double num = node.getDouble();
+ if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
+ cfw.addPush(num);
+ } else {
+ codegen.pushNumberAsObject(cfw, num);
+ }
+ }
+ break;
+
+ case Token.STRING:
+ cfw.addPush(node.getString());
+ break;
+
+ case Token.THIS:
+ cfw.addALoad(thisObjLocal);
+ break;
+
+ case Token.THISFN:
+ cfw.add(ByteCode.ALOAD_0);
+ break;
+
+ case Token.NULL:
+ cfw.add(ByteCode.ACONST_NULL);
+ break;
+
+ case Token.TRUE:
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean",
+ "TRUE", "Ljava/lang/Boolean;");
+ break;
+
+ case Token.FALSE:
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean",
+ "FALSE", "Ljava/lang/Boolean;");
+ break;
+
+ case Token.REGEXP:
+ {
+ int i = node.getExistingIntProp(Node.REGEXP_PROP);
+ // Scripts can not use REGEXP_ARRAY_FIELD_NAME since
+ // it it will make script.exec non-reentrant so they
+ // store regexp array in a local variable while
+ // functions always access precomputed
+ // REGEXP_ARRAY_FIELD_NAME not to consume locals
+ if (fnCurrent == null) {
+ cfw.addALoad(scriptRegexpLocal);
+ } else {
+ cfw.addALoad(funObjLocal);
+ cfw.add(ByteCode.GETFIELD, codegen.mainClassName,
+ Codegen.REGEXP_ARRAY_FIELD_NAME,
+ Codegen.REGEXP_ARRAY_FIELD_TYPE);
+ }
+ cfw.addPush(i);
+ cfw.add(ByteCode.AALOAD);
+ }
+ break;
+
+ case Token.COMMA: {
+ Node next = child.getNext();
+ while (next != null) {
+ generateExpression(child, node);
+ cfw.add(ByteCode.POP);
+ child = next;
+ next = next.getNext();
+ }
+ generateExpression(child, node);
+ break;
+ }
+
+ case Token.ENUM_NEXT:
+ case Token.ENUM_ID: {
+ int local = getLocalBlockRegister(node);
+ cfw.addALoad(local);
+ if (type == Token.ENUM_NEXT) {
+ addScriptRuntimeInvoke(
+ "enumNext", "(Ljava/lang/Object;)Ljava/lang/Boolean;");
+ } else {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("enumId",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ break;
+ }
+
+ case Token.ARRAYLIT:
+ visitArrayLiteral(node, child);
+ break;
+
+ case Token.OBJECTLIT:
+ visitObjectLiteral(node, child);
+ break;
+
+ case Token.NOT: {
+ int trueTarget = cfw.acquireLabel();
+ int falseTarget = cfw.acquireLabel();
+ int beyond = cfw.acquireLabel();
+ generateIfJump(child, node, trueTarget, falseTarget);
+
+ cfw.markLabel(trueTarget);
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean",
+ "FALSE", "Ljava/lang/Boolean;");
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(falseTarget);
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean",
+ "TRUE", "Ljava/lang/Boolean;");
+ cfw.markLabel(beyond);
+ cfw.adjustStackTop(-1);
+ break;
+ }
+
+ case Token.BITNOT:
+ generateExpression(child, node);
+ addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I");
+ cfw.addPush(-1); // implement ~a as (a ^ -1)
+ cfw.add(ByteCode.IXOR);
+ cfw.add(ByteCode.I2D);
+ addDoubleWrap();
+ break;
+
+ case Token.VOID:
+ generateExpression(child, node);
+ cfw.add(ByteCode.POP);
+ Codegen.pushUndefined(cfw);
+ break;
+
+ case Token.TYPEOF:
+ generateExpression(child, node);
+ addScriptRuntimeInvoke("typeof",
+ "(Ljava/lang/Object;"
+ +")Ljava/lang/String;");
+ break;
+
+ case Token.TYPEOFNAME:
+ visitTypeofname(node);
+ break;
+
+ case Token.INC:
+ case Token.DEC:
+ visitIncDec(node);
+ break;
+
+ case Token.OR:
+ case Token.AND: {
+ generateExpression(child, node);
+ cfw.add(ByteCode.DUP);
+ addScriptRuntimeInvoke("toBoolean",
+ "(Ljava/lang/Object;)Z");
+ int falseTarget = cfw.acquireLabel();
+ if (type == Token.AND)
+ cfw.add(ByteCode.IFEQ, falseTarget);
+ else
+ cfw.add(ByteCode.IFNE, falseTarget);
+ cfw.add(ByteCode.POP);
+ generateExpression(child.getNext(), node);
+ cfw.markLabel(falseTarget);
+ }
+ break;
+
+ case Token.HOOK : {
+ Node ifThen = child.getNext();
+ Node ifElse = ifThen.getNext();
+ generateExpression(child, node);
+ addScriptRuntimeInvoke("toBoolean",
+ "(Ljava/lang/Object;)Z");
+ int elseTarget = cfw.acquireLabel();
+ cfw.add(ByteCode.IFEQ, elseTarget);
+ short stack = cfw.getStackTop();
+ generateExpression(ifThen, node);
+ int afterHook = cfw.acquireLabel();
+ cfw.add(ByteCode.GOTO, afterHook);
+ cfw.markLabel(elseTarget, stack);
+ generateExpression(ifElse, node);
+ cfw.markLabel(afterHook);
+ }
+ break;
+
+ case Token.ADD: {
+ generateExpression(child, node);
+ generateExpression(child.getNext(), node);
+ switch (node.getIntProp(Node.ISNUMBER_PROP, -1)) {
+ case Node.BOTH:
+ cfw.add(ByteCode.DADD);
+ break;
+ case Node.LEFT:
+ addOptRuntimeInvoke("add",
+ "(DLjava/lang/Object;)Ljava/lang/Object;");
+ break;
+ case Node.RIGHT:
+ addOptRuntimeInvoke("add",
+ "(Ljava/lang/Object;D)Ljava/lang/Object;");
+ break;
+ default:
+ if (child.getType() == Token.STRING) {
+ addScriptRuntimeInvoke("add",
+ "(Ljava/lang/String;"
+ +"Ljava/lang/Object;"
+ +")Ljava/lang/String;");
+ } else if (child.getNext().getType() == Token.STRING) {
+ addScriptRuntimeInvoke("add",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +")Ljava/lang/String;");
+ } else {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("add",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ }
+ }
+ break;
+
+ case Token.MUL:
+ visitArithmetic(node, ByteCode.DMUL, child, parent);
+ break;
+
+ case Token.SUB:
+ visitArithmetic(node, ByteCode.DSUB, child, parent);
+ break;
+
+ case Token.DIV:
+ case Token.MOD:
+ visitArithmetic(node, type == Token.DIV
+ ? ByteCode.DDIV
+ : ByteCode.DREM, child, parent);
+ break;
+
+ case Token.BITOR:
+ case Token.BITXOR:
+ case Token.BITAND:
+ case Token.LSH:
+ case Token.RSH:
+ case Token.URSH:
+ visitBitOp(node, type, child);
+ break;
+
+ case Token.POS:
+ case Token.NEG:
+ generateExpression(child, node);
+ addObjectToDouble();
+ if (type == Token.NEG) {
+ cfw.add(ByteCode.DNEG);
+ }
+ addDoubleWrap();
+ break;
+
+ case Token.TO_DOUBLE:
+ // cnvt to double (not Double)
+ generateExpression(child, node);
+ addObjectToDouble();
+ break;
+
+ case Token.TO_OBJECT: {
+ // convert from double
+ int prop = -1;
+ if (child.getType() == Token.NUMBER) {
+ prop = child.getIntProp(Node.ISNUMBER_PROP, -1);
+ }
+ if (prop != -1) {
+ child.removeProp(Node.ISNUMBER_PROP);
+ generateExpression(child, node);
+ child.putIntProp(Node.ISNUMBER_PROP, prop);
+ } else {
+ generateExpression(child, node);
+ addDoubleWrap();
+ }
+ break;
+ }
+
+ case Token.IN:
+ case Token.INSTANCEOF:
+ case Token.LE:
+ case Token.LT:
+ case Token.GE:
+ case Token.GT: {
+ int trueGOTO = cfw.acquireLabel();
+ int falseGOTO = cfw.acquireLabel();
+ visitIfJumpRelOp(node, child, trueGOTO, falseGOTO);
+ addJumpedBooleanWrap(trueGOTO, falseGOTO);
+ break;
+ }
+
+ case Token.EQ:
+ case Token.NE:
+ case Token.SHEQ:
+ case Token.SHNE: {
+ int trueGOTO = cfw.acquireLabel();
+ int falseGOTO = cfw.acquireLabel();
+ visitIfJumpEqOp(node, child, trueGOTO, falseGOTO);
+ addJumpedBooleanWrap(trueGOTO, falseGOTO);
+ break;
+ }
+
+ case Token.GETPROP:
+ case Token.GETPROPNOWARN:
+ visitGetProp(node, child);
+ break;
+
+ case Token.GETELEM:
+ generateExpression(child, node); // object
+ generateExpression(child.getNext(), node); // id
+ cfw.addALoad(contextLocal);
+ if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
+ addScriptRuntimeInvoke(
+ "getObjectIndex",
+ "(Ljava/lang/Object;D"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ else {
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke(
+ "getObjectElem",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;");
+ }
+ break;
+
+ case Token.GET_REF:
+ generateExpression(child, node); // reference
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "refGet",
+ "(Lorg/mozilla/javascript/Ref;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ break;
+
+ case Token.GETVAR:
+ visitGetVar(node);
+ break;
+
+ case Token.SETVAR:
+ visitSetVar(node, child, true);
+ break;
+
+ case Token.SETNAME:
+ visitSetName(node, child);
+ break;
+
+ case Token.SETCONST:
+ visitSetConst(node, child);
+ break;
+
+ case Token.SETCONSTVAR:
+ visitSetConstVar(node, child, true);
+ break;
+
+ case Token.SETPROP:
+ case Token.SETPROP_OP:
+ visitSetProp(type, node, child);
+ break;
+
+ case Token.SETELEM:
+ case Token.SETELEM_OP:
+ visitSetElem(type, node, child);
+ break;
+
+ case Token.SET_REF:
+ case Token.SET_REF_OP:
+ {
+ generateExpression(child, node);
+ child = child.getNext();
+ if (type == Token.SET_REF_OP) {
+ cfw.add(ByteCode.DUP);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "refGet",
+ "(Lorg/mozilla/javascript/Ref;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "refSet",
+ "(Lorg/mozilla/javascript/Ref;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ break;
+
+ case Token.DEL_REF:
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("refDel",
+ "(Lorg/mozilla/javascript/Ref;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ break;
+
+ case Token.DELPROP:
+ generateExpression(child, node);
+ child = child.getNext();
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("delete",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ break;
+
+ case Token.BINDNAME:
+ {
+ while (child != null) {
+ generateExpression(child, node);
+ child = child.getNext();
+ }
+ // Generate code for "ScriptRuntime.bind(varObj, "s")"
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addPush(node.getString());
+ addScriptRuntimeInvoke(
+ "bind",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ }
+ break;
+
+ case Token.LOCAL_LOAD:
+ cfw.addALoad(getLocalBlockRegister(node));
+ break;
+
+ case Token.REF_SPECIAL:
+ {
+ String special = (String)node.getProp(Node.NAME_PROP);
+ generateExpression(child, node);
+ cfw.addPush(special);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "specialRef",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/Ref;");
+ }
+ break;
+
+ case Token.REF_MEMBER:
+ case Token.REF_NS_MEMBER:
+ case Token.REF_NAME:
+ case Token.REF_NS_NAME:
+ {
+ int memberTypeFlags
+ = node.getIntProp(Node.MEMBER_TYPE_PROP, 0);
+ // generate possible target, possible namespace and member
+ do {
+ generateExpression(child, node);
+ child = child.getNext();
+ } while (child != null);
+ cfw.addALoad(contextLocal);
+ String methodName, signature;
+ switch (type) {
+ case Token.REF_MEMBER:
+ methodName = "memberRef";
+ signature = "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I"
+ +")Lorg/mozilla/javascript/Ref;";
+ break;
+ case Token.REF_NS_MEMBER:
+ methodName = "memberRef";
+ signature = "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I"
+ +")Lorg/mozilla/javascript/Ref;";
+ break;
+ case Token.REF_NAME:
+ methodName = "nameRef";
+ signature = "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"I"
+ +")Lorg/mozilla/javascript/Ref;";
+ cfw.addALoad(variableObjectLocal);
+ break;
+ case Token.REF_NS_NAME:
+ methodName = "nameRef";
+ signature = "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"I"
+ +")Lorg/mozilla/javascript/Ref;";
+ cfw.addALoad(variableObjectLocal);
+ break;
+ default:
+ throw Kit.codeBug();
+ }
+ cfw.addPush(memberTypeFlags);
+ addScriptRuntimeInvoke(methodName, signature);
+ }
+ break;
+
+ case Token.DOTQUERY:
+ visitDotQuery(node, child);
+ break;
+
+ case Token.ESCXMLATTR:
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("escapeAttributeValue",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/String;");
+ break;
+
+ case Token.ESCXMLTEXT:
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("escapeTextValue",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/String;");
+ break;
+
+ case Token.DEFAULTNAMESPACE:
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke("setDefaultNamespace",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ break;
+
+ case Token.YIELD:
+ generateYieldPoint(node, true);
+ break;
+
+ case Token.WITHEXPR: {
+ Node enterWith = child;
+ Node with = enterWith.getNext();
+ Node leaveWith = with.getNext();
+ generateStatement(enterWith);
+ generateExpression(with.getFirstChild(), with);
+ generateStatement(leaveWith);
+ break;
+ }
+
+ case Token.ARRAYCOMP: {
+ Node initStmt = child;
+ Node expr = child.getNext();
+ generateStatement(initStmt);
+ generateExpression(expr, node);
+ break;
+ }
+
+ default:
+ throw new RuntimeException("Unexpected node type "+type);
+ }
+
+ }
+
+ private void generateYieldPoint(Node node, boolean exprContext) {
+ // save stack state
+ int top = cfw.getStackTop();
+ maxStack = maxStack > top ? maxStack : top;
+ if (cfw.getStackTop() != 0) {
+ generateGetGeneratorStackState();
+ for (int i = 0; i < top; i++) {
+ cfw.add(ByteCode.DUP_X1);
+ cfw.add(ByteCode.SWAP);
+ cfw.addLoadConstant(i);
+ cfw.add(ByteCode.SWAP);
+ cfw.add(ByteCode.AASTORE);
+ }
+ // pop the array object
+ cfw.add(ByteCode.POP);
+ }
+
+ // generate the yield argument
+ Node child = node.getFirstChild();
+ if (child != null)
+ generateExpression(child, node);
+ else
+ Codegen.pushUndefined(cfw);
+
+ // change the resumption state
+ int nextState = getNextGeneratorState(node);
+ generateSetGeneratorResumptionPoint(nextState);
+
+ boolean hasLocals = generateSaveLocals(node);
+
+ cfw.add(ByteCode.ARETURN);
+
+ generateCheckForThrowOrClose(getTargetLabel(node),
+ hasLocals, nextState);
+
+ // reconstruct the stack
+ if (top != 0) {
+ generateGetGeneratorStackState();
+ for (int i = 0; i < top; i++) {
+ cfw.add(ByteCode.DUP);
+ cfw.addLoadConstant(top - i - 1);
+ cfw.add(ByteCode.AALOAD);
+ cfw.add(ByteCode.SWAP);
+ }
+ cfw.add(ByteCode.POP);
+ }
+
+ // load return value from yield
+ if (exprContext) {
+ cfw.addALoad(argsLocal);
+ }
+ }
+
+ private void generateCheckForThrowOrClose(int label,
+ boolean hasLocals,
+ int nextState) {
+ int throwLabel = cfw.acquireLabel();
+ int closeLabel = cfw.acquireLabel();
+
+ // throw the user provided object, if the operation is .throw()
+ cfw.markLabel(throwLabel);
+ cfw.addALoad(argsLocal);
+ generateThrowJavaScriptException();
+
+ // throw our special internal exception if the generator is being closed
+ cfw.markLabel(closeLabel);
+ cfw.addALoad(argsLocal);
+ cfw.add(ByteCode.CHECKCAST, "java/lang/Throwable");
+ cfw.add(ByteCode.ATHROW);
+
+ // mark the re-entry point
+ // jump here after initializing the locals
+ if (label != -1)
+ cfw.markLabel(label);
+ if (!hasLocals) {
+ // jump here directly if there are no locals
+ cfw.markTableSwitchCase(generatorSwitch, nextState);
+ }
+
+ // see if we need to dispatch for .close() or .throw()
+ cfw.addILoad(operationLocal);
+ cfw.addLoadConstant(NativeGenerator.GENERATOR_CLOSE);
+ cfw.add(ByteCode.IF_ICMPEQ, closeLabel);
+ cfw.addILoad(operationLocal);
+ cfw.addLoadConstant(NativeGenerator.GENERATOR_THROW);
+ cfw.add(ByteCode.IF_ICMPEQ, throwLabel);
+ }
+
+ private void generateIfJump(Node node, Node parent,
+ int trueLabel, int falseLabel)
+ {
+ // System.out.println("gen code for " + node.toString());
+
+ int type = node.getType();
+ Node child = node.getFirstChild();
+
+ switch (type) {
+ case Token.NOT:
+ generateIfJump(child, node, falseLabel, trueLabel);
+ break;
+
+ case Token.OR:
+ case Token.AND: {
+ int interLabel = cfw.acquireLabel();
+ if (type == Token.AND) {
+ generateIfJump(child, node, interLabel, falseLabel);
+ }
+ else {
+ generateIfJump(child, node, trueLabel, interLabel);
+ }
+ cfw.markLabel(interLabel);
+ child = child.getNext();
+ generateIfJump(child, node, trueLabel, falseLabel);
+ break;
+ }
+
+ case Token.IN:
+ case Token.INSTANCEOF:
+ case Token.LE:
+ case Token.LT:
+ case Token.GE:
+ case Token.GT:
+ visitIfJumpRelOp(node, child, trueLabel, falseLabel);
+ break;
+
+ case Token.EQ:
+ case Token.NE:
+ case Token.SHEQ:
+ case Token.SHNE:
+ visitIfJumpEqOp(node, child, trueLabel, falseLabel);
+ break;
+
+ default:
+ // Generate generic code for non-optimized jump
+ generateExpression(node, parent);
+ addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z");
+ cfw.add(ByteCode.IFNE, trueLabel);
+ cfw.add(ByteCode.GOTO, falseLabel);
+ }
+ }
+
+ private void visitFunction(OptFunctionNode ofn, int functionType)
+ {
+ int fnIndex = codegen.getIndex(ofn.fnode);
+ cfw.add(ByteCode.NEW, codegen.mainClassName);
+ // Call function constructor
+ cfw.add(ByteCode.DUP);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(contextLocal); // load 'cx'
+ cfw.addPush(fnIndex);
+ cfw.addInvoke(ByteCode.INVOKESPECIAL, codegen.mainClassName,
+ "<init>", Codegen.FUNCTION_CONSTRUCTOR_SIGNATURE);
+
+ // Init mainScript field;
+ cfw.add(ByteCode.DUP);
+ if (isTopLevel) {
+ cfw.add(ByteCode.ALOAD_0);
+ } else {
+ cfw.add(ByteCode.ALOAD_0);
+ cfw.add(ByteCode.GETFIELD,
+ codegen.mainClassName,
+ Codegen.DIRECT_CALL_PARENT_FIELD,
+ codegen.mainClassSignature);
+ }
+ cfw.add(ByteCode.PUTFIELD,
+ codegen.mainClassName,
+ Codegen.DIRECT_CALL_PARENT_FIELD,
+ codegen.mainClassSignature);
+
+ int directTargetIndex = ofn.getDirectTargetIndex();
+ if (directTargetIndex >= 0) {
+ cfw.add(ByteCode.DUP);
+ if (isTopLevel) {
+ cfw.add(ByteCode.ALOAD_0);
+ } else {
+ cfw.add(ByteCode.ALOAD_0);
+ cfw.add(ByteCode.GETFIELD,
+ codegen.mainClassName,
+ Codegen.DIRECT_CALL_PARENT_FIELD,
+ codegen.mainClassSignature);
+ }
+ cfw.add(ByteCode.SWAP);
+ cfw.add(ByteCode.PUTFIELD,
+ codegen.mainClassName,
+ Codegen.getDirectTargetFieldName(directTargetIndex),
+ codegen.mainClassSignature);
+ }
+
+ if (functionType == FunctionNode.FUNCTION_EXPRESSION) {
+ // Leave closure object on stack and do not pass it to
+ // initFunction which suppose to connect statements to scope
+ return;
+ }
+ cfw.addPush(functionType);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(contextLocal); // load 'cx'
+ addOptRuntimeInvoke("initFunction",
+ "(Lorg/mozilla/javascript/NativeFunction;"
+ +"I"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")V");
+ }
+
+ private int getTargetLabel(Node target)
+ {
+ int labelId = target.labelId();
+ if (labelId == -1) {
+ labelId = cfw.acquireLabel();
+ target.labelId(labelId);
+ }
+ return labelId;
+ }
+
+ private void visitGoto(Node.Jump node, int type, Node child)
+ {
+ Node target = node.target;
+ if (type == Token.IFEQ || type == Token.IFNE) {
+ if (child == null) throw Codegen.badTree();
+ int targetLabel = getTargetLabel(target);
+ int fallThruLabel = cfw.acquireLabel();
+ if (type == Token.IFEQ)
+ generateIfJump(child, node, targetLabel, fallThruLabel);
+ else
+ generateIfJump(child, node, fallThruLabel, targetLabel);
+ cfw.markLabel(fallThruLabel);
+ } else {
+ if (type == Token.JSR) {
+ if (isGenerator) {
+ addGotoWithReturn(target);
+ } else {
+ addGoto(target, ByteCode.JSR);
+ }
+ } else {
+ addGoto(target, ByteCode.GOTO);
+ }
+ }
+ }
+
+ private void addGotoWithReturn(Node target) {
+ FinallyReturnPoint ret = finallys.get(target);
+ cfw.addLoadConstant(ret.jsrPoints.size());
+ addGoto(target, ByteCode.GOTO);
+ int retLabel = cfw.acquireLabel();
+ cfw.markLabel(retLabel);
+ ret.jsrPoints.add(Integer.valueOf(retLabel));
+ }
+
+ private void visitArrayLiteral(Node node, Node child)
+ {
+ int count = 0;
+ for (Node cursor = child; cursor != null; cursor = cursor.getNext()) {
+ ++count;
+ }
+ // load array to store array literal objects
+ addNewObjectArray(count);
+ for (int i = 0; i != count; ++i) {
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(i);
+ generateExpression(child, node);
+ cfw.add(ByteCode.AASTORE);
+ child = child.getNext();
+ }
+ int[] skipIndexes = (int[])node.getProp(Node.SKIP_INDEXES_PROP);
+ if (skipIndexes == null) {
+ cfw.add(ByteCode.ACONST_NULL);
+ cfw.add(ByteCode.ICONST_0);
+ } else {
+ cfw.addPush(OptRuntime.encodeIntArray(skipIndexes));
+ cfw.addPush(skipIndexes.length);
+ }
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addOptRuntimeInvoke("newArrayLiteral",
+ "([Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"I"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ }
+
+ private void visitObjectLiteral(Node node, Node child)
+ {
+ Object[] properties = (Object[])node.getProp(Node.OBJECT_IDS_PROP);
+ int count = properties.length;
+
+ // load array with property ids
+ addNewObjectArray(count);
+ for (int i = 0; i != count; ++i) {
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(i);
+ Object id = properties[i];
+ if (id instanceof String) {
+ cfw.addPush((String)id);
+ } else {
+ cfw.addPush(((Integer)id).intValue());
+ addScriptRuntimeInvoke("wrapInt", "(I)Ljava/lang/Integer;");
+ }
+ cfw.add(ByteCode.AASTORE);
+ }
+ // load array with property values
+ addNewObjectArray(count);
+ Node child2 = child;
+ for (int i = 0; i != count; ++i) {
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(i);
+ int childType = child.getType();
+ if (childType == Token.GET) {
+ generateExpression(child.getFirstChild(), node);
+ } else if (childType == Token.SET) {
+ generateExpression(child.getFirstChild(), node);
+ } else {
+ generateExpression(child, node);
+ }
+ cfw.add(ByteCode.AASTORE);
+ child = child.getNext();
+ }
+ // load array with getterSetter values
+ cfw.addPush(count);
+ cfw.add(ByteCode.NEWARRAY, ByteCode.T_INT);
+ for (int i = 0; i != count; ++i) {
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(i);
+ int childType = child2.getType();
+ if (childType == Token.GET) {
+ cfw.add(ByteCode.ICONST_M1);
+ } else if (childType == Token.SET) {
+ cfw.add(ByteCode.ICONST_1);
+ } else {
+ cfw.add(ByteCode.ICONST_0);
+ }
+ cfw.add(ByteCode.IASTORE);
+ child2 = child2.getNext();
+ }
+
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke("newObjectLiteral",
+ "([Ljava/lang/Object;"
+ +"[Ljava/lang/Object;"
+ +"[I"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ }
+
+ private void visitSpecialCall(Node node, int type, int specialType,
+ Node child)
+ {
+ cfw.addALoad(contextLocal);
+
+ if (type == Token.NEW) {
+ generateExpression(child, node);
+ // stack: ... cx functionObj
+ } else {
+ generateFunctionAndThisObj(child, node);
+ // stack: ... cx functionObj thisObj
+ }
+ child = child.getNext();
+
+ generateCallArgArray(node, child, false);
+
+ String methodName;
+ String callSignature;
+
+ if (type == Token.NEW) {
+ methodName = "newObjectSpecial";
+ callSignature = "(Lorg/mozilla/javascript/Context;"
+ +"Ljava/lang/Object;"
+ +"[Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"I" // call type
+ +")Ljava/lang/Object;";
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(thisObjLocal);
+ cfw.addPush(specialType);
+ } else {
+ methodName = "callSpecial";
+ callSignature = "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"I" // call type
+ +"Ljava/lang/String;I" // filename, linenumber
+ +")Ljava/lang/Object;";
+ cfw.addALoad(variableObjectLocal);
+ cfw.addALoad(thisObjLocal);
+ cfw.addPush(specialType);
+ String sourceName = scriptOrFn.getSourceName();
+ cfw.addPush(sourceName == null ? "" : sourceName);
+ cfw.addPush(itsLineNumber);
+ }
+
+ addOptRuntimeInvoke(methodName, callSignature);
+ }
+
+ private void visitStandardCall(Node node, Node child)
+ {
+ if (node.getType() != Token.CALL) throw Codegen.badTree();
+
+ Node firstArgChild = child.getNext();
+ int childType = child.getType();
+
+ String methodName;
+ String signature;
+
+ if (firstArgChild == null) {
+ if (childType == Token.NAME) {
+ // name() call
+ String name = child.getString();
+ cfw.addPush(name);
+ methodName = "callName0";
+ signature = "(Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ } else if (childType == Token.GETPROP) {
+ // x.name() call
+ Node propTarget = child.getFirstChild();
+ generateExpression(propTarget, node);
+ Node id = propTarget.getNext();
+ String property = id.getString();
+ cfw.addPush(property);
+ methodName = "callProp0";
+ signature = "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ } else if (childType == Token.GETPROPNOWARN) {
+ throw Kit.codeBug();
+ } else {
+ generateFunctionAndThisObj(child, node);
+ methodName = "call0";
+ signature = "(Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ }
+
+ } else if (childType == Token.NAME) {
+ // XXX: this optimization is only possible if name
+ // resolution
+ // is not affected by arguments evaluation and currently
+ // there are no checks for it
+ String name = child.getString();
+ generateCallArgArray(node, firstArgChild, false);
+ cfw.addPush(name);
+ methodName = "callName";
+ signature = "([Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ } else {
+ int argCount = 0;
+ for (Node arg = firstArgChild; arg != null; arg = arg.getNext()) {
+ ++argCount;
+ }
+ generateFunctionAndThisObj(child, node);
+ // stack: ... functionObj thisObj
+ if (argCount == 1) {
+ generateExpression(firstArgChild, node);
+ methodName = "call1";
+ signature = "(Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ } else if (argCount == 2) {
+ generateExpression(firstArgChild, node);
+ generateExpression(firstArgChild.getNext(), node);
+ methodName = "call2";
+ signature = "(Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ } else {
+ generateCallArgArray(node, firstArgChild, false);
+ methodName = "callN";
+ signature = "(Lorg/mozilla/javascript/Callable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;";
+ }
+ }
+
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addOptRuntimeInvoke(methodName, signature);
+ }
+
+ private void visitStandardNew(Node node, Node child)
+ {
+ if (node.getType() != Token.NEW) throw Codegen.badTree();
+
+ Node firstArgChild = child.getNext();
+
+ generateExpression(child, node);
+ // stack: ... functionObj
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ // stack: ... functionObj cx scope
+ generateCallArgArray(node, firstArgChild, false);
+ addScriptRuntimeInvoke(
+ "newObject",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ }
+
+ private void visitOptimizedCall(Node node, OptFunctionNode target,
+ int type, Node child)
+ {
+ Node firstArgChild = child.getNext();
+
+ short thisObjLocal = 0;
+ if (type == Token.NEW) {
+ generateExpression(child, node);
+ } else {
+ generateFunctionAndThisObj(child, node);
+ thisObjLocal = getNewWordLocal();
+ cfw.addAStore(thisObjLocal);
+ }
+ // stack: ... functionObj
+
+ int beyond = cfw.acquireLabel();
+
+ int directTargetIndex = target.getDirectTargetIndex();
+ if (isTopLevel) {
+ cfw.add(ByteCode.ALOAD_0);
+ } else {
+ cfw.add(ByteCode.ALOAD_0);
+ cfw.add(ByteCode.GETFIELD, codegen.mainClassName,
+ Codegen.DIRECT_CALL_PARENT_FIELD,
+ codegen.mainClassSignature);
+ }
+ cfw.add(ByteCode.GETFIELD, codegen.mainClassName,
+ Codegen.getDirectTargetFieldName(directTargetIndex),
+ codegen.mainClassSignature);
+
+ cfw.add(ByteCode.DUP2);
+ // stack: ... functionObj directFunct functionObj directFunct
+
+ int regularCall = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPNE, regularCall);
+
+ // stack: ... functionObj directFunct
+ short stackHeight = cfw.getStackTop();
+ cfw.add(ByteCode.SWAP);
+ cfw.add(ByteCode.POP);
+ // stack: ... directFunct
+ if (compilerEnv.isUseDynamicScope()) {
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ } else {
+ cfw.add(ByteCode.DUP);
+ // stack: ... directFunct directFunct
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/Scriptable",
+ "getParentScope",
+ "()Lorg/mozilla/javascript/Scriptable;");
+ // stack: ... directFunct scope
+ cfw.addALoad(contextLocal);
+ // stack: ... directFunct scope cx
+ cfw.add(ByteCode.SWAP);
+ }
+ // stack: ... directFunc cx scope
+
+ if (type == Token.NEW) {
+ cfw.add(ByteCode.ACONST_NULL);
+ } else {
+ cfw.addALoad(thisObjLocal);
+ }
+ // stack: ... directFunc cx scope thisObj
+/*
+Remember that directCall parameters are paired in 1 aReg and 1 dReg
+If the argument is an incoming arg, just pass the orginal pair thru.
+Else, if the argument is known to be typed 'Number', pass Void.TYPE
+in the aReg and the number is the dReg
+Else pass the JS object in the aReg and 0.0 in the dReg.
+*/
+ Node argChild = firstArgChild;
+ while (argChild != null) {
+ int dcp_register = nodeIsDirectCallParameter(argChild);
+ if (dcp_register >= 0) {
+ cfw.addALoad(dcp_register);
+ cfw.addDLoad(dcp_register + 1);
+ } else if (argChild.getIntProp(Node.ISNUMBER_PROP, -1)
+ == Node.BOTH)
+ {
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ generateExpression(argChild, node);
+ } else {
+ generateExpression(argChild, node);
+ cfw.addPush(0.0);
+ }
+ argChild = argChild.getNext();
+ }
+
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "emptyArgs", "[Ljava/lang/Object;");
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ codegen.mainClassName,
+ (type == Token.NEW)
+ ? codegen.getDirectCtorName(target.fnode)
+ : codegen.getBodyMethodName(target.fnode),
+ codegen.getBodyMethodSignature(target.fnode));
+
+ cfw.add(ByteCode.GOTO, beyond);
+
+ cfw.markLabel(regularCall, stackHeight);
+ // stack: ... functionObj directFunct
+ cfw.add(ByteCode.POP);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ // stack: ... functionObj cx scope
+ if (type != Token.NEW) {
+ cfw.addALoad(thisObjLocal);
+ releaseWordLocal(thisObjLocal);
+ // stack: ... functionObj cx scope thisObj
+ }
+ // XXX: this will generate code for the child array the second time,
+ // so expression code generation better not to alter tree structure...
+ generateCallArgArray(node, firstArgChild, true);
+
+ if (type == Token.NEW) {
+ addScriptRuntimeInvoke(
+ "newObject",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ } else {
+ cfw.addInvoke(ByteCode.INVOKEINTERFACE,
+ "org/mozilla/javascript/Callable",
+ "call",
+ "(Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"[Ljava/lang/Object;"
+ +")Ljava/lang/Object;");
+ }
+
+ cfw.markLabel(beyond);
+ }
+
+ private void generateCallArgArray(Node node, Node argChild, boolean directCall)
+ {
+ int argCount = 0;
+ for (Node child = argChild; child != null; child = child.getNext()) {
+ ++argCount;
+ }
+ // load array object to set arguments
+ if (argCount == 1 && itsOneArgArray >= 0) {
+ cfw.addALoad(itsOneArgArray);
+ } else {
+ addNewObjectArray(argCount);
+ }
+ // Copy arguments into it
+ for (int i = 0; i != argCount; ++i) {
+ // If we are compiling a generator an argument could be the result
+ // of a yield. In that case we will have an immediate on the stack
+ // which we need to avoid
+ if (!isGenerator) {
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(i);
+ }
+
+ if (!directCall) {
+ generateExpression(argChild, node);
+ } else {
+ // If this has also been a directCall sequence, the Number
+ // flag will have remained set for any parameter so that
+ // the values could be copied directly into the outgoing
+ // args. Here we want to force it to be treated as not in
+ // a Number context, so we set the flag off.
+ int dcp_register = nodeIsDirectCallParameter(argChild);
+ if (dcp_register >= 0) {
+ dcpLoadAsObject(dcp_register);
+ } else {
+ generateExpression(argChild, node);
+ int childNumberFlag
+ = argChild.getIntProp(Node.ISNUMBER_PROP, -1);
+ if (childNumberFlag == Node.BOTH) {
+ addDoubleWrap();
+ }
+ }
+ }
+
+ // When compiling generators, any argument to a method may be a
+ // yield expression. Hence we compile the argument first and then
+ // load the argument index and assign the value to the args array.
+ if (isGenerator) {
+ short tempLocal = getNewWordLocal();
+ cfw.addAStore(tempLocal);
+ cfw.add(ByteCode.CHECKCAST, "[Ljava/lang/Object;");
+ cfw.add(ByteCode.DUP);
+ cfw.addPush(i);
+ cfw.addALoad(tempLocal);
+ releaseWordLocal(tempLocal);
+ }
+
+ cfw.add(ByteCode.AASTORE);
+
+ argChild = argChild.getNext();
+ }
+ }
+
+ private void generateFunctionAndThisObj(Node node, Node parent)
+ {
+ // Place on stack (function object, function this) pair
+ int type = node.getType();
+ switch (node.getType()) {
+ case Token.GETPROPNOWARN:
+ throw Kit.codeBug();
+
+ case Token.GETPROP:
+ case Token.GETELEM: {
+ Node target = node.getFirstChild();
+ generateExpression(target, node);
+ Node id = target.getNext();
+ if (type == Token.GETPROP) {
+ String property = id.getString();
+ cfw.addPush(property);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke(
+ "getPropFunctionAndThis",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Callable;");
+ } else {
+ // Optimizer do not optimize this case for now
+ if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1)
+ throw Codegen.badTree();
+ generateExpression(id, node); // id
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getElemFunctionAndThis",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/Callable;");
+ }
+ break;
+ }
+
+ case Token.NAME: {
+ String name = node.getString();
+ cfw.addPush(name);
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke(
+ "getNameFunctionAndThis",
+ "(Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Callable;");
+ break;
+ }
+
+ default: // including GETVAR
+ generateExpression(node, parent);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getValueFunctionAndThis",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/Callable;");
+ break;
+ }
+ // Get thisObj prepared by get(Name|Prop|Elem|Value)FunctionAndThis
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "lastStoredScriptable",
+ "(Lorg/mozilla/javascript/Context;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ }
+
+ private void updateLineNumber(Node node)
+ {
+ itsLineNumber = node.getLineno();
+ if (itsLineNumber == -1)
+ return;
+ cfw.addLineNumberEntry((short)itsLineNumber);
+ }
+
+ private void visitTryCatchFinally(Node.Jump node, Node child)
+ {
+ /* Save the variable object, in case there are with statements
+ * enclosed by the try block and we catch some exception.
+ * We'll restore it for the catch block so that catch block
+ * statements get the right scope.
+ */
+
+ // OPT we only need to do this if there are enclosed WITH
+ // statements; could statically check and omit this if there aren't any.
+
+ // XXX OPT Maybe instead do syntactic transforms to associate
+ // each 'with' with a try/finally block that does the exitwith.
+
+ short savedVariableObject = getNewWordLocal();
+ cfw.addALoad(variableObjectLocal);
+ cfw.addAStore(savedVariableObject);
+
+ /*
+ * Generate the code for the tree; most of the work is done in IRFactory
+ * and NodeTransformer; Codegen just adds the java handlers for the
+ * javascript catch and finally clauses. */
+
+ int startLabel = cfw.acquireLabel();
+ cfw.markLabel(startLabel, (short)0);
+
+ Node catchTarget = node.target;
+ Node finallyTarget = node.getFinally();
+
+ // create a table for the equivalent of JSR returns
+ if (isGenerator && finallyTarget != null) {
+ FinallyReturnPoint ret = new FinallyReturnPoint();
+ if (finallys == null) {
+ finallys = new HashMap<Node,FinallyReturnPoint>();
+ }
+ // add the finally target to hashtable
+ finallys.put(finallyTarget, ret);
+ // add the finally node as well to the hash table
+ finallys.put(finallyTarget.getNext(), ret);
+ }
+
+ while (child != null) {
+ generateStatement(child);
+ child = child.getNext();
+ }
+
+ // control flow skips the handlers
+ int realEnd = cfw.acquireLabel();
+ cfw.add(ByteCode.GOTO, realEnd);
+
+ int exceptionLocal = getLocalBlockRegister(node);
+ // javascript handler; unwrap exception and GOTO to javascript
+ // catch area.
+ if (catchTarget != null) {
+ // get the label to goto
+ int catchLabel = catchTarget.labelId();
+
+ generateCatchBlock(JAVASCRIPT_EXCEPTION, savedVariableObject,
+ catchLabel, startLabel, exceptionLocal);
+ /*
+ * catch WrappedExceptions, see if they are wrapped
+ * JavaScriptExceptions. Otherwise, rethrow.
+ */
+ generateCatchBlock(EVALUATOR_EXCEPTION, savedVariableObject,
+ catchLabel, startLabel, exceptionLocal);
+
+ /*
+ we also need to catch EcmaErrors and feed the
+ associated error object to the handler
+ */
+ generateCatchBlock(ECMAERROR_EXCEPTION, savedVariableObject,
+ catchLabel, startLabel, exceptionLocal);
+
+ Context cx = Context.getCurrentContext();
+ if (cx != null &&
+ cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS))
+ {
+ generateCatchBlock(THROWABLE_EXCEPTION, savedVariableObject,
+ catchLabel, startLabel, exceptionLocal);
+ }
+ }
+
+ // finally handler; catch all exceptions, store to a local; JSR to
+ // the finally, then re-throw.
+ if (finallyTarget != null) {
+ int finallyHandler = cfw.acquireLabel();
+ cfw.markHandler(finallyHandler);
+ cfw.addAStore(exceptionLocal);
+
+ // reset the variable object local
+ cfw.addALoad(savedVariableObject);
+ cfw.addAStore(variableObjectLocal);
+
+ // get the label to JSR to
+ int finallyLabel = finallyTarget.labelId();
+ if (isGenerator)
+ addGotoWithReturn(finallyTarget);
+ else
+ cfw.add(ByteCode.JSR, finallyLabel);
+
+ // rethrow
+ cfw.addALoad(exceptionLocal);
+ if (isGenerator)
+ cfw.add(ByteCode.CHECKCAST, "java/lang/Throwable");
+ cfw.add(ByteCode.ATHROW);
+
+ // mark the handler
+ cfw.addExceptionHandler(startLabel, finallyLabel,
+ finallyHandler, null); // catch any
+ }
+ releaseWordLocal(savedVariableObject);
+ cfw.markLabel(realEnd);
+ }
+
+ private static final int JAVASCRIPT_EXCEPTION = 0;
+ private static final int EVALUATOR_EXCEPTION = 1;
+ private static final int ECMAERROR_EXCEPTION = 2;
+ private static final int THROWABLE_EXCEPTION = 3;
+
+ private void generateCatchBlock(int exceptionType,
+ short savedVariableObject,
+ int catchLabel, int startLabel,
+ int exceptionLocal)
+ {
+ int handler = cfw.acquireLabel();
+ cfw.markHandler(handler);
+
+ // MS JVM gets cranky if the exception object is left on the stack
+ cfw.addAStore(exceptionLocal);
+
+ // reset the variable object local
+ cfw.addALoad(savedVariableObject);
+ cfw.addAStore(variableObjectLocal);
+
+ String exceptionName;
+ if (exceptionType == JAVASCRIPT_EXCEPTION) {
+ exceptionName = "org/mozilla/javascript/JavaScriptException";
+ } else if (exceptionType == EVALUATOR_EXCEPTION) {
+ exceptionName = "org/mozilla/javascript/EvaluatorException";
+ } else if (exceptionType == ECMAERROR_EXCEPTION) {
+ exceptionName = "org/mozilla/javascript/EcmaError";
+ } else if (exceptionType == THROWABLE_EXCEPTION) {
+ exceptionName = "java/lang/Throwable";
+ } else {
+ throw Kit.codeBug();
+ }
+
+ // mark the handler
+ cfw.addExceptionHandler(startLabel, catchLabel, handler,
+ exceptionName);
+
+ cfw.add(ByteCode.GOTO, catchLabel);
+ }
+
+
+ private boolean generateSaveLocals(Node node)
+ {
+ int count = 0;
+ for (int i = 0; i < firstFreeLocal; i++) {
+ if (locals[i] != 0)
+ count++;
+ }
+
+ if (count == 0) {
+ ((FunctionNode)scriptOrFn).addLiveLocals(node, null);
+ return false;
+ }
+
+ // calculate the max locals
+ maxLocals = maxLocals > count ? maxLocals : count;
+
+ // create a locals list
+ int[] ls = new int[count];
+ int s = 0;
+ for (int i = 0; i < firstFreeLocal; i++) {
+ if (locals[i] != 0) {
+ ls[s] = i;
+ s++;
+ }
+ }
+
+ // save the locals
+ ((FunctionNode)scriptOrFn).addLiveLocals(node, ls);
+
+ // save locals
+ generateGetGeneratorLocalsState();
+ for (int i = 0; i < count; i++) {
+ cfw.add(ByteCode.DUP);
+ cfw.addLoadConstant(i);
+ cfw.addALoad(ls[i]);
+ cfw.add(ByteCode.AASTORE);
+ }
+ // pop the array off the stack
+ cfw.add(ByteCode.POP);
+
+ return true;
+ }
+
+ private void visitSwitch(Node.Jump switchNode, Node child)
+ {
+ // See comments in IRFactory.createSwitch() for description
+ // of SWITCH node
+
+ generateExpression(child, switchNode);
+ // save selector value
+ short selector = getNewWordLocal();
+ cfw.addAStore(selector);
+
+ for (Node.Jump caseNode = (Node.Jump)child.getNext();
+ caseNode != null;
+ caseNode = (Node.Jump)caseNode.getNext())
+ {
+ if (caseNode.getType() != Token.CASE)
+ throw Codegen.badTree();
+ Node test = caseNode.getFirstChild();
+ generateExpression(test, caseNode);
+ cfw.addALoad(selector);
+ addScriptRuntimeInvoke("shallowEq",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +")Z");
+ addGoto(caseNode.target, ByteCode.IFNE);
+ }
+ releaseWordLocal(selector);
+ }
+
+ private void visitTypeofname(Node node)
+ {
+ if (hasVarsInRegs) {
+ int varIndex = fnCurrent.fnode.getIndexForNameNode(node);
+ if (varIndex >= 0) {
+ if (fnCurrent.isNumberVar(varIndex)) {
+ cfw.addPush("number");
+ } else if (varIsDirectCallParameter(varIndex)) {
+ int dcp_register = varRegisters[varIndex];
+ cfw.addALoad(dcp_register);
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE",
+ "Ljava/lang/Class;");
+ int isNumberLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
+ short stack = cfw.getStackTop();
+ cfw.addALoad(dcp_register);
+ addScriptRuntimeInvoke("typeof",
+ "(Ljava/lang/Object;"
+ +")Ljava/lang/String;");
+ int beyond = cfw.acquireLabel();
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(isNumberLabel, stack);
+ cfw.addPush("number");
+ cfw.markLabel(beyond);
+ } else {
+ cfw.addALoad(varRegisters[varIndex]);
+ addScriptRuntimeInvoke("typeof",
+ "(Ljava/lang/Object;"
+ +")Ljava/lang/String;");
+ }
+ return;
+ }
+ }
+ cfw.addALoad(variableObjectLocal);
+ cfw.addPush(node.getString());
+ addScriptRuntimeInvoke("typeofName",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +")Ljava/lang/String;");
+ }
+
+ /**
+ * Save the current code offset. This saved code offset is used to
+ * compute instruction counts in subsequent calls to
+ * {@link #addInstructionCount()}.
+ */
+ private void saveCurrentCodeOffset() {
+ savedCodeOffset = cfw.getCurrentCodeOffset();
+ }
+
+ /**
+ * Generate calls to ScriptRuntime.addInstructionCount to keep track of
+ * executed instructions and call <code>observeInstructionCount()</code>
+ * if a threshold is exceeded.<br>
+ * Calculates the count from getCurrentCodeOffset - savedCodeOffset
+ */
+ private void addInstructionCount() {
+ int count = cfw.getCurrentCodeOffset() - savedCodeOffset;
+ if (count == 0)
+ return;
+ addInstructionCount(count);
+ }
+
+ /**
+ * Generate calls to ScriptRuntime.addInstructionCount to keep track of
+ * executed instructions and call <code>observeInstructionCount()</code>
+ * if a threshold is exceeded.<br>
+ * Takes the count as a parameter - used to add monitoring to loops and
+ * other blocks that don't have any ops - this allows
+ * for monitoring/killing of while(true) loops and such.
+ */
+ private void addInstructionCount(int count) {
+ cfw.addALoad(contextLocal);
+ cfw.addPush(count);
+ addScriptRuntimeInvoke("addInstructionCount",
+ "(Lorg/mozilla/javascript/Context;"
+ +"I)V");
+ }
+
+ private void visitIncDec(Node node)
+ {
+ int incrDecrMask = node.getExistingIntProp(Node.INCRDECR_PROP);
+ Node child = node.getFirstChild();
+ switch (child.getType()) {
+ case Token.GETVAR:
+ if (!hasVarsInRegs) Kit.codeBug();
+ if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
+ boolean post = ((incrDecrMask & Node.POST_FLAG) != 0);
+ int varIndex = fnCurrent.getVarIndex(child);
+ short reg = varRegisters[varIndex];
+ int offset = varIsDirectCallParameter(varIndex) ? 1 : 0;
+ cfw.addDLoad(reg + offset);
+ if (post) {
+ cfw.add(ByteCode.DUP2);
+ }
+ cfw.addPush(1.0);
+ if ((incrDecrMask & Node.DECR_FLAG) == 0) {
+ cfw.add(ByteCode.DADD);
+ } else {
+ cfw.add(ByteCode.DSUB);
+ }
+ if (!post) {
+ cfw.add(ByteCode.DUP2);
+ }
+ cfw.addDStore(reg + offset);
+ } else {
+ boolean post = ((incrDecrMask & Node.POST_FLAG) != 0);
+ int varIndex = fnCurrent.getVarIndex(child);
+ short reg = varRegisters[varIndex];
+ cfw.addALoad(reg);
+ if (post) {
+ cfw.add(ByteCode.DUP);
+ }
+ addObjectToDouble();
+ cfw.addPush(1.0);
+ if ((incrDecrMask & Node.DECR_FLAG) == 0) {
+ cfw.add(ByteCode.DADD);
+ } else {
+ cfw.add(ByteCode.DSUB);
+ }
+ addDoubleWrap();
+ if (!post) {
+ cfw.add(ByteCode.DUP);
+ }
+ cfw.addAStore(reg);
+ break;
+ }
+ break;
+ case Token.NAME:
+ cfw.addALoad(variableObjectLocal);
+ cfw.addPush(child.getString()); // push name
+ cfw.addALoad(contextLocal);
+ cfw.addPush(incrDecrMask);
+ addScriptRuntimeInvoke("nameIncrDecr",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I)Ljava/lang/Object;");
+ break;
+ case Token.GETPROPNOWARN:
+ throw Kit.codeBug();
+ case Token.GETPROP: {
+ Node getPropChild = child.getFirstChild();
+ generateExpression(getPropChild, node);
+ generateExpression(getPropChild.getNext(), node);
+ cfw.addALoad(contextLocal);
+ cfw.addPush(incrDecrMask);
+ addScriptRuntimeInvoke("propIncrDecr",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I)Ljava/lang/Object;");
+ break;
+ }
+ case Token.GETELEM: {
+ Node elemChild = child.getFirstChild();
+ generateExpression(elemChild, node);
+ generateExpression(elemChild.getNext(), node);
+ cfw.addALoad(contextLocal);
+ cfw.addPush(incrDecrMask);
+ if (elemChild.getNext().getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
+ addOptRuntimeInvoke("elemIncrDecr",
+ "(Ljava/lang/Object;"
+ +"D"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I"
+ +")Ljava/lang/Object;");
+ } else {
+ addScriptRuntimeInvoke("elemIncrDecr",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I"
+ +")Ljava/lang/Object;");
+ }
+ break;
+ }
+ case Token.GET_REF: {
+ Node refChild = child.getFirstChild();
+ generateExpression(refChild, node);
+ cfw.addALoad(contextLocal);
+ cfw.addPush(incrDecrMask);
+ addScriptRuntimeInvoke(
+ "refIncrDecr",
+ "(Lorg/mozilla/javascript/Ref;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"I)Ljava/lang/Object;");
+ break;
+ }
+ default:
+ Codegen.badTree();
+ }
+ }
+
+ private static boolean isArithmeticNode(Node node)
+ {
+ int type = node.getType();
+ return (type == Token.SUB)
+ || (type == Token.MOD)
+ || (type == Token.DIV)
+ || (type == Token.MUL);
+ }
+
+ private void visitArithmetic(Node node, int opCode, Node child,
+ Node parent)
+ {
+ int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
+ if (childNumberFlag != -1) {
+ generateExpression(child, node);
+ generateExpression(child.getNext(), node);
+ cfw.add(opCode);
+ }
+ else {
+ boolean childOfArithmetic = isArithmeticNode(parent);
+ generateExpression(child, node);
+ if (!isArithmeticNode(child))
+ addObjectToDouble();
+ generateExpression(child.getNext(), node);
+ if (!isArithmeticNode(child.getNext()))
+ addObjectToDouble();
+ cfw.add(opCode);
+ if (!childOfArithmetic) {
+ addDoubleWrap();
+ }
+ }
+ }
+
+ private void visitBitOp(Node node, int type, Node child)
+ {
+ int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
+ generateExpression(child, node);
+
+ // special-case URSH; work with the target arg as a long, so
+ // that we can return a 32-bit unsigned value, and call
+ // toUint32 instead of toInt32.
+ if (type == Token.URSH) {
+ addScriptRuntimeInvoke("toUint32", "(Ljava/lang/Object;)J");
+ generateExpression(child.getNext(), node);
+ addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I");
+ // Looks like we need to explicitly mask the shift to 5 bits -
+ // LUSHR takes 6 bits.
+ cfw.addPush(31);
+ cfw.add(ByteCode.IAND);
+ cfw.add(ByteCode.LUSHR);
+ cfw.add(ByteCode.L2D);
+ addDoubleWrap();
+ return;
+ }
+ if (childNumberFlag == -1) {
+ addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I");
+ generateExpression(child.getNext(), node);
+ addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I");
+ }
+ else {
+ addScriptRuntimeInvoke("toInt32", "(D)I");
+ generateExpression(child.getNext(), node);
+ addScriptRuntimeInvoke("toInt32", "(D)I");
+ }
+ switch (type) {
+ case Token.BITOR:
+ cfw.add(ByteCode.IOR);
+ break;
+ case Token.BITXOR:
+ cfw.add(ByteCode.IXOR);
+ break;
+ case Token.BITAND:
+ cfw.add(ByteCode.IAND);
+ break;
+ case Token.RSH:
+ cfw.add(ByteCode.ISHR);
+ break;
+ case Token.LSH:
+ cfw.add(ByteCode.ISHL);
+ break;
+ default:
+ throw Codegen.badTree();
+ }
+ cfw.add(ByteCode.I2D);
+ if (childNumberFlag == -1) {
+ addDoubleWrap();
+ }
+ }
+
+ private int nodeIsDirectCallParameter(Node node)
+ {
+ if (node.getType() == Token.GETVAR
+ && inDirectCallFunction && !itsForcedObjectParameters)
+ {
+ int varIndex = fnCurrent.getVarIndex(node);
+ if (fnCurrent.isParameter(varIndex)) {
+ return varRegisters[varIndex];
+ }
+ }
+ return -1;
+ }
+
+ private boolean varIsDirectCallParameter(int varIndex)
+ {
+ return fnCurrent.isParameter(varIndex)
+ && inDirectCallFunction && !itsForcedObjectParameters;
+ }
+
+ private void genSimpleCompare(int type, int trueGOTO, int falseGOTO)
+ {
+ if (trueGOTO == -1) throw Codegen.badTree();
+ switch (type) {
+ case Token.LE :
+ cfw.add(ByteCode.DCMPG);
+ cfw.add(ByteCode.IFLE, trueGOTO);
+ break;
+ case Token.GE :
+ cfw.add(ByteCode.DCMPL);
+ cfw.add(ByteCode.IFGE, trueGOTO);
+ break;
+ case Token.LT :
+ cfw.add(ByteCode.DCMPG);
+ cfw.add(ByteCode.IFLT, trueGOTO);
+ break;
+ case Token.GT :
+ cfw.add(ByteCode.DCMPL);
+ cfw.add(ByteCode.IFGT, trueGOTO);
+ break;
+ default :
+ throw Codegen.badTree();
+
+ }
+ if (falseGOTO != -1)
+ cfw.add(ByteCode.GOTO, falseGOTO);
+ }
+
+ private void visitIfJumpRelOp(Node node, Node child,
+ int trueGOTO, int falseGOTO)
+ {
+ if (trueGOTO == -1 || falseGOTO == -1) throw Codegen.badTree();
+ int type = node.getType();
+ Node rChild = child.getNext();
+ if (type == Token.INSTANCEOF || type == Token.IN) {
+ generateExpression(child, node);
+ generateExpression(rChild, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ (type == Token.INSTANCEOF) ? "instanceOf" : "in",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Z");
+ cfw.add(ByteCode.IFNE, trueGOTO);
+ cfw.add(ByteCode.GOTO, falseGOTO);
+ return;
+ }
+ int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1);
+ int left_dcp_register = nodeIsDirectCallParameter(child);
+ int right_dcp_register = nodeIsDirectCallParameter(rChild);
+ if (childNumberFlag != -1) {
+ // Force numeric context on both parameters and optimize
+ // direct call case as Optimizer currently does not handle it
+
+ if (childNumberFlag != Node.RIGHT) {
+ // Left already has number content
+ generateExpression(child, node);
+ } else if (left_dcp_register != -1) {
+ dcpLoadAsNumber(left_dcp_register);
+ } else {
+ generateExpression(child, node);
+ addObjectToDouble();
+ }
+
+ if (childNumberFlag != Node.LEFT) {
+ // Right already has number content
+ generateExpression(rChild, node);
+ } else if (right_dcp_register != -1) {
+ dcpLoadAsNumber(right_dcp_register);
+ } else {
+ generateExpression(rChild, node);
+ addObjectToDouble();
+ }
+
+ genSimpleCompare(type, trueGOTO, falseGOTO);
+
+ } else {
+ if (left_dcp_register != -1 && right_dcp_register != -1) {
+ // Generate code to dynamically check for number content
+ // if both operands are dcp
+ short stack = cfw.getStackTop();
+ int leftIsNotNumber = cfw.acquireLabel();
+ cfw.addALoad(left_dcp_register);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ cfw.add(ByteCode.IF_ACMPNE, leftIsNotNumber);
+ cfw.addDLoad(left_dcp_register + 1);
+ dcpLoadAsNumber(right_dcp_register);
+ genSimpleCompare(type, trueGOTO, falseGOTO);
+ if (stack != cfw.getStackTop()) throw Codegen.badTree();
+
+ cfw.markLabel(leftIsNotNumber);
+ int rightIsNotNumber = cfw.acquireLabel();
+ cfw.addALoad(right_dcp_register);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ cfw.add(ByteCode.IF_ACMPNE, rightIsNotNumber);
+ cfw.addALoad(left_dcp_register);
+ addObjectToDouble();
+ cfw.addDLoad(right_dcp_register + 1);
+ genSimpleCompare(type, trueGOTO, falseGOTO);
+ if (stack != cfw.getStackTop()) throw Codegen.badTree();
+
+ cfw.markLabel(rightIsNotNumber);
+ // Load both register as objects to call generic cmp_*
+ cfw.addALoad(left_dcp_register);
+ cfw.addALoad(right_dcp_register);
+
+ } else {
+ generateExpression(child, node);
+ generateExpression(rChild, node);
+ }
+
+ if (type == Token.GE || type == Token.GT) {
+ cfw.add(ByteCode.SWAP);
+ }
+ String routine = ((type == Token.LT)
+ || (type == Token.GT)) ? "cmp_LT" : "cmp_LE";
+ addScriptRuntimeInvoke(routine,
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +")Z");
+ cfw.add(ByteCode.IFNE, trueGOTO);
+ cfw.add(ByteCode.GOTO, falseGOTO);
+ }
+ }
+
+ private void visitIfJumpEqOp(Node node, Node child,
+ int trueGOTO, int falseGOTO)
+ {
+ if (trueGOTO == -1 || falseGOTO == -1) throw Codegen.badTree();
+
+ short stackInitial = cfw.getStackTop();
+ int type = node.getType();
+ Node rChild = child.getNext();
+
+ // Optimize if one of operands is null
+ if (child.getType() == Token.NULL || rChild.getType() == Token.NULL) {
+ // eq is symmetric in this case
+ if (child.getType() == Token.NULL) {
+ child = rChild;
+ }
+ generateExpression(child, node);
+ if (type == Token.SHEQ || type == Token.SHNE) {
+ int testCode = (type == Token.SHEQ)
+ ? ByteCode.IFNULL : ByteCode.IFNONNULL;
+ cfw.add(testCode, trueGOTO);
+ } else {
+ if (type != Token.EQ) {
+ // swap false/true targets for !=
+ if (type != Token.NE) throw Codegen.badTree();
+ int tmp = trueGOTO;
+ trueGOTO = falseGOTO;
+ falseGOTO = tmp;
+ }
+ cfw.add(ByteCode.DUP);
+ int undefCheckLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.IFNONNULL, undefCheckLabel);
+ short stack = cfw.getStackTop();
+ cfw.add(ByteCode.POP);
+ cfw.add(ByteCode.GOTO, trueGOTO);
+ cfw.markLabel(undefCheckLabel, stack);
+ Codegen.pushUndefined(cfw);
+ cfw.add(ByteCode.IF_ACMPEQ, trueGOTO);
+ }
+ cfw.add(ByteCode.GOTO, falseGOTO);
+ } else {
+ int child_dcp_register = nodeIsDirectCallParameter(child);
+ if (child_dcp_register != -1
+ && rChild.getType() == Token.TO_OBJECT)
+ {
+ Node convertChild = rChild.getFirstChild();
+ if (convertChild.getType() == Token.NUMBER) {
+ cfw.addALoad(child_dcp_register);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ int notNumbersLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPNE, notNumbersLabel);
+ cfw.addDLoad(child_dcp_register + 1);
+ cfw.addPush(convertChild.getDouble());
+ cfw.add(ByteCode.DCMPL);
+ if (type == Token.EQ)
+ cfw.add(ByteCode.IFEQ, trueGOTO);
+ else
+ cfw.add(ByteCode.IFNE, trueGOTO);
+ cfw.add(ByteCode.GOTO, falseGOTO);
+ cfw.markLabel(notNumbersLabel);
+ // fall thru into generic handling
+ }
+ }
+
+ generateExpression(child, node);
+ generateExpression(rChild, node);
+
+ String name;
+ int testCode;
+ switch (type) {
+ case Token.EQ:
+ name = "eq";
+ testCode = ByteCode.IFNE;
+ break;
+ case Token.NE:
+ name = "eq";
+ testCode = ByteCode.IFEQ;
+ break;
+ case Token.SHEQ:
+ name = "shallowEq";
+ testCode = ByteCode.IFNE;
+ break;
+ case Token.SHNE:
+ name = "shallowEq";
+ testCode = ByteCode.IFEQ;
+ break;
+ default:
+ throw Codegen.badTree();
+ }
+ addScriptRuntimeInvoke(name,
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +")Z");
+ cfw.add(testCode, trueGOTO);
+ cfw.add(ByteCode.GOTO, falseGOTO);
+ }
+ if (stackInitial != cfw.getStackTop()) throw Codegen.badTree();
+ }
+
+ private void visitSetName(Node node, Node child)
+ {
+ String name = node.getFirstChild().getString();
+ while (child != null) {
+ generateExpression(child, node);
+ child = child.getNext();
+ }
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ cfw.addPush(name);
+ addScriptRuntimeInvoke(
+ "setName",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +")Ljava/lang/Object;");
+ }
+
+ private void visitSetConst(Node node, Node child)
+ {
+ String name = node.getFirstChild().getString();
+ while (child != null) {
+ generateExpression(child, node);
+ child = child.getNext();
+ }
+ cfw.addALoad(contextLocal);
+ cfw.addPush(name);
+ addScriptRuntimeInvoke(
+ "setConst",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Ljava/lang/String;"
+ +")Ljava/lang/Object;");
+ }
+
+ private void visitGetVar(Node node)
+ {
+ if (!hasVarsInRegs) Kit.codeBug();
+ int varIndex = fnCurrent.getVarIndex(node);
+ short reg = varRegisters[varIndex];
+ if (varIsDirectCallParameter(varIndex)) {
+ // Remember that here the isNumber flag means that we
+ // want to use the incoming parameter in a Number
+ // context, so test the object type and convert the
+ // value as necessary.
+ if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) {
+ dcpLoadAsNumber(reg);
+ } else {
+ dcpLoadAsObject(reg);
+ }
+ } else if (fnCurrent.isNumberVar(varIndex)) {
+ cfw.addDLoad(reg);
+ } else {
+ cfw.addALoad(reg);
+ }
+ }
+
+ private void visitSetVar(Node node, Node child, boolean needValue)
+ {
+ if (!hasVarsInRegs) Kit.codeBug();
+ int varIndex = fnCurrent.getVarIndex(node);
+ generateExpression(child.getNext(), node);
+ boolean isNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1);
+ short reg = varRegisters[varIndex];
+ boolean [] constDeclarations = fnCurrent.fnode.getParamAndVarConst();
+ if (constDeclarations[varIndex]) {
+ if (!needValue) {
+ if (isNumber)
+ cfw.add(ByteCode.POP2);
+ else
+ cfw.add(ByteCode.POP);
+ }
+ }
+ else if (varIsDirectCallParameter(varIndex)) {
+ if (isNumber) {
+ if (needValue) cfw.add(ByteCode.DUP2);
+ cfw.addALoad(reg);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ int isNumberLabel = cfw.acquireLabel();
+ int beyond = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
+ short stack = cfw.getStackTop();
+ addDoubleWrap();
+ cfw.addAStore(reg);
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(isNumberLabel, stack);
+ cfw.addDStore(reg + 1);
+ cfw.markLabel(beyond);
+ }
+ else {
+ if (needValue) cfw.add(ByteCode.DUP);
+ cfw.addAStore(reg);
+ }
+ } else {
+ boolean isNumberVar = fnCurrent.isNumberVar(varIndex);
+ if (isNumber) {
+ if (isNumberVar) {
+ cfw.addDStore(reg);
+ if (needValue) cfw.addDLoad(reg);
+ } else {
+ if (needValue) cfw.add(ByteCode.DUP2);
+ // Cannot save number in variable since !isNumberVar,
+ // so convert to object
+ addDoubleWrap();
+ cfw.addAStore(reg);
+ }
+ } else {
+ if (isNumberVar) Kit.codeBug();
+ cfw.addAStore(reg);
+ if (needValue) cfw.addALoad(reg);
+ }
+ }
+ }
+
+ private void visitSetConstVar(Node node, Node child, boolean needValue)
+ {
+ if (!hasVarsInRegs) Kit.codeBug();
+ int varIndex = fnCurrent.getVarIndex(node);
+ generateExpression(child.getNext(), node);
+ boolean isNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1);
+ short reg = varRegisters[varIndex];
+ int beyond = cfw.acquireLabel();
+ int noAssign = cfw.acquireLabel();
+ if (isNumber) {
+ cfw.addILoad(reg + 2);
+ cfw.add(ByteCode.IFNE, noAssign);
+ short stack = cfw.getStackTop();
+ cfw.addPush(1);
+ cfw.addIStore(reg + 2);
+ cfw.addDStore(reg);
+ if (needValue) {
+ cfw.addDLoad(reg);
+ cfw.markLabel(noAssign, stack);
+ } else {
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(noAssign, stack);
+ cfw.add(ByteCode.POP2);
+ }
+ }
+ else {
+ cfw.addILoad(reg + 1);
+ cfw.add(ByteCode.IFNE, noAssign);
+ short stack = cfw.getStackTop();
+ cfw.addPush(1);
+ cfw.addIStore(reg + 1);
+ cfw.addAStore(reg);
+ if (needValue) {
+ cfw.addALoad(reg);
+ cfw.markLabel(noAssign, stack);
+ } else {
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(noAssign, stack);
+ cfw.add(ByteCode.POP);
+ }
+ }
+ cfw.markLabel(beyond);
+ }
+
+ private void visitGetProp(Node node, Node child)
+ {
+ generateExpression(child, node); // object
+ Node nameChild = child.getNext();
+ generateExpression(nameChild, node); // the name
+ if (node.getType() == Token.GETPROPNOWARN) {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getObjectPropNoWarn",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ return;
+ }
+ /*
+ for 'this.foo' we call getObjectProp(Scriptable...) which can
+ skip some casting overhead.
+ */
+ int childType = child.getType();
+ if (childType == Token.THIS && nameChild.getType() == Token.STRING) {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getObjectProp",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ } else {
+ cfw.addALoad(contextLocal);
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke(
+ "getObjectProp",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;");
+ }
+ }
+
+ private void visitSetProp(int type, Node node, Node child)
+ {
+ Node objectChild = child;
+ generateExpression(child, node);
+ child = child.getNext();
+ if (type == Token.SETPROP_OP) {
+ cfw.add(ByteCode.DUP);
+ }
+ Node nameChild = child;
+ generateExpression(child, node);
+ child = child.getNext();
+ if (type == Token.SETPROP_OP) {
+ // stack: ... object object name -> ... object name object name
+ cfw.add(ByteCode.DUP_X1);
+ //for 'this.foo += ...' we call thisGet which can skip some
+ //casting overhead.
+ if (objectChild.getType() == Token.THIS
+ && nameChild.getType() == Token.STRING)
+ {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getObjectProp",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ } else {
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getObjectProp",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ }
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "setObjectProp",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/String;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+
+ private void visitSetElem(int type, Node node, Node child)
+ {
+ generateExpression(child, node);
+ child = child.getNext();
+ if (type == Token.SETELEM_OP) {
+ cfw.add(ByteCode.DUP);
+ }
+ generateExpression(child, node);
+ child = child.getNext();
+ boolean indexIsNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1);
+ if (type == Token.SETELEM_OP) {
+ if (indexIsNumber) {
+ // stack: ... object object number
+ // -> ... object number object number
+ cfw.add(ByteCode.DUP2_X1);
+ cfw.addALoad(contextLocal);
+ addOptRuntimeInvoke(
+ "getObjectIndex",
+ "(Ljava/lang/Object;D"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ } else {
+ // stack: ... object object indexObject
+ // -> ... object indexObject object indexObject
+ cfw.add(ByteCode.DUP_X1);
+ cfw.addALoad(contextLocal);
+ addScriptRuntimeInvoke(
+ "getObjectElem",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ }
+ generateExpression(child, node);
+ cfw.addALoad(contextLocal);
+ if (indexIsNumber) {
+ addScriptRuntimeInvoke(
+ "setObjectIndex",
+ "(Ljava/lang/Object;"
+ +"D"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ } else {
+ addScriptRuntimeInvoke(
+ "setObjectElem",
+ "(Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Context;"
+ +")Ljava/lang/Object;");
+ }
+ }
+
+ private void visitDotQuery(Node node, Node child)
+ {
+ updateLineNumber(node);
+ generateExpression(child, node);
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke("enterDotQuery",
+ "(Ljava/lang/Object;"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+
+ // add push null/pop with label in between to simplify code for loop
+ // continue when it is necessary to pop the null result from
+ // updateDotQuery
+ cfw.add(ByteCode.ACONST_NULL);
+ int queryLoopStart = cfw.acquireLabel();
+ cfw.markLabel(queryLoopStart); // loop continue jumps here
+ cfw.add(ByteCode.POP);
+
+ generateExpression(child.getNext(), node);
+ addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z");
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke("updateDotQuery",
+ "(Z"
+ +"Lorg/mozilla/javascript/Scriptable;"
+ +")Ljava/lang/Object;");
+ cfw.add(ByteCode.DUP);
+ cfw.add(ByteCode.IFNULL, queryLoopStart);
+ // stack: ... non_null_result_of_updateDotQuery
+ cfw.addALoad(variableObjectLocal);
+ addScriptRuntimeInvoke("leaveDotQuery",
+ "(Lorg/mozilla/javascript/Scriptable;"
+ +")Lorg/mozilla/javascript/Scriptable;");
+ cfw.addAStore(variableObjectLocal);
+ }
+
+ private int getLocalBlockRegister(Node node)
+ {
+ Node localBlock = (Node)node.getProp(Node.LOCAL_BLOCK_PROP);
+ int localSlot = localBlock.getExistingIntProp(Node.LOCAL_PROP);
+ return localSlot;
+ }
+
+ private void dcpLoadAsNumber(int dcp_register)
+ {
+ cfw.addALoad(dcp_register);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ int isNumberLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
+ short stack = cfw.getStackTop();
+ cfw.addALoad(dcp_register);
+ addObjectToDouble();
+ int beyond = cfw.acquireLabel();
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(isNumberLabel, stack);
+ cfw.addDLoad(dcp_register + 1);
+ cfw.markLabel(beyond);
+ }
+
+ private void dcpLoadAsObject(int dcp_register)
+ {
+ cfw.addALoad(dcp_register);
+ cfw.add(ByteCode.GETSTATIC,
+ "java/lang/Void",
+ "TYPE",
+ "Ljava/lang/Class;");
+ int isNumberLabel = cfw.acquireLabel();
+ cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel);
+ short stack = cfw.getStackTop();
+ cfw.addALoad(dcp_register);
+ int beyond = cfw.acquireLabel();
+ cfw.add(ByteCode.GOTO, beyond);
+ cfw.markLabel(isNumberLabel, stack);
+ cfw.addDLoad(dcp_register + 1);
+ addDoubleWrap();
+ cfw.markLabel(beyond);
+ }
+
+ private void addGoto(Node target, int jumpcode)
+ {
+ int targetLabel = getTargetLabel(target);
+ cfw.add(jumpcode, targetLabel);
+ }
+
+ private void addObjectToDouble()
+ {
+ addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)D");
+ }
+
+ private void addNewObjectArray(int size)
+ {
+ if (size == 0) {
+ if (itsZeroArgArray >= 0) {
+ cfw.addALoad(itsZeroArgArray);
+ } else {
+ cfw.add(ByteCode.GETSTATIC,
+ "org/mozilla/javascript/ScriptRuntime",
+ "emptyArgs", "[Ljava/lang/Object;");
+ }
+ } else {
+ cfw.addPush(size);
+ cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
+ }
+ }
+
+ private void addScriptRuntimeInvoke(String methodName,
+ String methodSignature)
+ {
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org.mozilla.javascript.ScriptRuntime",
+ methodName,
+ methodSignature);
+ }
+
+ private void addOptRuntimeInvoke(String methodName,
+ String methodSignature)
+ {
+ cfw.addInvoke(ByteCode.INVOKESTATIC,
+ "org/mozilla/javascript/optimizer/OptRuntime",
+ methodName,
+ methodSignature);
+ }
+
+ private void addJumpedBooleanWrap(int trueLabel, int falseLabel)
+ {
+ cfw.markLabel(falseLabel);
+ int skip = cfw.acquireLabel();
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean",
+ "FALSE", "Ljava/lang/Boolean;");
+ cfw.add(ByteCode.GOTO, skip);
+ cfw.markLabel(trueLabel);
+ cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean",
+ "TRUE", "Ljava/lang/Boolean;");
+ cfw.markLabel(skip);
+ cfw.adjustStackTop(-1); // only have 1 of true/false
+ }
+
+ private void addDoubleWrap()
+ {
+ addOptRuntimeInvoke("wrapDouble", "(D)Ljava/lang/Double;");
+ }
+
+ /**
+ * Const locals use an extra slot to hold the has-been-assigned-once flag at
+ * runtime.
+ * @param isConst true iff the variable is const
+ * @return the register for the word pair (double/long)
+ */
+ private short getNewWordPairLocal(boolean isConst)
+ {
+ short result = getConsecutiveSlots(2, isConst);
+ if (result < (MAX_LOCALS - 1)) {
+ locals[result] = 1;
+ locals[result + 1] = 1;
+ if (isConst)
+ locals[result + 2] = 1;
+ if (result == firstFreeLocal) {
+ for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) {
+ if (locals[i] == 0) {
+ firstFreeLocal = (short) i;
+ if (localsMax < firstFreeLocal)
+ localsMax = firstFreeLocal;
+ return result;
+ }
+ }
+ }
+ else {
+ return result;
+ }
+ }
+ throw Context.reportRuntimeError("Program too complex " +
+ "(out of locals)");
+ }
+
+ private short getNewWordLocal(boolean isConst)
+ {
+ short result = getConsecutiveSlots(1, isConst);
+ if (result < (MAX_LOCALS - 1)) {
+ locals[result] = 1;
+ if (isConst)
+ locals[result + 1] = 1;
+ if (result == firstFreeLocal) {
+ for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) {
+ if (locals[i] == 0) {
+ firstFreeLocal = (short) i;
+ if (localsMax < firstFreeLocal)
+ localsMax = firstFreeLocal;
+ return result;
+ }
+ }
+ }
+ else {
+ return result;
+ }
+ }
+ throw Context.reportRuntimeError("Program too complex " +
+ "(out of locals)");
+ }
+
+ private short getNewWordLocal()
+ {
+ short result = firstFreeLocal;
+ locals[result] = 1;
+ for (int i = firstFreeLocal + 1; i < MAX_LOCALS; i++) {
+ if (locals[i] == 0) {
+ firstFreeLocal = (short) i;
+ if (localsMax < firstFreeLocal)
+ localsMax = firstFreeLocal;
+ return result;
+ }
+ }
+ throw Context.reportRuntimeError("Program too complex " +
+ "(out of locals)");
+ }
+
+ private short getConsecutiveSlots(int count, boolean isConst) {
+ if (isConst)
+ count++;
+ short result = firstFreeLocal;
+ while (true) {
+ if (result >= (MAX_LOCALS - 1))
+ break;
+ int i;
+ for (i = 0; i < count; i++)
+ if (locals[result + i] != 0)
+ break;
+ if (i >= count)
+ break;
+ result++;
+ }
+ return result;
+ }
+
+ // This is a valid call only for a local that is allocated by default.
+ private void incReferenceWordLocal(short local)
+ {
+ locals[local]++;
+ }
+
+ // This is a valid call only for a local that is allocated by default.
+ private void decReferenceWordLocal(short local)
+ {
+ locals[local]--;
+ }
+
+ private void releaseWordLocal(short local)
+ {
+ if (local < firstFreeLocal)
+ firstFreeLocal = local;
+ locals[local] = 0;
+ }
+
+
+ static final int GENERATOR_TERMINATE = -1;
+ static final int GENERATOR_START = 0;
+ static final int GENERATOR_YIELD_START = 1;
+
+ ClassFileWriter cfw;
+ Codegen codegen;
+ CompilerEnvirons compilerEnv;
+ ScriptOrFnNode scriptOrFn;
+ public int scriptOrFnIndex;
+ private int savedCodeOffset;
+
+ private OptFunctionNode fnCurrent;
+ private boolean isTopLevel;
+
+ private static final int MAX_LOCALS = 256;
+ private int[] locals;
+ private short firstFreeLocal;
+ private short localsMax;
+
+ private int itsLineNumber;
+
+ private boolean hasVarsInRegs;
+ private short[] varRegisters;
+ private boolean inDirectCallFunction;
+ private boolean itsForcedObjectParameters;
+ private int enterAreaStartLabel;
+ private int epilogueLabel;
+
+ // special known locals. If you add a new local here, be sure
+ // to initialize it to -1 in initBodyGeneration
+ private short variableObjectLocal;
+ private short popvLocal;
+ private short contextLocal;
+ private short argsLocal;
+ private short operationLocal;
+ private short thisObjLocal;
+ private short funObjLocal;
+ private short itsZeroArgArray;
+ private short itsOneArgArray;
+ private short scriptRegexpLocal;
+ private short generatorStateLocal;
+
+ private boolean isGenerator;
+ private int generatorSwitch;
+ private int maxLocals = 0;
+ private int maxStack = 0;
+
+ private Map<Node,FinallyReturnPoint> finallys;
+
+ class FinallyReturnPoint {
+ public List<Integer> jsrPoints = new ArrayList<Integer>();
+ public int tableLabel = 0;
+ }
+}
diff --git a/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java b/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java
new file mode 100644
index 0000000..e72732d
--- /dev/null
+++ b/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java
@@ -0,0 +1,135 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+
+package org.mozilla.javascript.optimizer;
+
+class DataFlowBitSet {
+
+ private int itsBits[];
+ private int itsSize;
+
+ DataFlowBitSet(int size)
+ {
+ itsSize = size;
+ itsBits = new int[(size + 31) >> 5];
+ }
+
+ void set(int n)
+ {
+ if (!(0 <= n && n < itsSize)) badIndex(n);
+ itsBits[n >> 5] |= 1 << (n & 31);
+ }
+
+ boolean test(int n)
+ {
+ if (!(0 <= n && n < itsSize)) badIndex(n);
+ return ((itsBits[n >> 5] & (1 << (n & 31))) != 0);
+ }
+
+ void not()
+ {
+ int bitsLength = itsBits.length;
+ for (int i = 0; i < bitsLength; i++)
+ itsBits[i] = ~itsBits[i];
+ }
+
+ void clear(int n)
+ {
+ if (!(0 <= n && n < itsSize)) badIndex(n);
+ itsBits[n >> 5] &= ~(1 << (n & 31));
+ }
+
+ void clear()
+ {
+ int bitsLength = itsBits.length;
+ for (int i = 0; i < bitsLength; i++)
+ itsBits[i] = 0;
+ }
+
+ void or(DataFlowBitSet b)
+ {
+ int bitsLength = itsBits.length;
+ for (int i = 0; i < bitsLength; i++)
+ itsBits[i] |= b.itsBits[i];
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append("DataFlowBitSet, size = ");
+ sb.append(itsSize);
+ sb.append('\n');
+ int bitsLength = itsBits.length;
+ for (int i = 0; i < bitsLength; i++) {
+ sb.append(Integer.toHexString(itsBits[i]));
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+
+ boolean df(DataFlowBitSet in, DataFlowBitSet gen, DataFlowBitSet notKill)
+ {
+ int bitsLength = itsBits.length;
+ boolean changed = false;
+ for (int i = 0; i < bitsLength; i++) {
+ int oldBits = itsBits[i];
+ itsBits[i] = (in.itsBits[i] | gen.itsBits[i]) & notKill.itsBits[i];
+ changed |= (oldBits != itsBits[i]);
+ }
+ return changed;
+ }
+
+ boolean df2(DataFlowBitSet in, DataFlowBitSet gen, DataFlowBitSet notKill)
+ {
+ int bitsLength = itsBits.length;
+ boolean changed = false;
+ for (int i = 0; i < bitsLength; i++) {
+ int oldBits = itsBits[i];
+ itsBits[i] = (in.itsBits[i] & notKill.itsBits[i]) | gen.itsBits[i];
+ changed |= (oldBits != itsBits[i]);
+ }
+ return changed;
+ }
+
+ private void badIndex(int n)
+ {
+ throw new RuntimeException("DataFlowBitSet bad index " + n);
+ }
+}
diff --git a/src/org/mozilla/javascript/optimizer/OptFunctionNode.java b/src/org/mozilla/javascript/optimizer/OptFunctionNode.java
new file mode 100644
index 0000000..e043165
--- /dev/null
+++ b/src/org/mozilla/javascript/optimizer/OptFunctionNode.java
@@ -0,0 +1,149 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Bob Jervis
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+
+final class OptFunctionNode
+{
+ OptFunctionNode(FunctionNode fnode)
+ {
+ this.fnode = fnode;
+ fnode.setCompilerData(this);
+ }
+
+ static OptFunctionNode get(ScriptOrFnNode scriptOrFn, int i)
+ {
+ FunctionNode fnode = scriptOrFn.getFunctionNode(i);
+ return (OptFunctionNode)fnode.getCompilerData();
+ }
+
+ static OptFunctionNode get(ScriptOrFnNode scriptOrFn)
+ {
+ return (OptFunctionNode)scriptOrFn.getCompilerData();
+ }
+
+ boolean isTargetOfDirectCall()
+ {
+ return directTargetIndex >= 0;
+ }
+
+ int getDirectTargetIndex()
+ {
+ return directTargetIndex;
+ }
+
+ void setDirectTargetIndex(int directTargetIndex)
+ {
+ // One time action
+ if (directTargetIndex < 0 || this.directTargetIndex >= 0)
+ Kit.codeBug();
+ this.directTargetIndex = directTargetIndex;
+ }
+
+ void setParameterNumberContext(boolean b)
+ {
+ itsParameterNumberContext = b;
+ }
+
+ boolean getParameterNumberContext()
+ {
+ return itsParameterNumberContext;
+ }
+
+ int getVarCount()
+ {
+ return fnode.getParamAndVarCount();
+ }
+
+ boolean isParameter(int varIndex)
+ {
+ return varIndex < fnode.getParamCount();
+ }
+
+ boolean isNumberVar(int varIndex)
+ {
+ varIndex -= fnode.getParamCount();
+ if (varIndex >= 0 && numberVarFlags != null) {
+ return numberVarFlags[varIndex];
+ }
+ return false;
+ }
+
+ void setIsNumberVar(int varIndex)
+ {
+ varIndex -= fnode.getParamCount();
+ // Can only be used with non-parameters
+ if (varIndex < 0) Kit.codeBug();
+ if (numberVarFlags == null) {
+ int size = fnode.getParamAndVarCount() - fnode.getParamCount();
+ numberVarFlags = new boolean[size];
+ }
+ numberVarFlags[varIndex] = true;
+ }
+
+ int getVarIndex(Node n)
+ {
+ int index = n.getIntProp(Node.VARIABLE_PROP, -1);
+ if (index == -1) {
+ Node node;
+ int type = n.getType();
+ if (type == Token.GETVAR) {
+ node = n;
+ } else if (type == Token.SETVAR ||
+ type == Token.SETCONSTVAR) {
+ node = n.getFirstChild();
+ } else {
+ throw Kit.codeBug();
+ }
+ index = fnode.getIndexForNameNode(node);
+ if (index < 0) throw Kit.codeBug();
+ n.putIntProp(Node.VARIABLE_PROP, index);
+ }
+ return index;
+ }
+
+ FunctionNode fnode;
+ private boolean[] numberVarFlags;
+ private int directTargetIndex = -1;
+ private boolean itsParameterNumberContext;
+ boolean itsContainsCalls0;
+ boolean itsContainsCalls1;
+}
diff --git a/src/org/mozilla/javascript/optimizer/OptRuntime.java b/src/org/mozilla/javascript/optimizer/OptRuntime.java
new file mode 100644
index 0000000..3900ea8
--- /dev/null
+++ b/src/org/mozilla/javascript/optimizer/OptRuntime.java
@@ -0,0 +1,311 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Roger Lawrence
+ * Hannes Wallnoefer
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+
+public final class OptRuntime extends ScriptRuntime
+{
+
+ public static final Double zeroObj = new Double(0.0);
+ public static final Double oneObj = new Double(1.0);
+ public static final Double minusOneObj = new Double(-1.0);
+
+ /**
+ * Implement ....() call shrinking optimizer code.
+ */
+ public static Object call0(Callable fun, Scriptable thisObj,
+ Context cx, Scriptable scope)
+ {
+ return fun.call(cx, scope, thisObj, ScriptRuntime.emptyArgs);
+ }
+
+ /**
+ * Implement ....(arg) call shrinking optimizer code.
+ */
+ public static Object call1(Callable fun, Scriptable thisObj, Object arg0,
+ Context cx, Scriptable scope)
+ {
+ return fun.call(cx, scope, thisObj, new Object[] { arg0 } );
+ }
+
+ /**
+ * Implement ....(arg0, arg1) call shrinking optimizer code.
+ */
+ public static Object call2(Callable fun, Scriptable thisObj,
+ Object arg0, Object arg1,
+ Context cx, Scriptable scope)
+ {
+ return fun.call(cx, scope, thisObj, new Object[] { arg0, arg1 });
+ }
+
+ /**
+ * Implement ....(arg0, arg1, ...) call shrinking optimizer code.
+ */
+ public static Object callN(Callable fun, Scriptable thisObj,
+ Object[] args,
+ Context cx, Scriptable scope)
+ {
+ return fun.call(cx, scope, thisObj, args);
+ }
+
+ /**
+ * Implement name(args) call shrinking optimizer code.
+ */
+ public static Object callName(Object[] args, String name,
+ Context cx, Scriptable scope)
+ {
+ Callable f = getNameFunctionAndThis(name, cx, scope);
+ Scriptable thisObj = lastStoredScriptable(cx);
+ return f.call(cx, scope, thisObj, args);
+ }
+
+ /**
+ * Implement name() call shrinking optimizer code.
+ */
+ public static Object callName0(String name,
+ Context cx, Scriptable scope)
+ {
+ Callable f = getNameFunctionAndThis(name, cx, scope);
+ Scriptable thisObj = lastStoredScriptable(cx);
+ return f.call(cx, scope, thisObj, ScriptRuntime.emptyArgs);
+ }
+
+ /**
+ * Implement x.property() call shrinking optimizer code.
+ */
+ public static Object callProp0(Object value, String property,
+ Context cx, Scriptable scope)
+ {
+ Callable f = getPropFunctionAndThis(value, property, cx, scope);
+ Scriptable thisObj = lastStoredScriptable(cx);
+ return f.call(cx, scope, thisObj, ScriptRuntime.emptyArgs);
+ }
+
+ public static Object add(Object val1, double val2)
+ {
+ if (val1 instanceof Scriptable)
+ val1 = ((Scriptable) val1).getDefaultValue(null);
+ if (!(val1 instanceof String))
+ return wrapDouble(toNumber(val1) + val2);
+ return ((String)val1).concat(toString(val2));
+ }
+
+ public static Object add(double val1, Object val2)
+ {
+ if (val2 instanceof Scriptable)
+ val2 = ((Scriptable) val2).getDefaultValue(null);
+ if (!(val2 instanceof String))
+ return wrapDouble(toNumber(val2) + val1);
+ return toString(val1).concat((String)val2);
+ }
+
+ public static Object elemIncrDecr(Object obj, double index,
+ Context cx, int incrDecrMask)
+ {
+ return ScriptRuntime.elemIncrDecr(obj, new Double(index), cx,
+ incrDecrMask);
+ }
+
+ public static Object[] padStart(Object[] currentArgs, int count) {
+ Object[] result = new Object[currentArgs.length + count];
+ System.arraycopy(currentArgs, 0, result, count, currentArgs.length);
+ return result;
+ }
+
+ public static void initFunction(NativeFunction fn, int functionType,
+ Scriptable scope, Context cx)
+ {
+ ScriptRuntime.initFunction(cx, scope, fn, functionType, false);
+ }
+
+ public static Object callSpecial(Context cx, Callable fun,
+ Scriptable thisObj, Object[] args,
+ Scriptable scope,
+ Scriptable callerThis, int callType,
+ String fileName, int lineNumber)
+ {
+ return ScriptRuntime.callSpecial(cx, fun, thisObj, args, scope,
+ callerThis, callType,
+ fileName, lineNumber);
+ }
+
+ public static Object newObjectSpecial(Context cx, Object fun,
+ Object[] args, Scriptable scope,
+ Scriptable callerThis, int callType)
+ {
+ return ScriptRuntime.newSpecial(cx, fun, args, scope, callType);
+ }
+
+ public static Double wrapDouble(double num)
+ {
+ if (num == 0.0) {
+ if (1 / num > 0) {
+ // +0.0
+ return zeroObj;
+ }
+ } else if (num == 1.0) {
+ return oneObj;
+ } else if (num == -1.0) {
+ return minusOneObj;
+ } else if (num != num) {
+ return NaNobj;
+ }
+ return new Double(num);
+ }
+
+ static String encodeIntArray(int[] array)
+ {
+ // XXX: this extremely inefficient for small integers
+ if (array == null) { return null; }
+ int n = array.length;
+ char[] buffer = new char[1 + n * 2];
+ buffer[0] = 1;
+ for (int i = 0; i != n; ++i) {
+ int value = array[i];
+ int shift = 1 + i * 2;
+ buffer[shift] = (char)(value >>> 16);
+ buffer[shift + 1] = (char)value;
+ }
+ return new String(buffer);
+ }
+
+ private static int[] decodeIntArray(String str, int arraySize)
+ {
+ // XXX: this extremely inefficient for small integers
+ if (arraySize == 0) {
+ if (str != null) throw new IllegalArgumentException();
+ return null;
+ }
+ if (str.length() != 1 + arraySize * 2 && str.charAt(0) != 1) {
+ throw new IllegalArgumentException();
+ }
+ int[] array = new int[arraySize];
+ for (int i = 0; i != arraySize; ++i) {
+ int shift = 1 + i * 2;
+ array[i] = (str.charAt(shift) << 16) | str.charAt(shift + 1);
+ }
+ return array;
+ }
+
+ public static Scriptable newArrayLiteral(Object[] objects,
+ String encodedInts,
+ int skipCount,
+ Context cx,
+ Scriptable scope)
+ {
+ int[] skipIndexces = decodeIntArray(encodedInts, skipCount);
+ return newArrayLiteral(objects, skipIndexces, cx, scope);
+ }
+
+ public static void main(final Script script, final String[] args)
+ {
+ ContextFactory.getGlobal().call(new ContextAction() {
+ public Object run(Context cx)
+ {
+ ScriptableObject global = getGlobal(cx);
+
+ // get the command line arguments and define "arguments"
+ // array in the top-level object
+ Object[] argsCopy = new Object[args.length];
+ System.arraycopy(args, 0, argsCopy, 0, args.length);
+ Scriptable argsObj = cx.newArray(global, argsCopy);
+ global.defineProperty("arguments", argsObj,
+ ScriptableObject.DONTENUM);
+ script.exec(cx, global);
+ return null;
+ }
+ });
+ }
+
+ public static void throwStopIteration(Object obj) {
+ throw new JavaScriptException(
+ NativeIterator.getStopIterationObject((Scriptable)obj), "", 0);
+ }
+
+ public static Scriptable createNativeGenerator(NativeFunction funObj,
+ Scriptable scope,
+ Scriptable thisObj,
+ int maxLocals,
+ int maxStack)
+ {
+ return new NativeGenerator(scope, funObj,
+ new GeneratorState(thisObj, maxLocals, maxStack));
+ }
+
+ public static Object[] getGeneratorStackState(Object obj) {
+ GeneratorState rgs = (GeneratorState) obj;
+ if (rgs.stackState == null)
+ rgs.stackState = new Object[rgs.maxStack];
+ return rgs.stackState;
+ }
+
+ public static Object[] getGeneratorLocalsState(Object obj) {
+ GeneratorState rgs = (GeneratorState) obj;
+ if (rgs.localsState == null)
+ rgs.localsState = new Object[rgs.maxLocals];
+ return rgs.localsState;
+ }
+
+ public static class GeneratorState {
+ static final String CLASS_NAME =
+ "org/mozilla/javascript/optimizer/OptRuntime$GeneratorState";
+
+ public int resumptionPoint;
+ static final String resumptionPoint_NAME = "resumptionPoint";
+ static final String resumptionPoint_TYPE = "I";
+
+ public Scriptable thisObj;
+ static final String thisObj_NAME = "thisObj";
+ static final String thisObj_TYPE =
+ "Lorg/mozilla/javascript/Scriptable;";
+
+ Object[] stackState;
+ Object[] localsState;
+ int maxLocals;
+ int maxStack;
+
+ GeneratorState(Scriptable thisObj, int maxLocals, int maxStack) {
+ this.thisObj = thisObj;
+ this.maxLocals = maxLocals;
+ this.maxStack = maxStack;
+ }
+ }
+}
diff --git a/src/org/mozilla/javascript/optimizer/OptTransformer.java b/src/org/mozilla/javascript/optimizer/OptTransformer.java
new file mode 100644
index 0000000..57f6113
--- /dev/null
+++ b/src/org/mozilla/javascript/optimizer/OptTransformer.java
@@ -0,0 +1,135 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+import java.util.Map;
+
+/**
+ * This class performs node transforms to prepare for optimization.
+ *
+ * @see NodeTransformer
+ * @author Norris Boyd
+ */
+
+class OptTransformer extends NodeTransformer {
+
+ OptTransformer(Map<String,OptFunctionNode> possibleDirectCalls, ObjArray directCallTargets)
+ {
+ this.possibleDirectCalls = possibleDirectCalls;
+ this.directCallTargets = directCallTargets;
+ }
+
+ @Override
+ protected void visitNew(Node node, ScriptOrFnNode tree) {
+ detectDirectCall(node, tree);
+ super.visitNew(node, tree);
+ }
+
+ @Override
+ protected void visitCall(Node node, ScriptOrFnNode tree) {
+ detectDirectCall(node, tree);
+ super.visitCall(node, tree);
+ }
+
+ private void detectDirectCall(Node node, ScriptOrFnNode tree)
+ {
+ if (tree.getType() == Token.FUNCTION) {
+ Node left = node.getFirstChild();
+
+ // count the arguments
+ int argCount = 0;
+ Node arg = left.getNext();
+ while (arg != null) {
+ arg = arg.getNext();
+ argCount++;
+ }
+
+ if (argCount == 0) {
+ OptFunctionNode.get(tree).itsContainsCalls0 = true;
+ }
+
+ /*
+ * Optimize a call site by converting call("a", b, c) into :
+ *
+ * FunctionObjectFor"a" <-- instance variable init'd by constructor
+ *
+ * // this is a DIRECTCALL node
+ * fn = GetProp(tmp = GetBase("a"), "a");
+ * if (fn == FunctionObjectFor"a")
+ * fn.call(tmp, b, c)
+ * else
+ * ScriptRuntime.Call(fn, tmp, b, c)
+ */
+ if (possibleDirectCalls != null) {
+ String targetName = null;
+ if (left.getType() == Token.NAME) {
+ targetName = left.getString();
+ } else if (left.getType() == Token.GETPROP) {
+ targetName = left.getFirstChild().getNext().getString();
+ } else if (left.getType() == Token.GETPROPNOWARN) {
+ throw Kit.codeBug();
+ }
+ if (targetName != null) {
+ OptFunctionNode ofn;
+ ofn = possibleDirectCalls.get(targetName);
+ if (ofn != null
+ && argCount == ofn.fnode.getParamCount()
+ && !ofn.fnode.requiresActivation())
+ {
+ // Refuse to directCall any function with more
+ // than 32 parameters - prevent code explosion
+ // for wacky test cases
+ if (argCount <= 32) {
+ node.putProp(Node.DIRECTCALL_PROP, ofn);
+ if (!ofn.isTargetOfDirectCall()) {
+ int index = directCallTargets.size();
+ directCallTargets.add(ofn);
+ ofn.setDirectTargetIndex(index);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private Map<String,OptFunctionNode> possibleDirectCalls;
+ private ObjArray directCallTargets;
+}
diff --git a/src/org/mozilla/javascript/optimizer/Optimizer.java b/src/org/mozilla/javascript/optimizer/Optimizer.java
new file mode 100644
index 0000000..bf225b0
--- /dev/null
+++ b/src/org/mozilla/javascript/optimizer/Optimizer.java
@@ -0,0 +1,509 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Roger Lawrence
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+
+package org.mozilla.javascript.optimizer;
+
+import org.mozilla.javascript.*;
+
+class Optimizer
+{
+
+ static final int NoType = 0;
+ static final int NumberType = 1;
+ static final int AnyType = 3;
+
+ // It is assumed that (NumberType | AnyType) == AnyType
+
+ void optimize(ScriptOrFnNode scriptOrFn)
+ {
+ // run on one function at a time for now
+ int functionCount = scriptOrFn.getFunctionCount();
+ for (int i = 0; i != functionCount; ++i) {
+ OptFunctionNode f = OptFunctionNode.get(scriptOrFn, i);
+ optimizeFunction(f);
+ }
+ }
+
+ private void optimizeFunction(OptFunctionNode theFunction)
+ {
+ if (theFunction.fnode.requiresActivation()) return;
+
+ inDirectCallFunction = theFunction.isTargetOfDirectCall();
+ this.theFunction = theFunction;
+
+ ObjArray statementsArray = new ObjArray();
+ buildStatementList_r(theFunction.fnode, statementsArray);
+ Node[] theStatementNodes = new Node[statementsArray.size()];
+ statementsArray.toArray(theStatementNodes);
+
+ Block.runFlowAnalyzes(theFunction, theStatementNodes);
+
+ if (!theFunction.fnode.requiresActivation()) {
+ /*
+ * Now that we know which local vars are in fact always
+ * Numbers, we re-write the tree to take advantage of
+ * that. Any arithmetic or assignment op involving just
+ * Number typed vars is marked so that the codegen will
+ * generate non-object code.
+ */
+ parameterUsedInNumberContext = false;
+ for (int i = 0; i < theStatementNodes.length; i++) {
+ rewriteForNumberVariables(theStatementNodes[i], NumberType);
+ }
+ theFunction.setParameterNumberContext(parameterUsedInNumberContext);
+ }
+
+ }
+
+
+/*
+ Each directCall parameter is passed as a pair of values - an object
+ and a double. The value passed depends on the type of value available at
+ the call site. If a double is available, the object in java/lang/Void.TYPE
+ is passed as the object value, and if an object value is available, then
+ 0.0 is passed as the double value.
+
+ The receiving routine always tests the object value before proceeding.
+ If the parameter is being accessed in a 'Number Context' then the code
+ sequence is :
+ if ("parameter_objectValue" == java/lang/Void.TYPE)
+ ...fine..., use the parameter_doubleValue
+ else
+ toNumber(parameter_objectValue)
+
+ and if the parameter is being referenced in an Object context, the code is
+ if ("parameter_objectValue" == java/lang/Void.TYPE)
+ new Double(parameter_doubleValue)
+ else
+ ...fine..., use the parameter_objectValue
+
+ If the receiving code never uses the doubleValue, it is converted on
+ entry to a Double instead.
+*/
+
+
+/*
+ We're referencing a node in a Number context (i.e. we'd prefer it
+ was a double value). If the node is a parameter in a directCall
+ function, mark it as being referenced in this context.
+*/
+ private void markDCPNumberContext(Node n)
+ {
+ if (inDirectCallFunction && n.getType() == Token.GETVAR) {
+ int varIndex = theFunction.getVarIndex(n);
+ if (theFunction.isParameter(varIndex)) {
+ parameterUsedInNumberContext = true;
+ }
+ }
+ }
+
+ private boolean convertParameter(Node n)
+ {
+ if (inDirectCallFunction && n.getType() == Token.GETVAR) {
+ int varIndex = theFunction.getVarIndex(n);
+ if (theFunction.isParameter(varIndex)) {
+ n.removeProp(Node.ISNUMBER_PROP);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int rewriteForNumberVariables(Node n, int desired)
+ {
+ switch (n.getType()) {
+ case Token.EXPR_VOID : {
+ Node child = n.getFirstChild();
+ int type = rewriteForNumberVariables(child, NumberType);
+ if (type == NumberType)
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NoType;
+ }
+ case Token.NUMBER :
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+
+ case Token.GETVAR :
+ {
+ int varIndex = theFunction.getVarIndex(n);
+ if (inDirectCallFunction
+ && theFunction.isParameter(varIndex)
+ && desired == NumberType)
+ {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+ }
+ else if (theFunction.isNumberVar(varIndex)) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+ }
+ return NoType;
+ }
+
+ case Token.INC :
+ case Token.DEC : {
+ Node child = n.getFirstChild();
+ // "child" will be GETVAR or GETPROP or GETELEM
+ if (child.getType() == Token.GETVAR) {
+ ;
+ if (rewriteForNumberVariables(child, NumberType) == NumberType &&
+ !convertParameter(child))
+ {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ markDCPNumberContext(child);
+ return NumberType;
+ }
+ return NoType;
+ }
+ else if (child.getType() == Token.GETELEM) {
+ return rewriteForNumberVariables(child, NumberType);
+ }
+ return NoType;
+ }
+ case Token.SETVAR : {
+ Node lChild = n.getFirstChild();
+ Node rChild = lChild.getNext();
+ int rType = rewriteForNumberVariables(rChild, NumberType);
+ int varIndex = theFunction.getVarIndex(n);
+ if (inDirectCallFunction
+ && theFunction.isParameter(varIndex))
+ {
+ if (rType == NumberType) {
+ if (!convertParameter(rChild)) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+ }
+ markDCPNumberContext(rChild);
+ return NoType;
+ }
+ else
+ return rType;
+ }
+ else if (theFunction.isNumberVar(varIndex)) {
+ if (rType != NumberType) {
+ n.removeChild(rChild);
+ n.addChildToBack(
+ new Node(Token.TO_DOUBLE, rChild));
+ }
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ markDCPNumberContext(rChild);
+ return NumberType;
+ }
+ else {
+ if (rType == NumberType) {
+ if (!convertParameter(rChild)) {
+ n.removeChild(rChild);
+ n.addChildToBack(
+ new Node(Token.TO_OBJECT, rChild));
+ }
+ }
+ return NoType;
+ }
+ }
+ case Token.LE :
+ case Token.LT :
+ case Token.GE :
+ case Token.GT : {
+ Node lChild = n.getFirstChild();
+ Node rChild = lChild.getNext();
+ int lType = rewriteForNumberVariables(lChild, NumberType);
+ int rType = rewriteForNumberVariables(rChild, NumberType);
+ markDCPNumberContext(lChild);
+ markDCPNumberContext(rChild);
+
+ if (convertParameter(lChild)) {
+ if (convertParameter(rChild)) {
+ return NoType;
+ } else if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.RIGHT);
+ }
+ }
+ else if (convertParameter(rChild)) {
+ if (lType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT);
+ }
+ }
+ else {
+ if (lType == NumberType) {
+ if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ }
+ else {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT);
+ }
+ }
+ else {
+ if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.RIGHT);
+ }
+ }
+ }
+ // we actually build a boolean value
+ return NoType;
+ }
+
+ case Token.ADD : {
+ Node lChild = n.getFirstChild();
+ Node rChild = lChild.getNext();
+ int lType = rewriteForNumberVariables(lChild, NumberType);
+ int rType = rewriteForNumberVariables(rChild, NumberType);
+
+
+ if (convertParameter(lChild)) {
+ if (convertParameter(rChild)) {
+ return NoType;
+ }
+ else {
+ if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.RIGHT);
+ }
+ }
+ }
+ else {
+ if (convertParameter(rChild)) {
+ if (lType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT);
+ }
+ }
+ else {
+ if (lType == NumberType) {
+ if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+ }
+ else {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT);
+ }
+ }
+ else {
+ if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP,
+ Node.RIGHT);
+ }
+ }
+ }
+ }
+ return NoType;
+ }
+
+ case Token.BITXOR :
+ case Token.BITOR :
+ case Token.BITAND :
+ case Token.RSH :
+ case Token.LSH :
+ case Token.SUB :
+ case Token.MUL :
+ case Token.DIV :
+ case Token.MOD : {
+ Node lChild = n.getFirstChild();
+ Node rChild = lChild.getNext();
+ int lType = rewriteForNumberVariables(lChild, NumberType);
+ int rType = rewriteForNumberVariables(rChild, NumberType);
+ markDCPNumberContext(lChild);
+ markDCPNumberContext(rChild);
+ if (lType == NumberType) {
+ if (rType == NumberType) {
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+ }
+ else {
+ if (!convertParameter(rChild)) {
+ n.removeChild(rChild);
+ n.addChildToBack(
+ new Node(Token.TO_DOUBLE, rChild));
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ }
+ return NumberType;
+ }
+ }
+ else {
+ if (rType == NumberType) {
+ if (!convertParameter(lChild)) {
+ n.removeChild(lChild);
+ n.addChildToFront(
+ new Node(Token.TO_DOUBLE, lChild));
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ }
+ return NumberType;
+ }
+ else {
+ if (!convertParameter(lChild)) {
+ n.removeChild(lChild);
+ n.addChildToFront(
+ new Node(Token.TO_DOUBLE, lChild));
+ }
+ if (!convertParameter(rChild)) {
+ n.removeChild(rChild);
+ n.addChildToBack(
+ new Node(Token.TO_DOUBLE, rChild));
+ }
+ n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH);
+ return NumberType;
+ }
+ }
+ }
+ case Token.SETELEM :
+ case Token.SETELEM_OP : {
+ Node arrayBase = n.getFirstChild();
+ Node arrayIndex = arrayBase.getNext();
+ Node rValue = arrayIndex.getNext();
+ int baseType = rewriteForNumberVariables(arrayBase, NumberType);
+ if (baseType == NumberType) {// can never happen ???
+ if (!convertParameter(arrayBase)) {
+ n.removeChild(arrayBase);
+ n.addChildToFront(
+ new Node(Token.TO_OBJECT, arrayBase));
+ }
+ }
+ int indexType = rewriteForNumberVariables(arrayIndex, NumberType);
+ if (indexType == NumberType) {
+ if (!convertParameter(arrayIndex)) {
+ // setting the ISNUMBER_PROP signals the codegen
+ // to use the OptRuntime.setObjectIndex that takes
+ // a double index
+ n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT);
+ }
+ }
+ int rValueType = rewriteForNumberVariables(rValue, NumberType);
+ if (rValueType == NumberType) {
+ if (!convertParameter(rValue)) {
+ n.removeChild(rValue);
+ n.addChildToBack(
+ new Node(Token.TO_OBJECT, rValue));
+ }
+ }
+ return NoType;
+ }
+ case Token.GETELEM : {
+ Node arrayBase = n.getFirstChild();
+ Node arrayIndex = arrayBase.getNext();
+ int baseType = rewriteForNumberVariables(arrayBase, NumberType);
+ if (baseType == NumberType) {// can never happen ???
+ if (!convertParameter(arrayBase)) {
+ n.removeChild(arrayBase);
+ n.addChildToFront(
+ new Node(Token.TO_OBJECT, arrayBase));
+ }
+ }
+ int indexType = rewriteForNumberVariables(arrayIndex, NumberType);
+ if (indexType == NumberType) {
+ if (!convertParameter(arrayIndex)) {
+ // setting the ISNUMBER_PROP signals the codegen
+ // to use the OptRuntime.getObjectIndex that takes
+ // a double index
+ n.putIntProp(Node.ISNUMBER_PROP, Node.RIGHT);
+ }
+ }
+ return NoType;
+ }
+ case Token.CALL :
+ {
+ Node child = n.getFirstChild(); // the function node
+ // must be an object
+ rewriteAsObjectChildren(child, child.getFirstChild());
+ child = child.getNext(); // the first arg
+
+ OptFunctionNode target
+ = (OptFunctionNode)n.getProp(Node.DIRECTCALL_PROP);
+ if (target != null) {
+/*
+ we leave each child as a Number if it can be. The codegen will
+ handle moving the pairs of parameters.
+*/
+ while (child != null) {
+ int type = rewriteForNumberVariables(child, NumberType);
+ if (type == NumberType) {
+ markDCPNumberContext(child);
+ }
+ child = child.getNext();
+ }
+ } else {
+ rewriteAsObjectChildren(n, child);
+ }
+ return NoType;
+ }
+ default : {
+ rewriteAsObjectChildren(n, n.getFirstChild());
+ return NoType;
+ }
+ }
+ }
+
+ private void rewriteAsObjectChildren(Node n, Node child)
+ {
+ // Force optimized children to be objects
+ while (child != null) {
+ Node nextChild = child.getNext();
+ int type = rewriteForNumberVariables(child, NoType);
+ if (type == NumberType) {
+ if (!convertParameter(child)) {
+ n.removeChild(child);
+ Node nuChild = new Node(Token.TO_OBJECT, child);
+ if (nextChild == null)
+ n.addChildToBack(nuChild);
+ else
+ n.addChildBefore(nuChild, nextChild);
+ }
+ }
+ child = nextChild;
+ }
+ }
+
+ private static void buildStatementList_r(Node node, ObjArray statements)
+ {
+ int type = node.getType();
+ if (type == Token.BLOCK
+ || type == Token.LOCAL_BLOCK
+ || type == Token.LOOP
+ || type == Token.FUNCTION)
+ {
+ Node child = node.getFirstChild();
+ while (child != null) {
+ buildStatementList_r(child, statements);
+ child = child.getNext();
+ }
+ } else {
+ statements.add(node);
+ }
+ }
+
+ private boolean inDirectCallFunction;
+ OptFunctionNode theFunction;
+ private boolean parameterUsedInNumberContext;
+}
diff --git a/src/org/mozilla/javascript/regexp/NativeRegExp.java b/src/org/mozilla/javascript/regexp/NativeRegExp.java
new file mode 100644
index 0000000..0bae489
--- /dev/null
+++ b/src/org/mozilla/javascript/regexp/NativeRegExp.java
@@ -0,0 +1,2792 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Brendan Eich
+ * Matthias Radestock
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.regexp;
+
+import java.io.Serializable;
+
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Function;
+import org.mozilla.javascript.IdFunctionObject;
+import org.mozilla.javascript.IdScriptableObject;
+import org.mozilla.javascript.Kit;
+import org.mozilla.javascript.ScriptRuntime;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import org.mozilla.javascript.Undefined;
+
+/**
+ * This class implements the RegExp native object.
+ *
+ * Revision History:
+ * Implementation in C by Brendan Eich
+ * Initial port to Java by Norris Boyd from jsregexp.c version 1.36
+ * Merged up to version 1.38, which included Unicode support.
+ * Merged bug fixes in version 1.39.
+ * Merged JSFUN13_BRANCH changes up to 1.32.2.13
+ *
+ * @author Brendan Eich
+ * @author Norris Boyd
+ */
+
+
+
+public class NativeRegExp extends IdScriptableObject implements Function
+{
+ static final long serialVersionUID = 4965263491464903264L;
+
+ private static final Object REGEXP_TAG = new Object();
+
+ public static final int JSREG_GLOB = 0x1; // 'g' flag: global
+ public static final int JSREG_FOLD = 0x2; // 'i' flag: fold
+ public static final int JSREG_MULTILINE = 0x4; // 'm' flag: multiline
+
+ //type of match to perform
+ public static final int TEST = 0;
+ public static final int MATCH = 1;
+ public static final int PREFIX = 2;
+
+ private static final boolean debug = false;
+
+ private static final byte REOP_EMPTY = 0; /* match rest of input against rest of r.e. */
+ private static final byte REOP_ALT = 1; /* alternative subexpressions in kid and next */
+ private static final byte REOP_BOL = 2; /* beginning of input (or line if multiline) */
+ private static final byte REOP_EOL = 3; /* end of input (or line if multiline) */
+ private static final byte REOP_WBDRY = 4; /* match "" at word boundary */
+ private static final byte REOP_WNONBDRY = 5; /* match "" at word non-boundary */
+ private static final byte REOP_QUANT = 6; /* quantified atom: atom{1,2} */
+ private static final byte REOP_STAR = 7; /* zero or more occurrences of kid */
+ private static final byte REOP_PLUS = 8; /* one or more occurrences of kid */
+ private static final byte REOP_OPT = 9; /* optional subexpression in kid */
+ private static final byte REOP_LPAREN = 10; /* left paren bytecode: kid is u.num'th sub-regexp */
+ private static final byte REOP_RPAREN = 11; /* right paren bytecode */
+ private static final byte REOP_DOT = 12; /* stands for any character */
+// private static final byte REOP_CCLASS = 13; /* character class: [a-f] */
+ private static final byte REOP_DIGIT = 14; /* match a digit char: [0-9] */
+ private static final byte REOP_NONDIGIT = 15; /* match a non-digit char: [^0-9] */
+ private static final byte REOP_ALNUM = 16; /* match an alphanumeric char: [0-9a-z_A-Z] */
+ private static final byte REOP_NONALNUM = 17; /* match a non-alphanumeric char: [^0-9a-z_A-Z] */
+ private static final byte REOP_SPACE = 18; /* match a whitespace char */
+ private static final byte REOP_NONSPACE = 19; /* match a non-whitespace char */
+ private static final byte REOP_BACKREF = 20; /* back-reference (e.g., \1) to a parenthetical */
+ private static final byte REOP_FLAT = 21; /* match a flat string */
+ private static final byte REOP_FLAT1 = 22; /* match a single char */
+ private static final byte REOP_JUMP = 23; /* for deoptimized closure loops */
+// private static final byte REOP_DOTSTAR = 24; /* optimize .* to use a single opcode */
+// private static final byte REOP_ANCHOR = 25; /* like .* but skips left context to unanchored r.e. */
+// private static final byte REOP_EOLONLY = 26; /* $ not preceded by any pattern */
+// private static final byte REOP_UCFLAT = 27; /* flat Unicode string; len immediate counts chars */
+ private static final byte REOP_UCFLAT1 = 28; /* single Unicode char */
+// private static final byte REOP_UCCLASS = 29; /* Unicode character class, vector of chars to match */
+// private static final byte REOP_NUCCLASS = 30; /* negated Unicode character class */
+// private static final byte REOP_BACKREFi = 31; /* case-independent REOP_BACKREF */
+ private static final byte REOP_FLATi = 32; /* case-independent REOP_FLAT */
+ private static final byte REOP_FLAT1i = 33; /* case-independent REOP_FLAT1 */
+// private static final byte REOP_UCFLATi = 34; /* case-independent REOP_UCFLAT */
+ private static final byte REOP_UCFLAT1i = 35; /* case-independent REOP_UCFLAT1 */
+// private static final byte REOP_ANCHOR1 = 36; /* first-char discriminating REOP_ANCHOR */
+// private static final byte REOP_NCCLASS = 37; /* negated 8-bit character class */
+// private static final byte REOP_DOTSTARMIN = 38; /* ungreedy version of REOP_DOTSTAR */
+// private static final byte REOP_LPARENNON = 39; /* non-capturing version of REOP_LPAREN */
+// private static final byte REOP_RPARENNON = 40; /* non-capturing version of REOP_RPAREN */
+ private static final byte REOP_ASSERT = 41; /* zero width positive lookahead assertion */
+ private static final byte REOP_ASSERT_NOT = 42; /* zero width negative lookahead assertion */
+ private static final byte REOP_ASSERTTEST = 43; /* sentinel at end of assertion child */
+ private static final byte REOP_ASSERTNOTTEST = 44; /* sentinel at end of !assertion child */
+ private static final byte REOP_MINIMALSTAR = 45; /* non-greedy version of * */
+ private static final byte REOP_MINIMALPLUS = 46; /* non-greedy version of + */
+ private static final byte REOP_MINIMALOPT = 47; /* non-greedy version of ? */
+ private static final byte REOP_MINIMALQUANT = 48; /* non-greedy version of {} */
+ private static final byte REOP_ENDCHILD = 49; /* sentinel at end of quantifier child */
+ private static final byte REOP_CLASS = 50; /* character class with index */
+ private static final byte REOP_REPEAT = 51; /* directs execution of greedy quantifier */
+ private static final byte REOP_MINIMALREPEAT = 52; /* directs execution of non-greedy quantifier */
+ private static final byte REOP_END = 53;
+
+
+
+ public static void init(Context cx, Scriptable scope, boolean sealed)
+ {
+
+ NativeRegExp proto = new NativeRegExp();
+ proto.re = (RECompiled)compileRE(cx, "", null, false);
+ proto.activatePrototypeMap(MAX_PROTOTYPE_ID);
+ proto.setParentScope(scope);
+ proto.setPrototype(getObjectPrototype(scope));
+
+ NativeRegExpCtor ctor = new NativeRegExpCtor();
+ // Bug #324006: ECMA-262 15.10.6.1 says "The initial value of
+ // RegExp.prototype.constructor is the builtin RegExp constructor."
+ proto.put("constructor", proto, ctor);
+
+ ScriptRuntime.setFunctionProtoAndParent(ctor, scope);
+
+ ctor.setImmunePrototypeProperty(proto);
+
+ if (sealed) {
+ proto.sealObject();
+ ctor.sealObject();
+ }
+
+ defineProperty(scope, "RegExp", ctor, ScriptableObject.DONTENUM);
+ }
+
+ NativeRegExp(Scriptable scope, Object regexpCompiled)
+ {
+ this.re = (RECompiled)regexpCompiled;
+ this.lastIndex = 0;
+ ScriptRuntime.setObjectProtoAndParent(this, scope);
+ }
+
+ @Override
+ public String getClassName()
+ {
+ return "RegExp";
+ }
+
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ return execSub(cx, scope, args, MATCH);
+ }
+
+ public Scriptable construct(Context cx, Scriptable scope, Object[] args)
+ {
+ return (Scriptable)execSub(cx, scope, args, MATCH);
+ }
+
+ Scriptable compile(Context cx, Scriptable scope, Object[] args)
+ {
+ if (args.length > 0 && args[0] instanceof NativeRegExp) {
+ if (args.length > 1 && args[1] != Undefined.instance) {
+ // report error
+ throw ScriptRuntime.typeError0("msg.bad.regexp.compile");
+ }
+ NativeRegExp thatObj = (NativeRegExp) args[0];
+ this.re = thatObj.re;
+ this.lastIndex = thatObj.lastIndex;
+ return this;
+ }
+ String s = args.length == 0 ? "" : ScriptRuntime.toString(args[0]);
+ String global = args.length > 1 && args[1] != Undefined.instance
+ ? ScriptRuntime.toString(args[1])
+ : null;
+ this.re = (RECompiled)compileRE(cx, s, global, false);
+ this.lastIndex = 0;
+ return this;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append('/');
+ if (re.source.length != 0) {
+ buf.append(re.source);
+ } else {
+ // See bugzilla 226045
+ buf.append("(?:)");
+ }
+ buf.append('/');
+ if ((re.flags & JSREG_GLOB) != 0)
+ buf.append('g');
+ if ((re.flags & JSREG_FOLD) != 0)
+ buf.append('i');
+ if ((re.flags & JSREG_MULTILINE) != 0)
+ buf.append('m');
+ return buf.toString();
+ }
+
+ NativeRegExp() { }
+
+ private static RegExpImpl getImpl(Context cx)
+ {
+ return (RegExpImpl) ScriptRuntime.getRegExpProxy(cx);
+ }
+
+ private Object execSub(Context cx, Scriptable scopeObj,
+ Object[] args, int matchType)
+ {
+ RegExpImpl reImpl = getImpl(cx);
+ String str;
+ if (args.length == 0) {
+ str = reImpl.input;
+ if (str == null) {
+ reportError("msg.no.re.input.for", toString());
+ }
+ } else {
+ str = ScriptRuntime.toString(args[0]);
+ }
+ double d = ((re.flags & JSREG_GLOB) != 0) ? lastIndex : 0;
+
+ Object rval;
+ if (d < 0 || str.length() < d) {
+ lastIndex = 0;
+ rval = null;
+ }
+ else {
+ int indexp[] = { (int)d };
+ rval = executeRegExp(cx, scopeObj, reImpl, str, indexp, matchType);
+ if ((re.flags & JSREG_GLOB) != 0) {
+ lastIndex = (rval == null || rval == Undefined.instance)
+ ? 0 : indexp[0];
+ }
+ }
+ return rval;
+ }
+
+ static Object compileRE(Context cx, String str, String global, boolean flat)
+ {
+ RECompiled regexp = new RECompiled();
+ regexp.source = str.toCharArray();
+ int length = str.length();
+
+ int flags = 0;
+ if (global != null) {
+ for (int i = 0; i < global.length(); i++) {
+ char c = global.charAt(i);
+ if (c == 'g') {
+ flags |= JSREG_GLOB;
+ } else if (c == 'i') {
+ flags |= JSREG_FOLD;
+ } else if (c == 'm') {
+ flags |= JSREG_MULTILINE;
+ } else {
+ reportError("msg.invalid.re.flag", String.valueOf(c));
+ }
+ }
+ }
+ regexp.flags = flags;
+
+ CompilerState state = new CompilerState(cx, regexp.source, length, flags);
+ if (flat && length > 0) {
+if (debug) {
+System.out.println("flat = \"" + str + "\"");
+}
+ state.result = new RENode(REOP_FLAT);
+ state.result.chr = state.cpbegin[0];
+ state.result.length = length;
+ state.result.flatIndex = 0;
+ state.progLength += 5;
+ }
+ else
+ if (!parseDisjunction(state))
+ return null;
+
+ regexp.program = new byte[state.progLength + 1];
+ if (state.classCount != 0) {
+ regexp.classList = new RECharSet[state.classCount];
+ regexp.classCount = state.classCount;
+ }
+ int endPC = emitREBytecode(state, regexp, 0, state.result);
+ regexp.program[endPC++] = REOP_END;
+
+if (debug) {
+System.out.println("Prog. length = " + endPC);
+for (int i = 0; i < endPC; i++) {
+ System.out.print(regexp.program[i]);
+ if (i < (endPC - 1)) System.out.print(", ");
+}
+System.out.println();
+}
+ regexp.parenCount = state.parenCount;
+
+ // If re starts with literal, init anchorCh accordingly
+ switch (regexp.program[0]) {
+ case REOP_UCFLAT1:
+ case REOP_UCFLAT1i:
+ regexp.anchorCh = (char)getIndex(regexp.program, 1);
+ break;
+ case REOP_FLAT1:
+ case REOP_FLAT1i:
+ regexp.anchorCh = (char)(regexp.program[1] & 0xFF);
+ break;
+ case REOP_FLAT:
+ case REOP_FLATi:
+ int k = getIndex(regexp.program, 1);
+ regexp.anchorCh = regexp.source[k];
+ break;
+ }
+
+if (debug) {
+if (regexp.anchorCh >= 0) {
+ System.out.println("Anchor ch = '" + (char)regexp.anchorCh + "'");
+}
+}
+ return regexp;
+ }
+
+ static boolean isDigit(char c)
+ {
+ return '0' <= c && c <= '9';
+ }
+
+ private static boolean isWord(char c)
+ {
+ return Character.isLetter(c) || isDigit(c) || c == '_';
+ }
+
+ private static boolean isLineTerm(char c)
+ {
+ return ScriptRuntime.isJSLineTerminator(c);
+ }
+
+ private static boolean isREWhiteSpace(int c)
+ {
+ return (c == '\u0020' || c == '\u0009'
+ || c == '\n' || c == '\r'
+ || c == 0x2028 || c == 0x2029
+ || c == '\u000C' || c == '\u000B'
+ || c == '\u00A0'
+ || Character.getType((char)c) == Character.SPACE_SEPARATOR);
+ }
+
+ /*
+ *
+ * 1. If IgnoreCase is false, return ch.
+ * 2. Let u be ch converted to upper case as if by calling
+ * String.prototype.toUpperCase on the one-character string ch.
+ * 3. If u does not consist of a single character, return ch.
+ * 4. Let cu be u's character.
+ * 5. If ch's code point value is greater than or equal to decimal 128 and cu's
+ * code point value is less than decimal 128, then return ch.
+ * 6. Return cu.
+ */
+ private static char upcase(char ch)
+ {
+ if (ch < 128) {
+ if ('a' <= ch && ch <= 'z') {
+ return (char)(ch + ('A' - 'a'));
+ }
+ return ch;
+ }
+ char cu = Character.toUpperCase(ch);
+ if ((ch >= 128) && (cu < 128)) return ch;
+ return cu;
+ }
+
+ private static char downcase(char ch)
+ {
+ if (ch < 128) {
+ if ('A' <= ch && ch <= 'Z') {
+ return (char)(ch + ('a' - 'A'));
+ }
+ return ch;
+ }
+ char cl = Character.toLowerCase(ch);
+ if ((ch >= 128) && (cl < 128)) return ch;
+ return cl;
+ }
+
+/*
+ * Validates and converts hex ascii value.
+ */
+ private static int toASCIIHexDigit(int c)
+ {
+ if (c < '0')
+ return -1;
+ if (c <= '9') {
+ return c - '0';
+ }
+ c |= 0x20;
+ if ('a' <= c && c <= 'f') {
+ return c - 'a' + 10;
+ }
+ return -1;
+ }
+
+/*
+ * Top-down regular expression grammar, based closely on Perl4.
+ *
+ * regexp: altern A regular expression is one or more
+ * altern '|' regexp alternatives separated by vertical bar.
+ */
+ private static boolean parseDisjunction(CompilerState state)
+ {
+ if (!parseAlternative(state))
+ return false;
+ char[] source = state.cpbegin;
+ int index = state.cp;
+ if (index != source.length && source[index] == '|') {
+ RENode altResult;
+ ++state.cp;
+ altResult = new RENode(REOP_ALT);
+ altResult.kid = state.result;
+ if (!parseDisjunction(state))
+ return false;
+ altResult.kid2 = state.result;
+ state.result = altResult;
+ /* ALT, <next>, ..., JUMP, <end> ... JUMP <end> */
+ state.progLength += 9;
+ }
+ return true;
+ }
+
+/*
+ * altern: item An alternative is one or more items,
+ * item altern concatenated together.
+ */
+ private static boolean parseAlternative(CompilerState state)
+ {
+ RENode headTerm = null;
+ RENode tailTerm = null;
+ char[] source = state.cpbegin;
+ while (true) {
+ if (state.cp == state.cpend || source[state.cp] == '|'
+ || (state.parenNesting != 0 && source[state.cp] == ')'))
+ {
+ if (headTerm == null) {
+ state.result = new RENode(REOP_EMPTY);
+ }
+ else
+ state.result = headTerm;
+ return true;
+ }
+ if (!parseTerm(state))
+ return false;
+ if (headTerm == null)
+ headTerm = state.result;
+ else {
+ if (tailTerm == null) {
+ headTerm.next = state.result;
+ tailTerm = state.result;
+ while (tailTerm.next != null) tailTerm = tailTerm.next;
+ }
+ else {
+ tailTerm.next = state.result;
+ tailTerm = tailTerm.next;
+ while (tailTerm.next != null) tailTerm = tailTerm.next;
+ }
+ }
+ }
+ }
+
+ /* calculate the total size of the bitmap required for a class expression */
+ private static boolean
+ calculateBitmapSize(CompilerState state, RENode target, char[] src,
+ int index, int end)
+ {
+ char rangeStart = 0;
+ char c;
+ int n;
+ int nDigits;
+ int i;
+ int max = 0;
+ boolean inRange = false;
+
+ target.bmsize = 0;
+
+ if (index == end)
+ return true;
+
+ if (src[index] == '^')
+ ++index;
+
+ while (index != end) {
+ int localMax = 0;
+ nDigits = 2;
+ switch (src[index]) {
+ case '\\':
+ ++index;
+ c = src[index++];
+ switch (c) {
+ case 'b':
+ localMax = 0x8;
+ break;
+ case 'f':
+ localMax = 0xC;
+ break;
+ case 'n':
+ localMax = 0xA;
+ break;
+ case 'r':
+ localMax = 0xD;
+ break;
+ case 't':
+ localMax = 0x9;
+ break;
+ case 'v':
+ localMax = 0xB;
+ break;
+ case 'c':
+ if (((index + 1) < end) && Character.isLetter(src[index + 1]))
+ localMax = (char)(src[index++] & 0x1F);
+ else
+ localMax = '\\';
+ break;
+ case 'u':
+ nDigits += 2;
+ // fall thru...
+ case 'x':
+ n = 0;
+ for (i = 0; (i < nDigits) && (index < end); i++) {
+ c = src[index++];
+ n = Kit.xDigitToInt(c, n);
+ if (n < 0) {
+ // Back off to accepting the original
+ // '\' as a literal
+ index -= (i + 1);
+ n = '\\';
+ break;
+ }
+ }
+ localMax = n;
+ break;
+ case 'd':
+ if (inRange) {
+ reportError("msg.bad.range", "");
+ return false;
+ }
+ localMax = '9';
+ break;
+ case 'D':
+ case 's':
+ case 'S':
+ case 'w':
+ case 'W':
+ if (inRange) {
+ reportError("msg.bad.range", "");
+ return false;
+ }
+ target.bmsize = 65535;
+ return true;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ /*
+ * This is a non-ECMA extension - decimal escapes (in this
+ * case, octal!) are supposed to be an error inside class
+ * ranges, but supported here for backwards compatibility.
+ *
+ */
+ n = (c - '0');
+ c = src[index];
+ if ('0' <= c && c <= '7') {
+ index++;
+ n = 8 * n + (c - '0');
+ c = src[index];
+ if ('0' <= c && c <= '7') {
+ index++;
+ i = 8 * n + (c - '0');
+ if (i <= 0377)
+ n = i;
+ else
+ index--;
+ }
+ }
+ localMax = n;
+ break;
+
+ default:
+ localMax = c;
+ break;
+ }
+ break;
+ default:
+ localMax = src[index++];
+ break;
+ }
+ if (inRange) {
+ if (rangeStart > localMax) {
+ reportError("msg.bad.range", "");
+ return false;
+ }
+ inRange = false;
+ }
+ else {
+ if (index < (end - 1)) {
+ if (src[index] == '-') {
+ ++index;
+ inRange = true;
+ rangeStart = (char)localMax;
+ continue;
+ }
+ }
+ }
+ if ((state.flags & JSREG_FOLD) != 0){
+ char cu = upcase((char)localMax);
+ char cd = downcase((char)localMax);
+ localMax = (cu >= cd) ? cu : cd;
+ }
+ if (localMax > max)
+ max = localMax;
+ }
+ target.bmsize = max;
+ return true;
+ }
+
+ /*
+ * item: assertion An item is either an assertion or
+ * quantatom a quantified atom.
+ *
+ * assertion: '^' Assertions match beginning of string
+ * (or line if the class static property
+ * RegExp.multiline is true).
+ * '$' End of string (or line if the class
+ * static property RegExp.multiline is
+ * true).
+ * '\b' Word boundary (between \w and \W).
+ * '\B' Word non-boundary.
+ *
+ * quantatom: atom An unquantified atom.
+ * quantatom '{' n ',' m '}'
+ * Atom must occur between n and m times.
+ * quantatom '{' n ',' '}' Atom must occur at least n times.
+ * quantatom '{' n '}' Atom must occur exactly n times.
+ * quantatom '*' Zero or more times (same as {0,}).
+ * quantatom '+' One or more times (same as {1,}).
+ * quantatom '?' Zero or one time (same as {0,1}).
+ *
+ * any of which can be optionally followed by '?' for ungreedy
+ *
+ * atom: '(' regexp ')' A parenthesized regexp (what matched
+ * can be addressed using a backreference,
+ * see '\' n below).
+ * '.' Matches any char except '\n'.
+ * '[' classlist ']' A character class.
+ * '[' '^' classlist ']' A negated character class.
+ * '\f' Form Feed.
+ * '\n' Newline (Line Feed).
+ * '\r' Carriage Return.
+ * '\t' Horizontal Tab.
+ * '\v' Vertical Tab.
+ * '\d' A digit (same as [0-9]).
+ * '\D' A non-digit.
+ * '\w' A word character, [0-9a-z_A-Z].
+ * '\W' A non-word character.
+ * '\s' A whitespace character, [ \b\f\n\r\t\v].
+ * '\S' A non-whitespace character.
+ * '\' n A backreference to the nth (n decimal
+ * and positive) parenthesized expression.
+ * '\' octal An octal escape sequence (octal must be
+ * two or three digits long, unless it is
+ * 0 for the null character).
+ * '\x' hex A hex escape (hex must be two digits).
+ * '\c' ctrl A control character, ctrl is a letter.
+ * '\' literalatomchar Any character except one of the above
+ * that follow '\' in an atom.
+ * otheratomchar Any character not first among the other
+ * atom right-hand sides.
+ */
+
+ private static void doFlat(CompilerState state, char c)
+ {
+ state.result = new RENode(REOP_FLAT);
+ state.result.chr = c;
+ state.result.length = 1;
+ state.result.flatIndex = -1;
+ state.progLength += 3;
+ }
+
+ private static int
+ getDecimalValue(char c, CompilerState state, int maxValue,
+ String overflowMessageId)
+ {
+ boolean overflow = false;
+ int start = state.cp;
+ char[] src = state.cpbegin;
+ int value = c - '0';
+ for (; state.cp != state.cpend; ++state.cp) {
+ c = src[state.cp];
+ if (!isDigit(c)) {
+ break;
+ }
+ if (!overflow) {
+ int digit = c - '0';
+ if (value < (maxValue - digit) / 10) {
+ value = value * 10 + digit;
+ } else {
+ overflow = true;
+ value = maxValue;
+ }
+ }
+ }
+ if (overflow) {
+ reportError(overflowMessageId,
+ String.valueOf(src, start, state.cp - start));
+ }
+ return value;
+ }
+
+ private static boolean
+ parseTerm(CompilerState state)
+ {
+ char[] src = state.cpbegin;
+ char c = src[state.cp++];
+ int nDigits = 2;
+ int parenBaseCount = state.parenCount;
+ int num, tmp;
+ RENode term;
+ int termStart;
+
+ switch (c) {
+ /* assertions and atoms */
+ case '^':
+ state.result = new RENode(REOP_BOL);
+ state.progLength++;
+ return true;
+ case '$':
+ state.result = new RENode(REOP_EOL);
+ state.progLength++;
+ return true;
+ case '\\':
+ if (state.cp < state.cpend) {
+ c = src[state.cp++];
+ switch (c) {
+ /* assertion escapes */
+ case 'b' :
+ state.result = new RENode(REOP_WBDRY);
+ state.progLength++;
+ return true;
+ case 'B':
+ state.result = new RENode(REOP_WNONBDRY);
+ state.progLength++;
+ return true;
+ /* Decimal escape */
+ case '0':
+/*
+ * Under 'strict' ECMA 3, we interpret \0 as NUL and don't accept octal.
+ * However, (XXX and since Rhino doesn't have a 'strict' mode) we'll just
+ * behave the old way for compatibility reasons.
+ * (see http://bugzilla.mozilla.org/show_bug.cgi?id=141078)
+ *
+ */
+ reportWarning(state.cx, "msg.bad.backref", "");
+ /* octal escape */
+ num = 0;
+ while (state.cp < state.cpend) {
+ c = src[state.cp];
+ if ((c >= '0') && (c <= '7')) {
+ state.cp++;
+ tmp = 8 * num + (c - '0');
+ if (tmp > 0377)
+ break;
+ num = tmp;
+ }
+ else
+ break;
+ }
+ c = (char)(num);
+ doFlat(state, c);
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ termStart = state.cp - 1;
+ num = getDecimalValue(c, state, 0xFFFF,
+ "msg.overlarge.backref");
+ if (num > state.parenCount)
+ reportWarning(state.cx, "msg.bad.backref", "");
+ /*
+ * n > 9 or > count of parentheses,
+ * then treat as octal instead.
+ */
+ if ((num > 9) && (num > state.parenCount)) {
+ state.cp = termStart;
+ num = 0;
+ while (state.cp < state.cpend) {
+ c = src[state.cp];
+ if ((c >= '0') && (c <= '7')) {
+ state.cp++;
+ tmp = 8 * num + (c - '0');
+ if (tmp > 0377)
+ break;
+ num = tmp;
+ }
+ else
+ break;
+ }
+ c = (char)(num);
+ doFlat(state, c);
+ break;
+ }
+ /* otherwise, it's a back-reference */
+ state.result = new RENode(REOP_BACKREF);
+ state.result.parenIndex = num - 1;
+ state.progLength += 3;
+ break;
+ /* Control escape */
+ case 'f':
+ c = 0xC;
+ doFlat(state, c);
+ break;
+ case 'n':
+ c = 0xA;
+ doFlat(state, c);
+ break;
+ case 'r':
+ c = 0xD;
+ doFlat(state, c);
+ break;
+ case 't':
+ c = 0x9;
+ doFlat(state, c);
+ break;
+ case 'v':
+ c = 0xB;
+ doFlat(state, c);
+ break;
+ /* Control letter */
+ case 'c':
+ if (((state.cp + 1) < state.cpend) &&
+ Character.isLetter(src[state.cp + 1]))
+ c = (char)(src[state.cp++] & 0x1F);
+ else {
+ /* back off to accepting the original '\' as a literal */
+ --state.cp;
+ c = '\\';
+ }
+ doFlat(state, c);
+ break;
+ /* UnicodeEscapeSequence */
+ case 'u':
+ nDigits += 2;
+ // fall thru...
+ /* HexEscapeSequence */
+ case 'x':
+ {
+ int n = 0;
+ int i;
+ for (i = 0; (i < nDigits)
+ && (state.cp < state.cpend); i++) {
+ c = src[state.cp++];
+ n = Kit.xDigitToInt(c, n);
+ if (n < 0) {
+ // Back off to accepting the original
+ // 'u' or 'x' as a literal
+ state.cp -= (i + 2);
+ n = src[state.cp++];
+ break;
+ }
+ }
+ c = (char)(n);
+ }
+ doFlat(state, c);
+ break;
+ /* Character class escapes */
+ case 'd':
+ state.result = new RENode(REOP_DIGIT);
+ state.progLength++;
+ break;
+ case 'D':
+ state.result = new RENode(REOP_NONDIGIT);
+ state.progLength++;
+ break;
+ case 's':
+ state.result = new RENode(REOP_SPACE);
+ state.progLength++;
+ break;
+ case 'S':
+ state.result = new RENode(REOP_NONSPACE);
+ state.progLength++;
+ break;
+ case 'w':
+ state.result = new RENode(REOP_ALNUM);
+ state.progLength++;
+ break;
+ case 'W':
+ state.result = new RENode(REOP_NONALNUM);
+ state.progLength++;
+ break;
+ /* IdentityEscape */
+ default:
+ state.result = new RENode(REOP_FLAT);
+ state.result.chr = c;
+ state.result.length = 1;
+ state.result.flatIndex = state.cp - 1;
+ state.progLength += 3;
+ break;
+ }
+ break;
+ }
+ else {
+ /* a trailing '\' is an error */
+ reportError("msg.trail.backslash", "");
+ return false;
+ }
+ case '(': {
+ RENode result = null;
+ termStart = state.cp;
+ if (state.cp + 1 < state.cpend && src[state.cp] == '?'
+ && ((c = src[state.cp + 1]) == '=' || c == '!' || c == ':'))
+ {
+ state.cp += 2;
+ if (c == '=') {
+ result = new RENode(REOP_ASSERT);
+ /* ASSERT, <next>, ... ASSERTTEST */
+ state.progLength += 4;
+ } else if (c == '!') {
+ result = new RENode(REOP_ASSERT_NOT);
+ /* ASSERTNOT, <next>, ... ASSERTNOTTEST */
+ state.progLength += 4;
+ }
+ } else {
+ result = new RENode(REOP_LPAREN);
+ /* LPAREN, <index>, ... RPAREN, <index> */
+ state.progLength += 6;
+ result.parenIndex = state.parenCount++;
+ }
+ ++state.parenNesting;
+ if (!parseDisjunction(state))
+ return false;
+ if (state.cp == state.cpend || src[state.cp] != ')') {
+ reportError("msg.unterm.paren", "");
+ return false;
+ }
+ ++state.cp;
+ --state.parenNesting;
+ if (result != null) {
+ result.kid = state.result;
+ state.result = result;
+ }
+ break;
+ }
+ case ')':
+ reportError("msg.re.unmatched.right.paren", "");
+ return false;
+ case '[':
+ state.result = new RENode(REOP_CLASS);
+ termStart = state.cp;
+ state.result.startIndex = termStart;
+ while (true) {
+ if (state.cp == state.cpend) {
+ reportError("msg.unterm.class", "");
+ return false;
+ }
+ if (src[state.cp] == '\\')
+ state.cp++;
+ else {
+ if (src[state.cp] == ']') {
+ state.result.kidlen = state.cp - termStart;
+ break;
+ }
+ }
+ state.cp++;
+ }
+ state.result.index = state.classCount++;
+ /*
+ * Call calculateBitmapSize now as we want any errors it finds
+ * to be reported during the parse phase, not at execution.
+ */
+ if (!calculateBitmapSize(state, state.result, src, termStart, state.cp++))
+ return false;
+ state.progLength += 3; /* CLASS, <index> */
+ break;
+
+ case '.':
+ state.result = new RENode(REOP_DOT);
+ state.progLength++;
+ break;
+ case '*':
+ case '+':
+ case '?':
+ reportError("msg.bad.quant", String.valueOf(src[state.cp - 1]));
+ return false;
+ default:
+ state.result = new RENode(REOP_FLAT);
+ state.result.chr = c;
+ state.result.length = 1;
+ state.result.flatIndex = state.cp - 1;
+ state.progLength += 3;
+ break;
+ }
+
+ term = state.result;
+ if (state.cp == state.cpend) {
+ return true;
+ }
+ boolean hasQ = false;
+ switch (src[state.cp]) {
+ case '+':
+ state.result = new RENode(REOP_QUANT);
+ state.result.min = 1;
+ state.result.max = -1;
+ /* <PLUS>, <parencount>, <parenindex>, <next> ... <ENDCHILD> */
+ state.progLength += 8;
+ hasQ = true;
+ break;
+ case '*':
+ state.result = new RENode(REOP_QUANT);
+ state.result.min = 0;
+ state.result.max = -1;
+ /* <STAR>, <parencount>, <parenindex>, <next> ... <ENDCHILD> */
+ state.progLength += 8;
+ hasQ = true;
+ break;
+ case '?':
+ state.result = new RENode(REOP_QUANT);
+ state.result.min = 0;
+ state.result.max = 1;
+ /* <OPT>, <parencount>, <parenindex>, <next> ... <ENDCHILD> */
+ state.progLength += 8;
+ hasQ = true;
+ break;
+ case '{': /* balance '}' */
+ {
+ int min = 0;
+ int max = -1;
+ int leftCurl = state.cp;
+
+ /* For Perl etc. compatibility, if quntifier does not match
+ * \{\d+(,\d*)?\} exactly back off from it
+ * being a quantifier, and chew it up as a literal
+ * atom next time instead.
+ */
+
+ c = src[++state.cp];
+ if (isDigit(c)) {
+ ++state.cp;
+ min = getDecimalValue(c, state, 0xFFFF,
+ "msg.overlarge.min");
+ c = src[state.cp];
+ if (c == ',') {
+ c = src[++state.cp];
+ if (isDigit(c)) {
+ ++state.cp;
+ max = getDecimalValue(c, state, 0xFFFF,
+ "msg.overlarge.max");
+ c = src[state.cp];
+ if (min > max) {
+ reportError("msg.max.lt.min",
+ String.valueOf(src[state.cp]));
+ return false;
+ }
+ }
+ } else {
+ max = min;
+ }
+ /* balance '{' */
+ if (c == '}') {
+ state.result = new RENode(REOP_QUANT);
+ state.result.min = min;
+ state.result.max = max;
+ // QUANT, <min>, <max>, <parencount>,
+ // <parenindex>, <next> ... <ENDCHILD>
+ state.progLength += 12;
+ hasQ = true;
+ }
+ }
+ if (!hasQ) {
+ state.cp = leftCurl;
+ }
+ break;
+ }
+ }
+ if (!hasQ)
+ return true;
+
+ ++state.cp;
+ state.result.kid = term;
+ state.result.parenIndex = parenBaseCount;
+ state.result.parenCount = state.parenCount - parenBaseCount;
+ if ((state.cp < state.cpend) && (src[state.cp] == '?')) {
+ ++state.cp;
+ state.result.greedy = false;
+ }
+ else
+ state.result.greedy = true;
+ return true;
+ }
+
+ private static void resolveForwardJump(byte[] array, int from, int pc)
+ {
+ if (from > pc) throw Kit.codeBug();
+ addIndex(array, from, pc - from);
+ }
+
+ private static int getOffset(byte[] array, int pc)
+ {
+ return getIndex(array, pc);
+ }
+
+ private static int addIndex(byte[] array, int pc, int index)
+ {
+ if (index < 0) throw Kit.codeBug();
+ if (index > 0xFFFF)
+ throw Context.reportRuntimeError("Too complex regexp");
+ array[pc] = (byte)(index >> 8);
+ array[pc + 1] = (byte)(index);
+ return pc + 2;
+ }
+
+ private static int getIndex(byte[] array, int pc)
+ {
+ return ((array[pc] & 0xFF) << 8) | (array[pc + 1] & 0xFF);
+ }
+
+ private static final int OFFSET_LEN = 2;
+ private static final int INDEX_LEN = 2;
+
+ private static int
+ emitREBytecode(CompilerState state, RECompiled re, int pc, RENode t)
+ {
+ RENode nextAlt;
+ int nextAltFixup, nextTermFixup;
+ byte[] program = re.program;
+
+ while (t != null) {
+ program[pc++] = t.op;
+ switch (t.op) {
+ case REOP_EMPTY:
+ --pc;
+ break;
+ case REOP_ALT:
+ nextAlt = t.kid2;
+ nextAltFixup = pc; /* address of next alternate */
+ pc += OFFSET_LEN;
+ pc = emitREBytecode(state, re, pc, t.kid);
+ program[pc++] = REOP_JUMP;
+ nextTermFixup = pc; /* address of following term */
+ pc += OFFSET_LEN;
+ resolveForwardJump(program, nextAltFixup, pc);
+ pc = emitREBytecode(state, re, pc, nextAlt);
+
+ program[pc++] = REOP_JUMP;
+ nextAltFixup = pc;
+ pc += OFFSET_LEN;
+
+ resolveForwardJump(program, nextTermFixup, pc);
+ resolveForwardJump(program, nextAltFixup, pc);
+ break;
+ case REOP_FLAT:
+ /*
+ * Consecutize FLAT's if possible.
+ */
+ if (t.flatIndex != -1) {
+ while ((t.next != null) && (t.next.op == REOP_FLAT)
+ && ((t.flatIndex + t.length)
+ == t.next.flatIndex)) {
+ t.length += t.next.length;
+ t.next = t.next.next;
+ }
+ }
+ if ((t.flatIndex != -1) && (t.length > 1)) {
+ if ((state.flags & JSREG_FOLD) != 0)
+ program[pc - 1] = REOP_FLATi;
+ else
+ program[pc - 1] = REOP_FLAT;
+ pc = addIndex(program, pc, t.flatIndex);
+ pc = addIndex(program, pc, t.length);
+ }
+ else {
+ if (t.chr < 256) {
+ if ((state.flags & JSREG_FOLD) != 0)
+ program[pc - 1] = REOP_FLAT1i;
+ else
+ program[pc - 1] = REOP_FLAT1;
+ program[pc++] = (byte)(t.chr);
+ }
+ else {
+ if ((state.flags & JSREG_FOLD) != 0)
+ program[pc - 1] = REOP_UCFLAT1i;
+ else
+ program[pc - 1] = REOP_UCFLAT1;
+ pc = addIndex(program, pc, t.chr);
+ }
+ }
+ break;
+ case REOP_LPAREN:
+ pc = addIndex(program, pc, t.parenIndex);
+ pc = emitREBytecode(state, re, pc, t.kid);
+ program[pc++] = REOP_RPAREN;
+ pc = addIndex(program, pc, t.parenIndex);
+ break;
+ case REOP_BACKREF:
+ pc = addIndex(program, pc, t.parenIndex);
+ break;
+ case REOP_ASSERT:
+ nextTermFixup = pc;
+ pc += OFFSET_LEN;
+ pc = emitREBytecode(state, re, pc, t.kid);
+ program[pc++] = REOP_ASSERTTEST;
+ resolveForwardJump(program, nextTermFixup, pc);
+ break;
+ case REOP_ASSERT_NOT:
+ nextTermFixup = pc;
+ pc += OFFSET_LEN;
+ pc = emitREBytecode(state, re, pc, t.kid);
+ program[pc++] = REOP_ASSERTNOTTEST;
+ resolveForwardJump(program, nextTermFixup, pc);
+ break;
+ case REOP_QUANT:
+ if ((t.min == 0) && (t.max == -1))
+ program[pc - 1] = (t.greedy) ? REOP_STAR : REOP_MINIMALSTAR;
+ else
+ if ((t.min == 0) && (t.max == 1))
+ program[pc - 1] = (t.greedy) ? REOP_OPT : REOP_MINIMALOPT;
+ else
+ if ((t.min == 1) && (t.max == -1))
+ program[pc - 1] = (t.greedy) ? REOP_PLUS : REOP_MINIMALPLUS;
+ else {
+ if (!t.greedy) program[pc - 1] = REOP_MINIMALQUANT;
+ pc = addIndex(program, pc, t.min);
+ // max can be -1 which addIndex does not accept
+ pc = addIndex(program, pc, t.max + 1);
+ }
+ pc = addIndex(program, pc, t.parenCount);
+ pc = addIndex(program, pc, t.parenIndex);
+ nextTermFixup = pc;
+ pc += OFFSET_LEN;
+ pc = emitREBytecode(state, re, pc, t.kid);
+ program[pc++] = REOP_ENDCHILD;
+ resolveForwardJump(program, nextTermFixup, pc);
+ break;
+ case REOP_CLASS:
+ pc = addIndex(program, pc, t.index);
+ re.classList[t.index] = new RECharSet(t.bmsize, t.startIndex,
+ t.kidlen);
+ break;
+ default:
+ break;
+ }
+ t = t.next;
+ }
+ return pc;
+ }
+
+ private static void
+ pushProgState(REGlobalData gData, int min, int max,
+ REBackTrackData backTrackLastToSave,
+ int continuation_pc, int continuation_op)
+ {
+ gData.stateStackTop = new REProgState(gData.stateStackTop, min, max,
+ gData.cp, backTrackLastToSave,
+ continuation_pc,
+ continuation_op);
+ }
+
+ private static REProgState
+ popProgState(REGlobalData gData)
+ {
+ REProgState state = gData.stateStackTop;
+ gData.stateStackTop = state.previous;
+ return state;
+ }
+
+ private static void
+ pushBackTrackState(REGlobalData gData, byte op, int target)
+ {
+ gData.backTrackStackTop = new REBackTrackData(gData, op, target);
+ }
+
+ /*
+ * Consecutive literal characters.
+ */
+ private static boolean
+ flatNMatcher(REGlobalData gData, int matchChars,
+ int length, char[] chars, int end)
+ {
+ if ((gData.cp + length) > end)
+ return false;
+ for (int i = 0; i < length; i++) {
+ if (gData.regexp.source[matchChars + i] != chars[gData.cp + i]) {
+ return false;
+ }
+ }
+ gData.cp += length;
+ return true;
+ }
+
+ private static boolean
+ flatNIMatcher(REGlobalData gData, int matchChars,
+ int length, char[] chars, int end)
+ {
+ if ((gData.cp + length) > end)
+ return false;
+ for (int i = 0; i < length; i++) {
+ if (upcase(gData.regexp.source[matchChars + i])
+ != upcase(chars[gData.cp + i]))
+ {
+ return false;
+ }
+ }
+ gData.cp += length;
+ return true;
+ }
+
+ /*
+ 1. Evaluate DecimalEscape to obtain an EscapeValue E.
+ 2. If E is not a character then go to step 6.
+ 3. Let ch be E's character.
+ 4. Let A be a one-element RECharSet containing the character ch.
+ 5. Call CharacterSetMatcher(A, false) and return its Matcher result.
+ 6. E must be an integer. Let n be that integer.
+ 7. If n=0 or n>NCapturingParens then throw a SyntaxError exception.
+ 8. Return an internal Matcher closure that takes two arguments, a State x
+ and a Continuation c, and performs the following:
+ 1. Let cap be x's captures internal array.
+ 2. Let s be cap[n].
+ 3. If s is undefined, then call c(x) and return its result.
+ 4. Let e be x's endIndex.
+ 5. Let len be s's length.
+ 6. Let f be e+len.
+ 7. If f>InputLength, return failure.
+ 8. If there exists an integer i between 0 (inclusive) and len (exclusive)
+ such that Canonicalize(s[i]) is not the same character as
+ Canonicalize(Input [e+i]), then return failure.
+ 9. Let y be the State (f, cap).
+ 10. Call c(y) and return its result.
+ */
+ private static boolean
+ backrefMatcher(REGlobalData gData, int parenIndex,
+ char[] chars, int end)
+ {
+ int len;
+ int i;
+ int parenContent = gData.parens_index(parenIndex);
+ if (parenContent == -1)
+ return true;
+
+ len = gData.parens_length(parenIndex);
+ if ((gData.cp + len) > end)
+ return false;
+
+ if ((gData.regexp.flags & JSREG_FOLD) != 0) {
+ for (i = 0; i < len; i++) {
+ if (upcase(chars[parenContent + i]) != upcase(chars[gData.cp + i]))
+ return false;
+ }
+ }
+ else {
+ for (i = 0; i < len; i++) {
+ if (chars[parenContent + i] != chars[gData.cp + i])
+ return false;
+ }
+ }
+ gData.cp += len;
+ return true;
+ }
+
+
+ /* Add a single character to the RECharSet */
+ private static void
+ addCharacterToCharSet(RECharSet cs, char c)
+ {
+ int byteIndex = (c / 8);
+ if (c > cs.length)
+ throw new RuntimeException();
+ cs.bits[byteIndex] |= 1 << (c & 0x7);
+ }
+
+
+ /* Add a character range, c1 to c2 (inclusive) to the RECharSet */
+ private static void
+ addCharacterRangeToCharSet(RECharSet cs, char c1, char c2)
+ {
+ int i;
+
+ int byteIndex1 = (c1 / 8);
+ int byteIndex2 = (c2 / 8);
+
+ if ((c2 > cs.length) || (c1 > c2))
+ throw new RuntimeException();
+
+ c1 &= 0x7;
+ c2 &= 0x7;
+
+ if (byteIndex1 == byteIndex2) {
+ cs.bits[byteIndex1] |= ((0xFF) >> (7 - (c2 - c1))) << c1;
+ }
+ else {
+ cs.bits[byteIndex1] |= 0xFF << c1;
+ for (i = byteIndex1 + 1; i < byteIndex2; i++)
+ cs.bits[i] = (byte)0xFF;
+ cs.bits[byteIndex2] |= (0xFF) >> (7 - c2);
+ }
+ }
+
+ /* Compile the source of the class into a RECharSet */
+ private static void
+ processCharSet(REGlobalData gData, RECharSet charSet)
+ {
+ synchronized (charSet) {
+ if (!charSet.converted) {
+ processCharSetImpl(gData, charSet);
+ charSet.converted = true;
+ }
+ }
+ }
+
+
+ private static void
+ processCharSetImpl(REGlobalData gData, RECharSet charSet)
+ {
+ int src = charSet.startIndex;
+ int end = src + charSet.strlength;
+
+ char rangeStart = 0, thisCh;
+ int byteLength;
+ char c;
+ int n;
+ int nDigits;
+ int i;
+ boolean inRange = false;
+
+ charSet.sense = true;
+ byteLength = (charSet.length / 8) + 1;
+ charSet.bits = new byte[byteLength];
+
+ if (src == end)
+ return;
+
+ if (gData.regexp.source[src] == '^') {
+ charSet.sense = false;
+ ++src;
+ }
+
+ while (src != end) {
+ nDigits = 2;
+ switch (gData.regexp.source[src]) {
+ case '\\':
+ ++src;
+ c = gData.regexp.source[src++];
+ switch (c) {
+ case 'b':
+ thisCh = 0x8;
+ break;
+ case 'f':
+ thisCh = 0xC;
+ break;
+ case 'n':
+ thisCh = 0xA;
+ break;
+ case 'r':
+ thisCh = 0xD;
+ break;
+ case 't':
+ thisCh = 0x9;
+ break;
+ case 'v':
+ thisCh = 0xB;
+ break;
+ case 'c':
+ if (((src + 1) < end) && isWord(gData.regexp.source[src + 1]))
+ thisCh = (char)(gData.regexp.source[src++] & 0x1F);
+ else {
+ --src;
+ thisCh = '\\';
+ }
+ break;
+ case 'u':
+ nDigits += 2;
+ // fall thru
+ case 'x':
+ n = 0;
+ for (i = 0; (i < nDigits) && (src < end); i++) {
+ c = gData.regexp.source[src++];
+ int digit = toASCIIHexDigit(c);
+ if (digit < 0) {
+ /* back off to accepting the original '\'
+ * as a literal
+ */
+ src -= (i + 1);
+ n = '\\';
+ break;
+ }
+ n = (n << 4) | digit;
+ }
+ thisCh = (char)(n);
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ /*
+ * This is a non-ECMA extension - decimal escapes (in this
+ * case, octal!) are supposed to be an error inside class
+ * ranges, but supported here for backwards compatibility.
+ *
+ */
+ n = (c - '0');
+ c = gData.regexp.source[src];
+ if ('0' <= c && c <= '7') {
+ src++;
+ n = 8 * n + (c - '0');
+ c = gData.regexp.source[src];
+ if ('0' <= c && c <= '7') {
+ src++;
+ i = 8 * n + (c - '0');
+ if (i <= 0377)
+ n = i;
+ else
+ src--;
+ }
+ }
+ thisCh = (char)(n);
+ break;
+
+ case 'd':
+ addCharacterRangeToCharSet(charSet, '0', '9');
+ continue; /* don't need range processing */
+ case 'D':
+ addCharacterRangeToCharSet(charSet, (char)0, (char)('0' - 1));
+ addCharacterRangeToCharSet(charSet, (char)('9' + 1),
+ (char)(charSet.length));
+ continue;
+ case 's':
+ for (i = charSet.length; i >= 0; i--)
+ if (isREWhiteSpace(i))
+ addCharacterToCharSet(charSet, (char)(i));
+ continue;
+ case 'S':
+ for (i = charSet.length; i >= 0; i--)
+ if (!isREWhiteSpace(i))
+ addCharacterToCharSet(charSet, (char)(i));
+ continue;
+ case 'w':
+ for (i = charSet.length; i >= 0; i--)
+ if (isWord((char)i))
+ addCharacterToCharSet(charSet, (char)(i));
+ continue;
+ case 'W':
+ for (i = charSet.length; i >= 0; i--)
+ if (!isWord((char)i))
+ addCharacterToCharSet(charSet, (char)(i));
+ continue;
+ default:
+ thisCh = c;
+ break;
+
+ }
+ break;
+
+ default:
+ thisCh = gData.regexp.source[src++];
+ break;
+
+ }
+ if (inRange) {
+ if ((gData.regexp.flags & JSREG_FOLD) != 0) {
+ addCharacterRangeToCharSet(charSet,
+ upcase(rangeStart),
+ upcase(thisCh));
+ addCharacterRangeToCharSet(charSet,
+ downcase(rangeStart),
+ downcase(thisCh));
+ } else {
+ addCharacterRangeToCharSet(charSet, rangeStart, thisCh);
+ }
+ inRange = false;
+ }
+ else {
+ if ((gData.regexp.flags & JSREG_FOLD) != 0) {
+ addCharacterToCharSet(charSet, upcase(thisCh));
+ addCharacterToCharSet(charSet, downcase(thisCh));
+ } else {
+ addCharacterToCharSet(charSet, thisCh);
+ }
+ if (src < (end - 1)) {
+ if (gData.regexp.source[src] == '-') {
+ ++src;
+ inRange = true;
+ rangeStart = thisCh;
+ }
+ }
+ }
+ }
+ }
+
+
+ /*
+ * Initialize the character set if it this is the first call.
+ * Test the bit - if the ^ flag was specified, non-inclusion is a success
+ */
+ private static boolean
+ classMatcher(REGlobalData gData, RECharSet charSet, char ch)
+ {
+ if (!charSet.converted) {
+ processCharSet(gData, charSet);
+ }
+
+ int byteIndex = ch / 8;
+ if (charSet.sense) {
+ if ((charSet.length == 0) ||
+ ( (ch > charSet.length)
+ || ((charSet.bits[byteIndex] & (1 << (ch & 0x7))) == 0) ))
+ return false;
+ } else {
+ if (! ((charSet.length == 0) ||
+ ( (ch > charSet.length)
+ || ((charSet.bits[byteIndex] & (1 << (ch & 0x7))) == 0) )))
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean
+ executeREBytecode(REGlobalData gData, char[] chars, int end)
+ {
+ int pc = 0;
+ byte program[] = gData.regexp.program;
+ int currentContinuation_op;
+ int currentContinuation_pc;
+ boolean result = false;
+
+ currentContinuation_pc = 0;
+ currentContinuation_op = REOP_END;
+if (debug) {
+System.out.println("Input = \"" + new String(chars) + "\", start at " + gData.cp);
+}
+ int op = program[pc++];
+ for (;;) {
+if (debug) {
+System.out.println("Testing at " + gData.cp + ", op = " + op);
+}
+ switch (op) {
+ case REOP_EMPTY:
+ result = true;
+ break;
+ case REOP_BOL:
+ if (gData.cp != 0) {
+ if (gData.multiline ||
+ ((gData.regexp.flags & JSREG_MULTILINE) != 0)) {
+ if (!isLineTerm(chars[gData.cp - 1])) {
+ result = false;
+ break;
+ }
+ }
+ else {
+ result = false;
+ break;
+ }
+ }
+ result = true;
+ break;
+ case REOP_EOL:
+ if (gData.cp != end) {
+ if (gData.multiline ||
+ ((gData.regexp.flags & JSREG_MULTILINE) != 0)) {
+ if (!isLineTerm(chars[gData.cp])) {
+ result = false;
+ break;
+ }
+ }
+ else {
+ result = false;
+ break;
+ }
+ }
+ result = true;
+ break;
+ case REOP_WBDRY:
+ result = ((gData.cp == 0 || !isWord(chars[gData.cp - 1]))
+ ^ !((gData.cp < end) && isWord(chars[gData.cp])));
+ break;
+ case REOP_WNONBDRY:
+ result = ((gData.cp == 0 || !isWord(chars[gData.cp - 1]))
+ ^ ((gData.cp < end) && isWord(chars[gData.cp])));
+ break;
+ case REOP_DOT:
+ result = (gData.cp != end && !isLineTerm(chars[gData.cp]));
+ if (result) {
+ gData.cp++;
+ }
+ break;
+ case REOP_DIGIT:
+ result = (gData.cp != end && isDigit(chars[gData.cp]));
+ if (result) {
+ gData.cp++;
+ }
+ break;
+ case REOP_NONDIGIT:
+ result = (gData.cp != end && !isDigit(chars[gData.cp]));
+ if (result) {
+ gData.cp++;
+ }
+ break;
+ case REOP_SPACE:
+ result = (gData.cp != end && isREWhiteSpace(chars[gData.cp]));
+ if (result) {
+ gData.cp++;
+ }
+ break;
+ case REOP_NONSPACE:
+ result = (gData.cp != end && !isREWhiteSpace(chars[gData.cp]));
+ if (result) {
+ gData.cp++;
+ }
+ break;
+ case REOP_ALNUM:
+ result = (gData.cp != end && isWord(chars[gData.cp]));
+ if (result) {
+ gData.cp++;
+ }
+ break;
+ case REOP_NONALNUM:
+ result = (gData.cp != end && !isWord(chars[gData.cp]));
+ if (result) {
+ gData.cp++;
+ }
+ break;
+ case REOP_FLAT:
+ {
+ int offset = getIndex(program, pc);
+ pc += INDEX_LEN;
+ int length = getIndex(program, pc);
+ pc += INDEX_LEN;
+ result = flatNMatcher(gData, offset, length, chars, end);
+ }
+ break;
+ case REOP_FLATi:
+ {
+ int offset = getIndex(program, pc);
+ pc += INDEX_LEN;
+ int length = getIndex(program, pc);
+ pc += INDEX_LEN;
+ result = flatNIMatcher(gData, offset, length, chars, end);
+ }
+ break;
+ case REOP_FLAT1:
+ {
+ char matchCh = (char)(program[pc++] & 0xFF);
+ result = (gData.cp != end && chars[gData.cp] == matchCh);
+ if (result) {
+ gData.cp++;
+ }
+ }
+ break;
+ case REOP_FLAT1i:
+ {
+ char matchCh = (char)(program[pc++] & 0xFF);
+ result = (gData.cp != end
+ && upcase(chars[gData.cp]) == upcase(matchCh));
+ if (result) {
+ gData.cp++;
+ }
+ }
+ break;
+ case REOP_UCFLAT1:
+ {
+ char matchCh = (char)getIndex(program, pc);
+ pc += INDEX_LEN;
+ result = (gData.cp != end && chars[gData.cp] == matchCh);
+ if (result) {
+ gData.cp++;
+ }
+ }
+ break;
+ case REOP_UCFLAT1i:
+ {
+ char matchCh = (char)getIndex(program, pc);
+ pc += INDEX_LEN;
+ result = (gData.cp != end
+ && upcase(chars[gData.cp]) == upcase(matchCh));
+ if (result) {
+ gData.cp++;
+ }
+ }
+ break;
+ case REOP_ALT:
+ {
+ int nextpc;
+ byte nextop;
+ pushProgState(gData, 0, 0, null,
+ currentContinuation_pc,
+ currentContinuation_op);
+ nextpc = pc + getOffset(program, pc);
+ nextop = program[nextpc++];
+ pushBackTrackState(gData, nextop, nextpc);
+ pc += INDEX_LEN;
+ op = program[pc++];
+ }
+ continue;
+
+ case REOP_JUMP:
+ {
+ int offset;
+ REProgState state = popProgState(gData);
+ currentContinuation_pc = state.continuation_pc;
+ currentContinuation_op = state.continuation_op;
+ offset = getOffset(program, pc);
+ pc += offset;
+ op = program[pc++];
+ }
+ continue;
+
+
+ case REOP_LPAREN:
+ {
+ int parenIndex = getIndex(program, pc);
+ pc += INDEX_LEN;
+ gData.set_parens(parenIndex, gData.cp, 0);
+ op = program[pc++];
+ }
+ continue;
+ case REOP_RPAREN:
+ {
+ int cap_index;
+ int parenIndex = getIndex(program, pc);
+ pc += INDEX_LEN;
+ cap_index = gData.parens_index(parenIndex);
+ gData.set_parens(parenIndex, cap_index,
+ gData.cp - cap_index);
+ if (parenIndex > gData.lastParen)
+ gData.lastParen = parenIndex;
+ op = program[pc++];
+ }
+ continue;
+ case REOP_BACKREF:
+ {
+ int parenIndex = getIndex(program, pc);
+ pc += INDEX_LEN;
+ result = backrefMatcher(gData, parenIndex, chars, end);
+ }
+ break;
+
+ case REOP_CLASS:
+ {
+ int index = getIndex(program, pc);
+ pc += INDEX_LEN;
+ if (gData.cp != end) {
+ if (classMatcher(gData, gData.regexp.classList[index],
+ chars[gData.cp]))
+ {
+ gData.cp++;
+ result = true;
+ break;
+ }
+ }
+ result = false;
+ }
+ break;
+
+ case REOP_ASSERT:
+ case REOP_ASSERT_NOT:
+ {
+ byte testOp;
+ pushProgState(gData, 0, 0, gData.backTrackStackTop,
+ currentContinuation_pc,
+ currentContinuation_op);
+ if (op == REOP_ASSERT) {
+ testOp = REOP_ASSERTTEST;
+ } else {
+ testOp = REOP_ASSERTNOTTEST;
+ }
+ pushBackTrackState(gData, testOp,
+ pc + getOffset(program, pc));
+ pc += INDEX_LEN;
+ op = program[pc++];
+ }
+ continue;
+
+ case REOP_ASSERTTEST:
+ case REOP_ASSERTNOTTEST:
+ {
+ REProgState state = popProgState(gData);
+ gData.cp = state.index;
+ gData.backTrackStackTop = state.backTrack;
+ currentContinuation_pc = state.continuation_pc;
+ currentContinuation_op = state.continuation_op;
+ if (result) {
+ if (op == REOP_ASSERTTEST) {
+ result = true;
+ } else {
+ result = false;
+ }
+ } else {
+ if (op == REOP_ASSERTTEST) {
+ // Do nothing
+ } else {
+ result = true;
+ }
+ }
+ }
+ break;
+
+ case REOP_STAR:
+ case REOP_PLUS:
+ case REOP_OPT:
+ case REOP_QUANT:
+ case REOP_MINIMALSTAR:
+ case REOP_MINIMALPLUS:
+ case REOP_MINIMALOPT:
+ case REOP_MINIMALQUANT:
+ {
+ int min, max;
+ boolean greedy = false;
+ switch (op) {
+ case REOP_STAR:
+ greedy = true;
+ // fallthrough
+ case REOP_MINIMALSTAR:
+ min = 0;
+ max = -1;
+ break;
+ case REOP_PLUS:
+ greedy = true;
+ // fallthrough
+ case REOP_MINIMALPLUS:
+ min = 1;
+ max = -1;
+ break;
+ case REOP_OPT:
+ greedy = true;
+ // fallthrough
+ case REOP_MINIMALOPT:
+ min = 0;
+ max = 1;
+ break;
+ case REOP_QUANT:
+ greedy = true;
+ // fallthrough
+ case REOP_MINIMALQUANT:
+ min = getOffset(program, pc);
+ pc += INDEX_LEN;
+ // See comments in emitREBytecode for " - 1" reason
+ max = getOffset(program, pc) - 1;
+ pc += INDEX_LEN;
+ break;
+ default:
+ throw Kit.codeBug();
+ }
+ pushProgState(gData, min, max, null,
+ currentContinuation_pc,
+ currentContinuation_op);
+ if (greedy) {
+ currentContinuation_op = REOP_REPEAT;
+ currentContinuation_pc = pc;
+ pushBackTrackState(gData, REOP_REPEAT, pc);
+ /* Step over <parencount>, <parenindex> & <next> */
+ pc += 3 * INDEX_LEN;
+ op = program[pc++];
+ } else {
+ if (min != 0) {
+ currentContinuation_op = REOP_MINIMALREPEAT;
+ currentContinuation_pc = pc;
+ /* <parencount> <parenindex> & <next> */
+ pc += 3 * INDEX_LEN;
+ op = program[pc++];
+ } else {
+ pushBackTrackState(gData, REOP_MINIMALREPEAT, pc);
+ popProgState(gData);
+ pc += 2 * INDEX_LEN; // <parencount> & <parenindex>
+ pc = pc + getOffset(program, pc);
+ op = program[pc++];
+ }
+ }
+ }
+ continue;
+
+ case REOP_ENDCHILD:
+ // Use the current continuation.
+ pc = currentContinuation_pc;
+ op = currentContinuation_op;
+ continue;
+
+ case REOP_REPEAT:
+ {
+ REProgState state = popProgState(gData);
+ if (!result) {
+ //
+ // There's been a failure, see if we have enough
+ // children.
+ //
+ if (state.min == 0)
+ result = true;
+ currentContinuation_pc = state.continuation_pc;
+ currentContinuation_op = state.continuation_op;
+ pc += 2 * INDEX_LEN; /* <parencount> & <parenindex> */
+ pc = pc + getOffset(program, pc);
+ break;
+ }
+ else {
+ if (state.min == 0 && gData.cp == state.index) {
+ // matched an empty string, that'll get us nowhere
+ result = false;
+ currentContinuation_pc = state.continuation_pc;
+ currentContinuation_op = state.continuation_op;
+ pc += 2 * INDEX_LEN;
+ pc = pc + getOffset(program, pc);
+ break;
+ }
+ int new_min = state.min, new_max = state.max;
+ if (new_min != 0) new_min--;
+ if (new_max != -1) new_max--;
+ if (new_max == 0) {
+ result = true;
+ currentContinuation_pc = state.continuation_pc;
+ currentContinuation_op = state.continuation_op;
+ pc += 2 * INDEX_LEN;
+ pc = pc + getOffset(program, pc);
+ break;
+ }
+ pushProgState(gData, new_min, new_max, null,
+ state.continuation_pc,
+ state.continuation_op);
+ currentContinuation_op = REOP_REPEAT;
+ currentContinuation_pc = pc;
+ pushBackTrackState(gData, REOP_REPEAT, pc);
+ int parenCount = getIndex(program, pc);
+ pc += INDEX_LEN;
+ int parenIndex = getIndex(program, pc);
+ pc += 2 * INDEX_LEN;
+ op = program[pc++];
+ for (int k = 0; k < parenCount; k++) {
+ gData.set_parens(parenIndex + k, -1, 0);
+ }
+ }
+ }
+ continue;
+
+ case REOP_MINIMALREPEAT:
+ {
+ REProgState state = popProgState(gData);
+ if (!result) {
+ //
+ // Non-greedy failure - try to consume another child.
+ //
+ if (state.max == -1 || state.max > 0) {
+ pushProgState(gData, state.min, state.max, null,
+ state.continuation_pc,
+ state.continuation_op);
+ currentContinuation_op = REOP_MINIMALREPEAT;
+ currentContinuation_pc = pc;
+ int parenCount = getIndex(program, pc);
+ pc += INDEX_LEN;
+ int parenIndex = getIndex(program, pc);
+ pc += 2 * INDEX_LEN;
+ for (int k = 0; k < parenCount; k++) {
+ gData.set_parens(parenIndex + k, -1, 0);
+ }
+ op = program[pc++];
+ continue;
+ } else {
+ // Don't need to adjust pc since we're going to pop.
+ currentContinuation_pc = state.continuation_pc;
+ currentContinuation_op = state.continuation_op;
+ break;
+ }
+ } else {
+ if (state.min == 0 && gData.cp == state.index) {
+ // Matched an empty string, that'll get us nowhere.
+ result = false;
+ currentContinuation_pc = state.continuation_pc;
+ currentContinuation_op = state.continuation_op;
+ break;
+ }
+ int new_min = state.min, new_max = state.max;
+ if (new_min != 0) new_min--;
+ if (new_max != -1) new_max--;
+ pushProgState(gData, new_min, new_max, null,
+ state.continuation_pc,
+ state.continuation_op);
+ if (new_min != 0) {
+ currentContinuation_op = REOP_MINIMALREPEAT;
+ currentContinuation_pc = pc;
+ int parenCount = getIndex(program, pc);
+ pc += INDEX_LEN;
+ int parenIndex = getIndex(program, pc);
+ pc += 2 * INDEX_LEN;
+ for (int k = 0; k < parenCount; k++) {
+ gData.set_parens(parenIndex + k, -1, 0);
+ }
+ op = program[pc++];
+ } else {
+ currentContinuation_pc = state.continuation_pc;
+ currentContinuation_op = state.continuation_op;
+ pushBackTrackState(gData, REOP_MINIMALREPEAT, pc);
+ popProgState(gData);
+ pc += 2 * INDEX_LEN;
+ pc = pc + getOffset(program, pc);
+ op = program[pc++];
+ }
+ continue;
+ }
+ }
+
+ case REOP_END:
+ return true;
+
+ default:
+ throw Kit.codeBug();
+
+ }
+ /*
+ * If the match failed and there's a backtrack option, take it.
+ * Otherwise this is a complete and utter failure.
+ */
+ if (!result) {
+ REBackTrackData backTrackData = gData.backTrackStackTop;
+ if (backTrackData != null) {
+ gData.backTrackStackTop = backTrackData.previous;
+
+ gData.lastParen = backTrackData.lastParen;
+
+ // XXX: If backTrackData will no longer be used, then
+ // there is no need to clone backTrackData.parens
+ if (backTrackData.parens != null) {
+ gData.parens = backTrackData.parens.clone();
+ }
+
+ gData.cp = backTrackData.cp;
+
+ gData.stateStackTop = backTrackData.stateStackTop;
+
+ currentContinuation_op
+ = gData.stateStackTop.continuation_op;
+ currentContinuation_pc
+ = gData.stateStackTop.continuation_pc;
+ pc = backTrackData.continuation_pc;
+ op = backTrackData.continuation_op;
+ continue;
+ }
+ else
+ return false;
+ }
+
+ op = program[pc++];
+ }
+
+ }
+
+ private static boolean
+ matchRegExp(REGlobalData gData, RECompiled re,
+ char[] chars, int start, int end, boolean multiline)
+ {
+ if (re.parenCount != 0) {
+ gData.parens = new long[re.parenCount];
+ } else {
+ gData.parens = null;
+ }
+
+ gData.backTrackStackTop = null;
+
+ gData.stateStackTop = null;
+
+ gData.multiline = multiline;
+ gData.regexp = re;
+ gData.lastParen = 0;
+
+ int anchorCh = gData.regexp.anchorCh;
+ //
+ // have to include the position beyond the last character
+ // in order to detect end-of-input/line condition
+ //
+ for (int i = start; i <= end; ++i) {
+ //
+ // If the first node is a literal match, step the index into
+ // the string until that match is made, or fail if it can't be
+ // found at all.
+ //
+ if (anchorCh >= 0) {
+ for (;;) {
+ if (i == end) {
+ return false;
+ }
+ char matchCh = chars[i];
+ if (matchCh == anchorCh ||
+ ((gData.regexp.flags & JSREG_FOLD) != 0
+ && upcase(matchCh) == upcase((char)anchorCh)))
+ {
+ break;
+ }
+ ++i;
+ }
+ }
+ gData.cp = i;
+ for (int j = 0; j < re.parenCount; j++) {
+ gData.set_parens(j, -1, 0);
+ }
+ boolean result = executeREBytecode(gData, chars, end);
+
+ gData.backTrackStackTop = null;
+ gData.stateStackTop = null;
+ if (result) {
+ gData.skipped = i - start;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /*
+ * indexp is assumed to be an array of length 1
+ */
+ Object executeRegExp(Context cx, Scriptable scopeObj, RegExpImpl res,
+ String str, int indexp[], int matchType)
+ {
+ REGlobalData gData = new REGlobalData();
+
+ int start = indexp[0];
+ char[] charArray = str.toCharArray();
+ int end = charArray.length;
+ if (start > end)
+ start = end;
+ //
+ // Call the recursive matcher to do the real work.
+ //
+ boolean matches = matchRegExp(gData, re, charArray, start, end,
+ res.multiline);
+ if (!matches) {
+ if (matchType != PREFIX) return null;
+ return Undefined.instance;
+ }
+ int index = gData.cp;
+ int i = index;
+ indexp[0] = i;
+ int matchlen = i - (start + gData.skipped);
+ int ep = index;
+ index -= matchlen;
+ Object result;
+ Scriptable obj;
+
+ if (matchType == TEST) {
+ /*
+ * Testing for a match and updating cx.regExpImpl: don't allocate
+ * an array object, do return true.
+ */
+ result = Boolean.TRUE;
+ obj = null;
+ }
+ else {
+ /*
+ * The array returned on match has element 0 bound to the matched
+ * string, elements 1 through re.parenCount bound to the paren
+ * matches, an index property telling the length of the left context,
+ * and an input property referring to the input string.
+ */
+ Scriptable scope = getTopLevelScope(scopeObj);
+ result = ScriptRuntime.newObject(cx, scope, "Array", null);
+ obj = (Scriptable) result;
+
+ String matchstr = new String(charArray, index, matchlen);
+ obj.put(0, obj, matchstr);
+ }
+
+ if (re.parenCount == 0) {
+ res.parens = null;
+ res.lastParen = SubString.emptySubString;
+ } else {
+ SubString parsub = null;
+ int num;
+ res.parens = new SubString[re.parenCount];
+ for (num = 0; num < re.parenCount; num++) {
+ int cap_index = gData.parens_index(num);
+ String parstr;
+ if (cap_index != -1) {
+ int cap_length = gData.parens_length(num);
+ parsub = new SubString(charArray, cap_index, cap_length);
+ res.parens[num] = parsub;
+ if (matchType == TEST) continue;
+ parstr = parsub.toString();
+ obj.put(num+1, obj, parstr);
+ }
+ else {
+ if (matchType != TEST)
+ obj.put(num+1, obj, Undefined.instance);
+ }
+ }
+ res.lastParen = parsub;
+ }
+
+ if (! (matchType == TEST)) {
+ /*
+ * Define the index and input properties last for better for/in loop
+ * order (so they come after the elements).
+ */
+ obj.put("index", obj, new Integer(start + gData.skipped));
+ obj.put("input", obj, str);
+ }
+
+ if (res.lastMatch == null) {
+ res.lastMatch = new SubString();
+ res.leftContext = new SubString();
+ res.rightContext = new SubString();
+ }
+ res.lastMatch.charArray = charArray;
+ res.lastMatch.index = index;
+ res.lastMatch.length = matchlen;
+
+ res.leftContext.charArray = charArray;
+ if (cx.getLanguageVersion() == Context.VERSION_1_2) {
+ /*
+ * JS1.2 emulated Perl4.0.1.8 (patch level 36) for global regexps used
+ * in scalar contexts, and unintentionally for the string.match "list"
+ * psuedo-context. On "hi there bye", the following would result:
+ *
+ * Language while(/ /g){print("$`");} s/ /$`/g
+ * perl4.036 "hi", "there" "hihitherehi therebye"
+ * perl5 "hi", "hi there" "hihitherehi therebye"
+ * js1.2 "hi", "there" "hihitheretherebye"
+ *
+ * Insofar as JS1.2 always defined $` as "left context from the last
+ * match" for global regexps, it was more consistent than perl4.
+ */
+ res.leftContext.index = start;
+ res.leftContext.length = gData.skipped;
+ } else {
+ /*
+ * For JS1.3 and ECMAv2, emulate Perl5 exactly:
+ *
+ * js1.3 "hi", "hi there" "hihitherehi therebye"
+ */
+ res.leftContext.index = 0;
+ res.leftContext.length = start + gData.skipped;
+ }
+
+ res.rightContext.charArray = charArray;
+ res.rightContext.index = ep;
+ res.rightContext.length = end - ep;
+
+ return result;
+ }
+
+ int getFlags()
+ {
+ return re.flags;
+ }
+
+ private static void reportWarning(Context cx, String messageId, String arg)
+ {
+ if (cx.hasFeature(Context.FEATURE_STRICT_MODE)) {
+ String msg = ScriptRuntime.getMessage1(messageId, arg);
+ Context.reportWarning(msg);
+ }
+ }
+
+ private static void reportError(String messageId, String arg)
+ {
+ String msg = ScriptRuntime.getMessage1(messageId, arg);
+ throw ScriptRuntime.constructError("SyntaxError", msg);
+ }
+
+// #string_id_map#
+
+ private static final int
+ Id_lastIndex = 1,
+ Id_source = 2,
+ Id_global = 3,
+ Id_ignoreCase = 4,
+ Id_multiline = 5,
+
+ MAX_INSTANCE_ID = 5;
+
+ @Override
+ protected int getMaxInstanceId()
+ {
+ return MAX_INSTANCE_ID;
+ }
+
+ @Override
+ protected int findInstanceIdInfo(String s)
+ {
+ int id;
+// #generated# Last update: 2007-05-09 08:16:24 EDT
+ L0: { id = 0; String X = null; int c;
+ int s_length = s.length();
+ if (s_length==6) {
+ c=s.charAt(0);
+ if (c=='g') { X="global";id=Id_global; }
+ else if (c=='s') { X="source";id=Id_source; }
+ }
+ else if (s_length==9) {
+ c=s.charAt(0);
+ if (c=='l') { X="lastIndex";id=Id_lastIndex; }
+ else if (c=='m') { X="multiline";id=Id_multiline; }
+ }
+ else if (s_length==10) { X="ignoreCase";id=Id_ignoreCase; }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+// #/string_id_map#
+
+ if (id == 0) return super.findInstanceIdInfo(s);
+
+ int attr;
+ switch (id) {
+ case Id_lastIndex:
+ attr = PERMANENT | DONTENUM;
+ break;
+ case Id_source:
+ case Id_global:
+ case Id_ignoreCase:
+ case Id_multiline:
+ attr = PERMANENT | READONLY | DONTENUM;
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+ return instanceIdInfo(attr, id);
+ }
+
+ @Override
+ protected String getInstanceIdName(int id)
+ {
+ switch (id) {
+ case Id_lastIndex: return "lastIndex";
+ case Id_source: return "source";
+ case Id_global: return "global";
+ case Id_ignoreCase: return "ignoreCase";
+ case Id_multiline: return "multiline";
+ }
+ return super.getInstanceIdName(id);
+ }
+
+ @Override
+ protected Object getInstanceIdValue(int id)
+ {
+ switch (id) {
+ case Id_lastIndex:
+ return ScriptRuntime.wrapNumber(lastIndex);
+ case Id_source:
+ return new String(re.source);
+ case Id_global:
+ return ScriptRuntime.wrapBoolean((re.flags & JSREG_GLOB) != 0);
+ case Id_ignoreCase:
+ return ScriptRuntime.wrapBoolean((re.flags & JSREG_FOLD) != 0);
+ case Id_multiline:
+ return ScriptRuntime.wrapBoolean((re.flags & JSREG_MULTILINE) != 0);
+ }
+ return super.getInstanceIdValue(id);
+ }
+
+ @Override
+ protected void setInstanceIdValue(int id, Object value)
+ {
+ if (id == Id_lastIndex) {
+ lastIndex = ScriptRuntime.toNumber(value);
+ return;
+ }
+ super.setInstanceIdValue(id, value);
+ }
+
+ @Override
+ protected void initPrototypeId(int id)
+ {
+ String s;
+ int arity;
+ switch (id) {
+ case Id_compile: arity=1; s="compile"; break;
+ case Id_toString: arity=0; s="toString"; break;
+ case Id_toSource: arity=0; s="toSource"; break;
+ case Id_exec: arity=1; s="exec"; break;
+ case Id_test: arity=1; s="test"; break;
+ case Id_prefix: arity=1; s="prefix"; break;
+ default: throw new IllegalArgumentException(String.valueOf(id));
+ }
+ initPrototypeMethod(REGEXP_TAG, id, s, arity);
+ }
+
+ @Override
+ public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args)
+ {
+ if (!f.hasTag(REGEXP_TAG)) {
+ return super.execIdCall(f, cx, scope, thisObj, args);
+ }
+ int id = f.methodId();
+ switch (id) {
+ case Id_compile:
+ return realThis(thisObj, f).compile(cx, scope, args);
+
+ case Id_toString:
+ case Id_toSource:
+ return realThis(thisObj, f).toString();
+
+ case Id_exec:
+ return realThis(thisObj, f).execSub(cx, scope, args, MATCH);
+
+ case Id_test: {
+ Object x = realThis(thisObj, f).execSub(cx, scope, args, TEST);
+ return Boolean.TRUE.equals(x) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ case Id_prefix:
+ return realThis(thisObj, f).execSub(cx, scope, args, PREFIX);
+ }
+ throw new IllegalArgumentException(String.valueOf(id));
+ }
+
+ private static NativeRegExp realThis(Scriptable thisObj, IdFunctionObject f)
+ {
+ if (!(thisObj instanceof NativeRegExp))
+ throw incompatibleCallError(f);
+ return (NativeRegExp)thisObj;
+ }
+
+// #string_id_map#
+ @Override
+ protected int findPrototypeId(String s)
+ {
+ int id;
+// #generated# Last update: 2007-05-09 08:16:24 EDT
+ L0: { id = 0; String X = null; int c;
+ L: switch (s.length()) {
+ case 4: c=s.charAt(0);
+ if (c=='e') { X="exec";id=Id_exec; }
+ else if (c=='t') { X="test";id=Id_test; }
+ break L;
+ case 6: X="prefix";id=Id_prefix; break L;
+ case 7: X="compile";id=Id_compile; break L;
+ case 8: c=s.charAt(3);
+ if (c=='o') { X="toSource";id=Id_toSource; }
+ else if (c=='t') { X="toString";id=Id_toString; }
+ break L;
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ break L0;
+ }
+// #/generated#
+ return id;
+ }
+
+ private static final int
+ Id_compile = 1,
+ Id_toString = 2,
+ Id_toSource = 3,
+ Id_exec = 4,
+ Id_test = 5,
+ Id_prefix = 6,
+
+ MAX_PROTOTYPE_ID = 6;
+
+// #/string_id_map#
+
+ private RECompiled re;
+ double lastIndex; /* index after last match, for //g iterator */
+
+} // class NativeRegExp
+
+class RECompiled implements Serializable
+{
+ static final long serialVersionUID = -6144956577595844213L;
+
+ char []source; /* locked source string, sans // */
+ int parenCount; /* number of parenthesized submatches */
+ int flags; /* flags */
+ byte[] program; /* regular expression bytecode */
+ int classCount; /* count [...] bitmaps */
+ RECharSet[] classList; /* list of [...] bitmaps */
+ int anchorCh = -1; /* if >= 0, then re starts with this literal char */
+}
+
+class RENode {
+
+ RENode(byte op)
+ {
+ this.op = op;
+ }
+
+ byte op; /* r.e. op bytecode */
+ RENode next; /* next in concatenation order */
+ RENode kid; /* first operand */
+
+ RENode kid2; /* second operand */
+ int num; /* could be a number */
+ int parenIndex; /* or a parenthesis index */
+
+ /* or a range */
+ int min;
+ int max;
+ int parenCount;
+ boolean greedy;
+
+ /* or a character class */
+ int startIndex;
+ int kidlen; /* length of string at kid, in chars */
+ int bmsize; /* bitmap size, based on max char code */
+ int index; /* index into class list */
+
+ /* or a literal sequence */
+ char chr; /* of one character */
+ int length; /* or many (via the index) */
+ int flatIndex; /* which is -1 if not sourced */
+
+}
+
+class CompilerState {
+
+ CompilerState(Context cx, char[] source, int length, int flags)
+ {
+ this.cx = cx;
+ this.cpbegin = source;
+ this.cp = 0;
+ this.cpend = length;
+ this.flags = flags;
+ this.parenCount = 0;
+ this.classCount = 0;
+ this.progLength = 0;
+ }
+
+ Context cx;
+ char cpbegin[];
+ int cpend;
+ int cp;
+ int flags;
+ int parenCount;
+ int parenNesting;
+ int classCount; /* number of [] encountered */
+ int progLength; /* estimated bytecode length */
+ RENode result;
+}
+
+class REProgState
+{
+ REProgState(REProgState previous, int min, int max, int index,
+ REBackTrackData backTrack,
+ int continuation_pc, int continuation_op)
+ {
+ this.previous = previous;
+ this.min = min;
+ this.max = max;
+ this.index = index;
+ this.continuation_op = continuation_op;
+ this.continuation_pc = continuation_pc;
+ this.backTrack = backTrack;
+ }
+
+ REProgState previous; // previous state in stack
+
+ int min; /* current quantifier min */
+ int max; /* current quantifier max */
+ int index; /* progress in text */
+ int continuation_op;
+ int continuation_pc;
+ REBackTrackData backTrack; // used by ASSERT_ to recover state
+}
+
+class REBackTrackData {
+
+ REBackTrackData(REGlobalData gData, int op, int pc)
+ {
+ previous = gData.backTrackStackTop;
+ continuation_op = op;
+ continuation_pc = pc;
+ lastParen = gData.lastParen;
+ if (gData.parens != null) {
+ parens = gData.parens.clone();
+ }
+ cp = gData.cp;
+ stateStackTop = gData.stateStackTop;
+ }
+
+ REBackTrackData previous;
+
+ int continuation_op; /* where to backtrack to */
+ int continuation_pc;
+ int lastParen;
+ long[] parens; /* parenthesis captures */
+ int cp; /* char buffer index */
+ REProgState stateStackTop; /* state of op that backtracked */
+}
+
+class REGlobalData {
+ boolean multiline;
+ RECompiled regexp; /* the RE in execution */
+ int lastParen; /* highest paren set so far */
+ int skipped; /* chars skipped anchoring this r.e. */
+
+ int cp; /* char buffer index */
+ long[] parens; /* parens captures */
+
+ REProgState stateStackTop; /* stack of state of current ancestors */
+
+ REBackTrackData backTrackStackTop; /* last matched-so-far position */
+
+
+ /**
+ * Get start of parenthesis capture contents, -1 for empty.
+ */
+ int parens_index(int i)
+ {
+ return (int)(parens[i]);
+ }
+
+ /**
+ * Get length of parenthesis capture contents.
+ */
+ int parens_length(int i)
+ {
+ return (int)(parens[i] >>> 32);
+ }
+
+ void set_parens(int i, int index, int length)
+ {
+ parens[i] = (index & 0xffffffffL) | ((long)length << 32);
+ }
+
+}
+
+/*
+ * This struct holds a bitmap representation of a class from a regexp.
+ * There's a list of these referenced by the classList field in the NativeRegExp
+ * struct below. The initial state has startIndex set to the offset in the
+ * original regexp source of the beginning of the class contents. The first
+ * use of the class converts the source representation into a bitmap.
+ *
+ */
+final class RECharSet implements Serializable
+{
+ static final long serialVersionUID = 7931787979395898394L;
+
+ RECharSet(int length, int startIndex, int strlength)
+ {
+ this.length = length;
+ this.startIndex = startIndex;
+ this.strlength = strlength;
+ }
+
+ int length;
+ int startIndex;
+ int strlength;
+
+ volatile transient boolean converted;
+ volatile transient boolean sense;
+ volatile transient byte[] bits;
+}
+
+
diff --git a/src/org/mozilla/javascript/regexp/NativeRegExpCtor.java b/src/org/mozilla/javascript/regexp/NativeRegExpCtor.java
new file mode 100644
index 0000000..4934b25
--- /dev/null
+++ b/src/org/mozilla/javascript/regexp/NativeRegExpCtor.java
@@ -0,0 +1,297 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Brendan Eich
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.regexp;
+
+import org.mozilla.javascript.*;
+
+/**
+ * This class implements the RegExp constructor native object.
+ *
+ * Revision History:
+ * Implementation in C by Brendan Eich
+ * Initial port to Java by Norris Boyd from jsregexp.c version 1.36
+ * Merged up to version 1.38, which included Unicode support.
+ * Merged bug fixes in version 1.39.
+ * Merged JSFUN13_BRANCH changes up to 1.32.2.11
+ *
+ * @author Brendan Eich
+ * @author Norris Boyd
+ */
+class NativeRegExpCtor extends BaseFunction
+{
+ static final long serialVersionUID = -5733330028285400526L;
+
+ NativeRegExpCtor()
+ {
+ }
+
+ @Override
+ public String getFunctionName()
+ {
+ return "RegExp";
+ }
+
+ @Override
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj,
+ Object[] args)
+ {
+ if (args.length > 0 && args[0] instanceof NativeRegExp &&
+ (args.length == 1 || args[1] == Undefined.instance))
+ {
+ return args[0];
+ }
+ return construct(cx, scope, args);
+ }
+
+ @Override
+ public Scriptable construct(Context cx, Scriptable scope, Object[] args)
+ {
+ NativeRegExp re = new NativeRegExp();
+ re.compile(cx, scope, args);
+ ScriptRuntime.setObjectProtoAndParent(re, scope);
+ return re;
+ }
+
+ private static RegExpImpl getImpl()
+ {
+ Context cx = Context.getCurrentContext();
+ return (RegExpImpl) ScriptRuntime.getRegExpProxy(cx);
+ }
+
+// #string_id_map#
+
+ private static final int
+ Id_multiline = 1,
+ Id_STAR = 2, // #string=$*#
+
+ Id_input = 3,
+ Id_UNDERSCORE = 4, // #string=$_#
+
+ Id_lastMatch = 5,
+ Id_AMPERSAND = 6, // #string=$&#
+
+ Id_lastParen = 7,
+ Id_PLUS = 8, // #string=$+#
+
+ Id_leftContext = 9,
+ Id_BACK_QUOTE = 10, // #string=$`#
+
+ Id_rightContext = 11,
+ Id_QUOTE = 12, // #string=$'#
+
+ DOLLAR_ID_BASE = 12;
+
+ private static final int
+ Id_DOLLAR_1 = DOLLAR_ID_BASE + 1, // #string=$1#
+ Id_DOLLAR_2 = DOLLAR_ID_BASE + 2, // #string=$2#
+ Id_DOLLAR_3 = DOLLAR_ID_BASE + 3, // #string=$3#
+ Id_DOLLAR_4 = DOLLAR_ID_BASE + 4, // #string=$4#
+ Id_DOLLAR_5 = DOLLAR_ID_BASE + 5, // #string=$5#
+ Id_DOLLAR_6 = DOLLAR_ID_BASE + 6, // #string=$6#
+ Id_DOLLAR_7 = DOLLAR_ID_BASE + 7, // #string=$7#
+ Id_DOLLAR_8 = DOLLAR_ID_BASE + 8, // #string=$8#
+ Id_DOLLAR_9 = DOLLAR_ID_BASE + 9, // #string=$9#
+
+ MAX_INSTANCE_ID = DOLLAR_ID_BASE + 9;
+
+ @Override
+ protected int getMaxInstanceId()
+ {
+ return super.getMaxInstanceId() + MAX_INSTANCE_ID;
+ }
+
+ @Override
+ protected int findInstanceIdInfo(String s) {
+ int id;
+// #generated# Last update: 2001-05-24 16:09:31 GMT+02:00
+ L0: { id = 0; String X = null; int c;
+ L: switch (s.length()) {
+ case 2: switch (s.charAt(1)) {
+ case '&': if (s.charAt(0)=='$') {id=Id_AMPERSAND; break L0;} break L;
+ case '\'': if (s.charAt(0)=='$') {id=Id_QUOTE; break L0;} break L;
+ case '*': if (s.charAt(0)=='$') {id=Id_STAR; break L0;} break L;
+ case '+': if (s.charAt(0)=='$') {id=Id_PLUS; break L0;} break L;
+ case '1': if (s.charAt(0)=='$') {id=Id_DOLLAR_1; break L0;} break L;
+ case '2': if (s.charAt(0)=='$') {id=Id_DOLLAR_2; break L0;} break L;
+ case '3': if (s.charAt(0)=='$') {id=Id_DOLLAR_3; break L0;} break L;
+ case '4': if (s.charAt(0)=='$') {id=Id_DOLLAR_4; break L0;} break L;
+ case '5': if (s.charAt(0)=='$') {id=Id_DOLLAR_5; break L0;} break L;
+ case '6': if (s.charAt(0)=='$') {id=Id_DOLLAR_6; break L0;} break L;
+ case '7': if (s.charAt(0)=='$') {id=Id_DOLLAR_7; break L0;} break L;
+ case '8': if (s.charAt(0)=='$') {id=Id_DOLLAR_8; break L0;} break L;
+ case '9': if (s.charAt(0)=='$') {id=Id_DOLLAR_9; break L0;} break L;
+ case '_': if (s.charAt(0)=='$') {id=Id_UNDERSCORE; break L0;} break L;
+ case '`': if (s.charAt(0)=='$') {id=Id_BACK_QUOTE; break L0;} break L;
+ } break L;
+ case 5: X="input";id=Id_input; break L;
+ case 9: c=s.charAt(4);
+ if (c=='M') { X="lastMatch";id=Id_lastMatch; }
+ else if (c=='P') { X="lastParen";id=Id_lastParen; }
+ else if (c=='i') { X="multiline";id=Id_multiline; }
+ break L;
+ case 11: X="leftContext";id=Id_leftContext; break L;
+ case 12: X="rightContext";id=Id_rightContext; break L;
+ }
+ if (X!=null && X!=s && !X.equals(s)) id = 0;
+ }
+// #/generated#
+
+ if (id == 0) return super.findInstanceIdInfo(s);
+
+ int attr;
+ switch (id) {
+ case Id_multiline:
+ case Id_STAR:
+ case Id_input:
+ case Id_UNDERSCORE:
+ attr = PERMANENT;
+ break;
+ default:
+ attr = PERMANENT | READONLY;
+ break;
+ }
+
+ return instanceIdInfo(attr, super.getMaxInstanceId() + id);
+ }
+
+// #/string_id_map#
+
+ @Override
+ protected String getInstanceIdName(int id)
+ {
+ int shifted = id - super.getMaxInstanceId();
+ if (1 <= shifted && shifted <= MAX_INSTANCE_ID) {
+ switch (shifted) {
+ case Id_multiline: return "multiline";
+ case Id_STAR: return "$*";
+
+ case Id_input: return "input";
+ case Id_UNDERSCORE: return "$_";
+
+ case Id_lastMatch: return "lastMatch";
+ case Id_AMPERSAND: return "$&";
+
+ case Id_lastParen: return "lastParen";
+ case Id_PLUS: return "$+";
+
+ case Id_leftContext: return "leftContext";
+ case Id_BACK_QUOTE: return "$`";
+
+ case Id_rightContext: return "rightContext";
+ case Id_QUOTE: return "$'";
+ }
+ // Must be one of $1..$9, convert to 0..8
+ int substring_number = shifted - DOLLAR_ID_BASE - 1;
+ char[] buf = { '$', (char)('1' + substring_number) };
+ return new String(buf);
+ }
+ return super.getInstanceIdName(id);
+ }
+
+ @Override
+ protected Object getInstanceIdValue(int id)
+ {
+ int shifted = id - super.getMaxInstanceId();
+ if (1 <= shifted && shifted <= MAX_INSTANCE_ID) {
+ RegExpImpl impl = getImpl();
+ Object stringResult;
+ switch (shifted) {
+ case Id_multiline:
+ case Id_STAR:
+ return ScriptRuntime.wrapBoolean(impl.multiline);
+
+ case Id_input:
+ case Id_UNDERSCORE:
+ stringResult = impl.input;
+ break;
+
+ case Id_lastMatch:
+ case Id_AMPERSAND:
+ stringResult = impl.lastMatch;
+ break;
+
+ case Id_lastParen:
+ case Id_PLUS:
+ stringResult = impl.lastParen;
+ break;
+
+ case Id_leftContext:
+ case Id_BACK_QUOTE:
+ stringResult = impl.leftContext;
+ break;
+
+ case Id_rightContext:
+ case Id_QUOTE:
+ stringResult = impl.rightContext;
+ break;
+
+ default:
+ {
+ // Must be one of $1..$9, convert to 0..8
+ int substring_number = shifted - DOLLAR_ID_BASE - 1;
+ stringResult = impl.getParenSubString(substring_number);
+ break;
+ }
+ }
+ return (stringResult == null) ? "" : stringResult.toString();
+ }
+ return super.getInstanceIdValue(id);
+ }
+
+ @Override
+ protected void setInstanceIdValue(int id, Object value)
+ {
+ int shifted = id - super.getMaxInstanceId();
+ switch (shifted) {
+ case Id_multiline:
+ case Id_STAR:
+ getImpl().multiline = ScriptRuntime.toBoolean(value);
+ return;
+
+ case Id_input:
+ case Id_UNDERSCORE:
+ getImpl().input = ScriptRuntime.toString(value);
+ return;
+ }
+ super.setInstanceIdValue(id, value);
+ }
+
+}
diff --git a/src/org/mozilla/javascript/regexp/RegExpImpl.java b/src/org/mozilla/javascript/regexp/RegExpImpl.java
new file mode 100644
index 0000000..4b0a303
--- /dev/null
+++ b/src/org/mozilla/javascript/regexp/RegExpImpl.java
@@ -0,0 +1,541 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.regexp;
+
+import org.mozilla.javascript.*;
+
+/**
+ *
+ */
+public class RegExpImpl implements RegExpProxy {
+
+ public boolean isRegExp(Scriptable obj) {
+ return obj instanceof NativeRegExp;
+ }
+
+ public Object compileRegExp(Context cx, String source, String flags)
+ {
+ return NativeRegExp.compileRE(cx, source, flags, false);
+ }
+
+ public Scriptable wrapRegExp(Context cx, Scriptable scope,
+ Object compiled)
+ {
+ return new NativeRegExp(scope, compiled);
+ }
+
+ public Object action(Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args,
+ int actionType)
+ {
+ GlobData data = new GlobData();
+ data.mode = actionType;
+
+ switch (actionType) {
+ case RA_MATCH:
+ {
+ Object rval;
+ data.optarg = 1;
+ rval = matchOrReplace(cx, scope, thisObj, args,
+ this, data, false);
+ return data.arrayobj == null ? rval : data.arrayobj;
+ }
+
+ case RA_SEARCH:
+ data.optarg = 1;
+ return matchOrReplace(cx, scope, thisObj, args,
+ this, data, false);
+
+ case RA_REPLACE:
+ {
+ Object arg1 = args.length < 2 ? Undefined.instance : args[1];
+ String repstr = null;
+ Function lambda = null;
+ if (arg1 instanceof Function) {
+ lambda = (Function) arg1;
+ } else {
+ repstr = ScriptRuntime.toString(arg1);
+ }
+
+ data.optarg = 2;
+ data.lambda = lambda;
+ data.repstr = repstr;
+ data.dollar = repstr == null ? -1 : repstr.indexOf('$');
+ data.charBuf = null;
+ data.leftIndex = 0;
+ Object val = matchOrReplace(cx, scope, thisObj, args,
+ this, data, true);
+ SubString rc = this.rightContext;
+
+ if (data.charBuf == null) {
+ if (data.global || val == null
+ || !val.equals(Boolean.TRUE))
+ {
+ /* Didn't match even once. */
+ return data.str;
+ }
+ SubString lc = this.leftContext;
+ replace_glob(data, cx, scope, this, lc.index, lc.length);
+ }
+ data.charBuf.append(rc.charArray, rc.index, rc.length);
+ return data.charBuf.toString();
+ }
+
+ default:
+ throw Kit.codeBug();
+ }
+ }
+
+ /**
+ * Analog of C match_or_replace.
+ */
+ private static Object matchOrReplace(Context cx, Scriptable scope,
+ Scriptable thisObj, Object[] args,
+ RegExpImpl reImpl,
+ GlobData data, boolean forceFlat)
+ {
+ NativeRegExp re;
+
+ String str = ScriptRuntime.toString(thisObj);
+ data.str = str;
+ Scriptable topScope = ScriptableObject.getTopLevelScope(scope);
+
+ if (args.length == 0) {
+ Object compiled = NativeRegExp.compileRE(cx, "", "", false);
+ re = new NativeRegExp(topScope, compiled);
+ } else if (args[0] instanceof NativeRegExp) {
+ re = (NativeRegExp) args[0];
+ } else {
+ String src = ScriptRuntime.toString(args[0]);
+ String opt;
+ if (data.optarg < args.length) {
+ args[0] = src;
+ opt = ScriptRuntime.toString(args[data.optarg]);
+ } else {
+ opt = null;
+ }
+ Object compiled = NativeRegExp.compileRE(cx, src, opt, forceFlat);
+ re = new NativeRegExp(topScope, compiled);
+ }
+ data.regexp = re;
+
+ data.global = (re.getFlags() & NativeRegExp.JSREG_GLOB) != 0;
+ int[] indexp = { 0 };
+ Object result = null;
+ if (data.mode == RA_SEARCH) {
+ result = re.executeRegExp(cx, scope, reImpl,
+ str, indexp, NativeRegExp.TEST);
+ if (result != null && result.equals(Boolean.TRUE))
+ result = new Integer(reImpl.leftContext.length);
+ else
+ result = new Integer(-1);
+ } else if (data.global) {
+ re.lastIndex = 0;
+ for (int count = 0; indexp[0] <= str.length(); count++) {
+ result = re.executeRegExp(cx, scope, reImpl,
+ str, indexp, NativeRegExp.TEST);
+ if (result == null || !result.equals(Boolean.TRUE))
+ break;
+ if (data.mode == RA_MATCH) {
+ match_glob(data, cx, scope, count, reImpl);
+ } else {
+ if (data.mode != RA_REPLACE) Kit.codeBug();
+ SubString lastMatch = reImpl.lastMatch;
+ int leftIndex = data.leftIndex;
+ int leftlen = lastMatch.index - leftIndex;
+ data.leftIndex = lastMatch.index + lastMatch.length;
+ replace_glob(data, cx, scope, reImpl, leftIndex, leftlen);
+ }
+ if (reImpl.lastMatch.length == 0) {
+ if (indexp[0] == str.length())
+ break;
+ indexp[0]++;
+ }
+ }
+ } else {
+ result = re.executeRegExp(cx, scope, reImpl, str, indexp,
+ ((data.mode == RA_REPLACE)
+ ? NativeRegExp.TEST
+ : NativeRegExp.MATCH));
+ }
+
+ return result;
+ }
+
+
+
+ public int find_split(Context cx, Scriptable scope, String target,
+ String separator, Scriptable reObj,
+ int[] ip, int[] matchlen,
+ boolean[] matched, String[][] parensp)
+ {
+ int i = ip[0];
+ int length = target.length();
+ int result;
+
+ int version = cx.getLanguageVersion();
+ NativeRegExp re = (NativeRegExp) reObj;
+ again:
+ while (true) { // imitating C label
+ /* JS1.2 deviated from Perl by never matching at end of string. */
+ int ipsave = ip[0]; // reuse ip to save object creation
+ ip[0] = i;
+ Object ret = re.executeRegExp(cx, scope, this, target, ip,
+ NativeRegExp.TEST);
+ if (ret != Boolean.TRUE) {
+ // Mismatch: ensure our caller advances i past end of string.
+ ip[0] = ipsave;
+ matchlen[0] = 1;
+ matched[0] = false;
+ return length;
+ }
+ i = ip[0];
+ ip[0] = ipsave;
+ matched[0] = true;
+
+ SubString sep = this.lastMatch;
+ matchlen[0] = sep.length;
+ if (matchlen[0] == 0) {
+ /*
+ * Empty string match: never split on an empty
+ * match at the start of a find_split cycle. Same
+ * rule as for an empty global match in
+ * match_or_replace.
+ */
+ if (i == ip[0]) {
+ /*
+ * "Bump-along" to avoid sticking at an empty
+ * match, but don't bump past end of string --
+ * our caller must do that by adding
+ * sep->length to our return value.
+ */
+ if (i == length) {
+ if (version == Context.VERSION_1_2) {
+ matchlen[0] = 1;
+ result = i;
+ }
+ else
+ result = -1;
+ break;
+ }
+ i++;
+ continue again; // imitating C goto
+ }
+ }
+ // PR_ASSERT((size_t)i >= sep->length);
+ result = i - matchlen[0];
+ break;
+ }
+ int size = (parens == null) ? 0 : parens.length;
+ parensp[0] = new String[size];
+ for (int num = 0; num < size; num++) {
+ SubString parsub = getParenSubString(num);
+ parensp[0][num] = parsub.toString();
+ }
+ return result;
+ }
+
+ /**
+ * Analog of REGEXP_PAREN_SUBSTRING in C jsregexp.h.
+ * Assumes zero-based; i.e., for $3, i==2
+ */
+ SubString getParenSubString(int i)
+ {
+ if (parens != null && i < parens.length) {
+ SubString parsub = parens[i];
+ if (parsub != null) {
+ return parsub;
+ }
+ }
+ return SubString.emptySubString;
+ }
+
+ /*
+ * Analog of match_glob() in jsstr.c
+ */
+ private static void match_glob(GlobData mdata, Context cx,
+ Scriptable scope, int count,
+ RegExpImpl reImpl)
+ {
+ if (mdata.arrayobj == null) {
+ Scriptable s = ScriptableObject.getTopLevelScope(scope);
+ mdata.arrayobj = ScriptRuntime.newObject(cx, s, "Array", null);
+ }
+ SubString matchsub = reImpl.lastMatch;
+ String matchstr = matchsub.toString();
+ mdata.arrayobj.put(count, mdata.arrayobj, matchstr);
+ }
+
+ /*
+ * Analog of replace_glob() in jsstr.c
+ */
+ private static void replace_glob(GlobData rdata, Context cx,
+ Scriptable scope, RegExpImpl reImpl,
+ int leftIndex, int leftlen)
+ {
+ int replen;
+ String lambdaStr;
+ if (rdata.lambda != null) {
+ // invoke lambda function with args lastMatch, $1, $2, ... $n,
+ // leftContext.length, whole string.
+ SubString[] parens = reImpl.parens;
+ int parenCount = (parens == null) ? 0 : parens.length;
+ Object[] args = new Object[parenCount + 3];
+ args[0] = reImpl.lastMatch.toString();
+ for (int i=0; i < parenCount; i++) {
+ SubString sub = parens[i];
+ if (sub != null) {
+ args[i+1] = sub.toString();
+ } else {
+ args[i+1] = Undefined.instance;
+ }
+ }
+ args[parenCount+1] = new Integer(reImpl.leftContext.length);
+ args[parenCount+2] = rdata.str;
+ // This is a hack to prevent expose of reImpl data to
+ // JS function which can run new regexps modifing
+ // regexp that are used later by the engine.
+ // TODO: redesign is necessary
+ if (reImpl != ScriptRuntime.getRegExpProxy(cx)) Kit.codeBug();
+ RegExpImpl re2 = new RegExpImpl();
+ re2.multiline = reImpl.multiline;
+ re2.input = reImpl.input;
+ ScriptRuntime.setRegExpProxy(cx, re2);
+ try {
+ Scriptable parent = ScriptableObject.getTopLevelScope(scope);
+ Object result = rdata.lambda.call(cx, parent, parent, args);
+ lambdaStr = ScriptRuntime.toString(result);
+ } finally {
+ ScriptRuntime.setRegExpProxy(cx, reImpl);
+ }
+ replen = lambdaStr.length();
+ } else {
+ lambdaStr = null;
+ replen = rdata.repstr.length();
+ if (rdata.dollar >= 0) {
+ int[] skip = new int[1];
+ int dp = rdata.dollar;
+ do {
+ SubString sub = interpretDollar(cx, reImpl, rdata.repstr,
+ dp, skip);
+ if (sub != null) {
+ replen += sub.length - skip[0];
+ dp += skip[0];
+ } else {
+ ++dp;
+ }
+ dp = rdata.repstr.indexOf('$', dp);
+ } while (dp >= 0);
+ }
+ }
+
+ int growth = leftlen + replen + reImpl.rightContext.length;
+ StringBuffer charBuf = rdata.charBuf;
+ if (charBuf == null) {
+ charBuf = new StringBuffer(growth);
+ rdata.charBuf = charBuf;
+ } else {
+ charBuf.ensureCapacity(rdata.charBuf.length() + growth);
+ }
+
+ charBuf.append(reImpl.leftContext.charArray, leftIndex, leftlen);
+ if (rdata.lambda != null) {
+ charBuf.append(lambdaStr);
+ } else {
+ do_replace(rdata, cx, reImpl);
+ }
+ }
+
+ private static SubString interpretDollar(Context cx, RegExpImpl res,
+ String da, int dp, int[] skip)
+ {
+ char dc;
+ int num, tmp;
+
+ if (da.charAt(dp) != '$') Kit.codeBug();
+
+ /* Allow a real backslash (literal "\\") to escape "$1" etc. */
+ int version = cx.getLanguageVersion();
+ if (version != Context.VERSION_DEFAULT
+ && version <= Context.VERSION_1_4)
+ {
+ if (dp > 0 && da.charAt(dp - 1) == '\\')
+ return null;
+ }
+ int daL = da.length();
+ if (dp + 1 >= daL)
+ return null;
+ /* Interpret all Perl match-induced dollar variables. */
+ dc = da.charAt(dp + 1);
+ if (NativeRegExp.isDigit(dc)) {
+ int cp;
+ if (version != Context.VERSION_DEFAULT
+ && version <= Context.VERSION_1_4)
+ {
+ if (dc == '0')
+ return null;
+ /* Check for overflow to avoid gobbling arbitrary decimal digits. */
+ num = 0;
+ cp = dp;
+ while (++cp < daL && NativeRegExp.isDigit(dc = da.charAt(cp)))
+ {
+ tmp = 10 * num + (dc - '0');
+ if (tmp < num)
+ break;
+ num = tmp;
+ }
+ }
+ else { /* ECMA 3, 1-9 or 01-99 */
+ int parenCount = (res.parens == null) ? 0 : res.parens.length;
+ num = dc - '0';
+ if (num > parenCount)
+ return null;
+ cp = dp + 2;
+ if ((dp + 2) < daL) {
+ dc = da.charAt(dp + 2);
+ if (NativeRegExp.isDigit(dc)) {
+ tmp = 10 * num + (dc - '0');
+ if (tmp <= parenCount) {
+ cp++;
+ num = tmp;
+ }
+ }
+ }
+ if (num == 0) return null; /* $0 or $00 is not valid */
+ }
+ /* Adjust num from 1 $n-origin to 0 array-index-origin. */
+ num--;
+ skip[0] = cp - dp;
+ return res.getParenSubString(num);
+ }
+
+ skip[0] = 2;
+ switch (dc) {
+ case '$':
+ return new SubString("$");
+ case '&':
+ return res.lastMatch;
+ case '+':
+ return res.lastParen;
+ case '`':
+ if (version == Context.VERSION_1_2) {
+ /*
+ * JS1.2 imitated the Perl4 bug where left context at each step
+ * in an iterative use of a global regexp started from last match,
+ * not from the start of the target string. But Perl4 does start
+ * $` at the beginning of the target string when it is used in a
+ * substitution, so we emulate that special case here.
+ */
+ res.leftContext.index = 0;
+ res.leftContext.length = res.lastMatch.index;
+ }
+ return res.leftContext;
+ case '\'':
+ return res.rightContext;
+ }
+ return null;
+ }
+
+ /**
+ * Analog of do_replace in jsstr.c
+ */
+ private static void do_replace(GlobData rdata, Context cx,
+ RegExpImpl regExpImpl)
+ {
+ StringBuffer charBuf = rdata.charBuf;
+ int cp = 0;
+ String da = rdata.repstr;
+ int dp = rdata.dollar;
+ if (dp != -1) {
+ int[] skip = new int[1];
+ do {
+ int len = dp - cp;
+ charBuf.append(da.substring(cp, dp));
+ cp = dp;
+ SubString sub = interpretDollar(cx, regExpImpl, da,
+ dp, skip);
+ if (sub != null) {
+ len = sub.length;
+ if (len > 0) {
+ charBuf.append(sub.charArray, sub.index, len);
+ }
+ cp += skip[0];
+ dp += skip[0];
+ } else {
+ ++dp;
+ }
+ dp = da.indexOf('$', dp);
+ } while (dp >= 0);
+ }
+ int daL = da.length();
+ if (daL > cp) {
+ charBuf.append(da.substring(cp, daL));
+ }
+ }
+
+ String input; /* input string to match (perl $_, GC root) */
+ boolean multiline; /* whether input contains newlines (perl $*) */
+ SubString[] parens; /* Vector of SubString; last set of parens
+ matched (perl $1, $2) */
+ SubString lastMatch; /* last string matched (perl $&) */
+ SubString lastParen; /* last paren matched (perl $+) */
+ SubString leftContext; /* input to left of last match (perl $`) */
+ SubString rightContext; /* input to right of last match (perl $') */
+}
+
+
+final class GlobData
+{
+ int mode; /* input: return index, match object, or void */
+ int optarg; /* input: index of optional flags argument */
+ boolean global; /* output: whether regexp was global */
+ String str; /* output: 'this' parameter object as string */
+ NativeRegExp regexp;/* output: regexp parameter object private data */
+
+ // match-specific data
+
+ Scriptable arrayobj;
+
+ // replace-specific data
+
+ Function lambda; /* replacement function object or null */
+ String repstr; /* replacement string */
+ int dollar = -1; /* -1 or index of first $ in repstr */
+ StringBuffer charBuf; /* result characters, null initially */
+ int leftIndex; /* leftContext index, always 0 for JS1.2 */
+}
diff --git a/src/org/mozilla/javascript/regexp/SubString.java b/src/org/mozilla/javascript/regexp/SubString.java
new file mode 100644
index 0000000..b5383c9
--- /dev/null
+++ b/src/org/mozilla/javascript/regexp/SubString.java
@@ -0,0 +1,76 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1998.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-1999
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.regexp;
+
+class SubString {
+
+ public SubString()
+ {
+ }
+
+ public SubString(String str)
+ {
+ index = 0;
+ charArray = str.toCharArray();
+ length = str.length();
+ }
+
+ public SubString(char[] source, int start, int len)
+ {
+ // there must be a better way of doing this??
+ index = 0;
+ length = len;
+ charArray = new char[len];
+ for (int j = 0; j < len; j++)
+ charArray[j] = source[start + j];
+ }
+
+ @Override
+ public String toString() {
+ return charArray == null
+ ? ""
+ : new String(charArray, index, length);
+ }
+
+ static final SubString emptySubString = new SubString();
+
+ char[] charArray;
+ int index;
+ int length;
+}
+
diff --git a/src/org/mozilla/javascript/resources/Messages.properties b/src/org/mozilla/javascript/resources/Messages.properties
new file mode 100644
index 0000000..4f85e8a
--- /dev/null
+++ b/src/org/mozilla/javascript/resources/Messages.properties
@@ -0,0 +1,781 @@
+#
+# Default JavaScript messages file.
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Rhino code, released
+# May 6, 1999.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 1997-1999
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Norris Boyd
+# Bob Jervis
+#
+# Alternatively, the contents of this file may be used under the terms of
+# the GNU General Public License Version 2 or later (the "GPL"), in which
+# case the provisions of the GPL are applicable instead of those above. If
+# you wish to allow use of your version of this file only under the terms of
+# the GPL and not to allow others to use your version of this file under the
+# MPL, indicate your decision by deleting the provisions above and replacing
+# them with the notice and other provisions required by the GPL. If you do
+# not delete the provisions above, a recipient may use your version of this
+# file under either the MPL or the GPL.
+#
+# ***** END LICENSE BLOCK *****
+
+# This is replaced during jar assembly from property string
+# and should not be translated
+implementation.version = @IMPLEMENTATION.VERSION@
+
+#
+# To add JavaScript error messages for a particular locale, create a
+# new Messages_[locale].properties file, where [locale] is the Java
+# string abbreviation for that locale. For example, JavaScript
+# messages for the Polish locale should be located in
+# Messages_pl.properties, and messages for the Italian Swiss locale
+# should be located in Messages_it_CH.properties. Message properties
+# files should be accessible through the classpath under
+# org.mozilla.javascript.resources
+#
+# See:
+# java.util.ResourceBundle
+# java.text.MessageFormat
+#
+
+# SomeJavaClassWhereUsed
+
+# Codegen
+msg.dup.parms =\
+ Duplicate parameter name "{0}".
+
+msg.too.big.jump =\
+ Program too complex: too big jump offset.
+
+msg.too.big.index =\
+ Program too complex: internal index exceeds 64K limit.
+
+msg.while.compiling.fn =\
+ Encountered code generation error while compiling function "{0}": {1}
+
+msg.while.compiling.script =\
+ Encountered code generation error while compiling script: {0}
+
+# Context
+msg.ctor.not.found =\
+ Constructor for "{0}" not found.
+
+msg.not.ctor =\
+ "{0}" is not a constructor.
+
+# FunctionObject
+msg.varargs.ctor =\
+ Method or constructor "{0}" must be static with the signature \
+ "(Context cx, Object[] args, Function ctorObj, boolean inNewExpr)" \
+ to define a variable arguments constructor.
+
+msg.varargs.fun =\
+ Method "{0}" must be static with the signature \
+ "(Context cx, Scriptable thisObj, Object[] args, Function funObj)" \
+ to define a variable arguments function.
+
+msg.incompat.call =\
+ Method "{0}" called on incompatible object.
+
+msg.bad.parms =\
+ Unsupported parameter type "{0}" in method "{1}".
+
+msg.bad.method.return =\
+ Unsupported return type "{0}" in method "{1}".
+
+msg.bad.ctor.return =\
+ Construction of objects of type "{0}" is not supported.
+
+msg.no.overload =\
+ Method "{0}" occurs multiple times in class "{1}".
+
+msg.method.not.found =\
+ Method "{0}" not found in "{1}".
+
+# IRFactory
+
+msg.bad.for.in.lhs =\
+ Invalid left-hand side of for..in loop.
+
+msg.mult.index =\
+ Only one variable allowed in for..in loop.
+
+msg.bad.for.in.destruct =\
+ Left hand side of for..in loop must be an array of length 2 to accept \
+ key/value pair.
+
+msg.cant.convert =\
+ Can''t convert to type "{0}".
+
+msg.bad.assign.left =\
+ Invalid assignment left-hand side.
+
+msg.bad.decr =\
+ Invalid decrement operand.
+
+msg.bad.incr =\
+ Invalid increment operand.
+
+msg.bad.yield =\
+ yield must be in a function.
+
+msg.yield.parenthesized =\
+ yield expression must be parenthesized.
+
+# NativeGlobal
+msg.cant.call.indirect =\
+ Function "{0}" must be called directly, and not by way of a \
+ function of another name.
+
+msg.eval.nonstring =\
+ Calling eval() with anything other than a primitive string value will \
+ simply return the value. Is this what you intended?
+
+msg.eval.nonstring.strict =\
+ Calling eval() with anything other than a primitive string value is not \
+ allowed in strict mode.
+
+msg.bad.destruct.op =\
+ Invalid destructuring assignment operator
+
+# NativeCall
+msg.only.from.new =\
+ "{0}" may only be invoked from a "new" expression.
+
+msg.deprec.ctor =\
+ The "{0}" constructor is deprecated.
+
+# NativeFunction
+msg.no.function.ref.found =\
+ no source found to decompile function reference {0}
+
+msg.arg.isnt.array =\
+ second argument to Function.prototype.apply must be an array
+
+# NativeGlobal
+msg.bad.esc.mask =\
+ invalid string escape mask
+
+# NativeJavaClass
+msg.cant.instantiate =\
+ error instantiating ({0}): class {1} is interface or abstract
+
+msg.bad.ctor.sig =\
+ Found constructor with wrong signature: \
+ {0} calling {1} with signature {2}
+
+msg.not.java.obj =\
+ Expected argument to getClass() to be a Java object.
+
+msg.no.java.ctor =\
+ Java constructor for "{0}" with arguments "{1}" not found.
+
+# NativeJavaMethod
+msg.method.ambiguous =\
+ The choice of Java method {0}.{1} matching JavaScript argument types ({2}) is ambiguous; \
+ candidate methods are: {3}
+
+msg.constructor.ambiguous =\
+ The choice of Java constructor {0} matching JavaScript argument types ({1}) is ambiguous; \
+ candidate constructors are: {2}
+
+# NativeJavaObject
+msg.conversion.not.allowed =\
+ Cannot convert {0} to {1}
+
+msg.no.empty.interface.conversion =\
+ Cannot convert {0} to interface {1} with no methods
+
+msg.no.function.interface.conversion =\
+ Cannot convert function {0} to interface since it contains methods with \
+ different signatures
+
+# NativeJavaPackage
+msg.not.classloader =\
+ Constructor for "Packages" expects argument of type java.lang.Classloader
+
+# NativeRegExp
+msg.bad.quant =\
+ Invalid quantifier {0}
+
+msg.overlarge.backref =\
+ Overly large back reference {0}
+
+msg.overlarge.min =\
+ Overly large minimum {0}
+
+msg.overlarge.max =\
+ Overly large maximum {0}
+
+msg.zero.quant =\
+ Zero quantifier {0}
+
+msg.max.lt.min =\
+ Maximum {0} less than minimum
+
+msg.unterm.quant =\
+ Unterminated quantifier {0}
+
+msg.unterm.paren =\
+ Unterminated parenthetical {0}
+
+msg.unterm.class =\
+ Unterminated character class {0}
+
+msg.bad.range =\
+ Invalid range in character class.
+
+msg.trail.backslash =\
+ Trailing \\ in regular expression.
+
+msg.re.unmatched.right.paren =\
+ unmatched ) in regular expression.
+
+msg.no.regexp =\
+ Regular expressions are not available.
+
+msg.bad.backref =\
+ back-reference exceeds number of capturing parentheses.
+
+msg.bad.regexp.compile =\
+ Only one argument may be specified if the first argument to \
+ RegExp.prototype.compile is a RegExp object.
+
+# Parser
+msg.got.syntax.errors = \
+ Compilation produced {0} syntax errors.
+
+msg.var.redecl =\
+ TypeError: redeclaration of var {0}.
+
+msg.const.redecl =\
+ TypeError: redeclaration of const {0}.
+
+msg.let.redecl =\
+ TypeError: redeclaration of variable {0}.
+
+msg.parm.redecl =\
+ TypeError: redeclaration of formal parameter {0}.
+
+msg.fn.redecl =\
+ TypeError: redeclaration of function {0}.
+
+msg.let.decl.not.in.block =\
+ SyntaxError: let declaration not directly within block
+
+# NodeTransformer
+msg.dup.label =\
+ duplicated label
+
+msg.undef.label =\
+ undefined label
+
+msg.bad.break =\
+ unlabelled break must be inside loop or switch
+
+msg.continue.outside =\
+ continue must be inside loop
+
+msg.continue.nonloop =\
+ continue can only use labeles of iteration statements
+
+msg.bad.throw.eol =\
+ Line terminator is not allowed between the throw keyword and throw \
+ expression.
+
+msg.no.paren.parms =\
+ missing ( before function parameters.
+
+msg.no.parm =\
+ missing formal parameter
+
+msg.no.paren.after.parms =\
+ missing ) after formal parameters
+
+msg.no.brace.body =\
+ missing '{' before function body
+
+msg.no.brace.after.body =\
+ missing } after function body
+
+msg.no.paren.cond =\
+ missing ( before condition
+
+msg.no.paren.after.cond =\
+ missing ) after condition
+
+msg.no.semi.stmt =\
+ missing ; before statement
+
+msg.no.name.after.dot =\
+ missing name after . operator
+
+msg.no.name.after.coloncolon =\
+ missing name after :: operator
+
+msg.no.name.after.dotdot =\
+ missing name after .. operator
+
+msg.no.name.after.xmlAttr =\
+ missing name after .@
+
+msg.no.bracket.index =\
+ missing ] in index expression
+
+msg.no.paren.switch =\
+ missing ( before switch expression
+
+msg.no.paren.after.switch =\
+ missing ) after switch expression
+
+msg.no.brace.switch =\
+ missing '{' before switch body
+
+msg.bad.switch =\
+ invalid switch statement
+
+msg.no.colon.case =\
+ missing : after case expression
+
+msg.double.switch.default =\
+ double default label in the switch statement
+
+msg.no.while.do =\
+ missing while after do-loop body
+
+msg.no.paren.for =\
+ missing ( after for
+
+msg.no.semi.for =\
+ missing ; after for-loop initializer
+
+msg.no.semi.for.cond =\
+ missing ; after for-loop condition
+
+msg.in.after.for.name =\
+ missing in after for
+
+msg.no.paren.for.ctrl =\
+ missing ) after for-loop control
+
+msg.no.paren.with =\
+ missing ( before with-statement object
+
+msg.no.paren.after.with =\
+ missing ) after with-statement object
+
+msg.no.paren.after.let =\
+ missing ( after let
+
+msg.no.paren.let =\
+ missing ) after variable list
+
+msg.no.curly.let =\
+ missing } after let statement
+
+msg.bad.return =\
+ invalid return
+
+msg.no.brace.block =\
+ missing } in compound statement
+
+msg.bad.label =\
+ invalid label
+
+msg.bad.var =\
+ missing variable name
+
+msg.bad.var.init =\
+ invalid variable initialization
+
+msg.no.colon.cond =\
+ missing : in conditional expression
+
+msg.no.paren.arg =\
+ missing ) after argument list
+
+msg.no.bracket.arg =\
+ missing ] after element list
+
+msg.bad.prop =\
+ invalid property id
+
+msg.no.colon.prop =\
+ missing : after property id
+
+msg.no.brace.prop =\
+ missing } after property list
+
+msg.no.paren =\
+ missing ) in parenthetical
+
+msg.reserved.id =\
+ identifier is a reserved word
+
+msg.no.paren.catch =\
+ missing ( before catch-block condition
+
+msg.bad.catchcond =\
+ invalid catch block condition
+
+msg.catch.unreachable =\
+ any catch clauses following an unqualified catch are unreachable
+
+msg.no.brace.try =\
+ missing '{' before try block
+
+msg.no.brace.catchblock =\
+ missing '{' before catch-block body
+
+msg.try.no.catchfinally =\
+ ''try'' without ''catch'' or ''finally''
+
+msg.no.return.value =\
+ function {0} does not always return a value
+
+msg.anon.no.return.value =\
+ anonymous function does not always return a value
+
+msg.return.inconsistent =\
+ return statement is inconsistent with previous usage
+
+msg.generator.returns =\
+ TypeError: generator function {0} returns a value
+
+msg.anon.generator.returns =\
+ TypeError: anonymous generator function returns a value
+
+msg.syntax =\
+ syntax error
+
+msg.unexpected.eof =\
+ Unexpected end of file
+
+msg.XML.bad.form =\
+ illegally formed XML syntax
+
+msg.XML.not.available =\
+ XML runtime not available
+
+msg.too.deep.parser.recursion =\
+ Too deep recursion while parsing
+
+msg.no.side.effects =\
+ Code has no side effects
+
+msg.extra.trailing.comma =\
+ Trailing comma is not legal in an ECMA-262 object initializer
+
+msg.equal.as.assign =\
+ Test for equality (==) mistyped as assignment (=)?
+
+msg.var.hides.arg =\
+ Variable {0} hides argument
+
+msg.destruct.assign.no.init =\
+ Missing = in destructuring declaration
+
+# ScriptRuntime
+msg.no.properties =\
+ {0} has no properties.
+
+msg.invalid.iterator =\
+ Invalid iterator value
+
+msg.iterator.primitive =\
+ __iterator__ returned a primitive value
+
+msg.assn.create.strict =\
+ Assignment to undeclared variable {0}
+
+msg.ref.undefined.prop =\
+ Reference to undefined property "{0}"
+
+msg.prop.not.found =\
+ Property {0} not found.
+
+msg.set.prop.no.setter =\
+ Cannot set property {0} that has only a getter.
+
+msg.invalid.type =\
+ Invalid JavaScript value of type {0}
+
+msg.primitive.expected =\
+ Primitive type expected (had {0} instead)
+
+msg.namespace.expected =\
+ Namespace object expected to left of :: (found {0} instead)
+
+msg.null.to.object =\
+ Cannot convert null to an object.
+
+msg.undef.to.object =\
+ Cannot convert undefined to an object.
+
+msg.cyclic.value =\
+ Cyclic {0} value not allowed.
+
+msg.is.not.defined =\
+ "{0}" is not defined.
+
+msg.undef.prop.read =\
+ Cannot read property "{1}" from {0}
+
+msg.undef.prop.write =\
+ Cannot set property "{1}" of {0} to "{2}"
+
+msg.undef.prop.delete =\
+ Cannot delete property "{1}" of {0}
+
+msg.undef.method.call =\
+ Cannot call method "{1}" of {0}
+
+msg.undef.with =\
+ Cannot apply "with" to {0}
+
+msg.isnt.function =\
+ {0} is not a function, it is {1}.
+
+msg.isnt.function.in =\
+ Cannot call property {0} in object {1}. It is not a function, it is "{2}".
+
+msg.function.not.found =\
+ Cannot find function {0}.
+
+msg.function.not.found.in =\
+ Cannot find function {0} in object {1}.
+
+msg.isnt.xml.object =\
+ {0} is not an xml object.
+
+msg.no.ref.to.get =\
+ {0} is not a reference to read reference value.
+
+msg.no.ref.to.set =\
+ {0} is not a reference to set reference value to {1}.
+
+msg.no.ref.from.function =\
+ Function {0} can not be used as the left-hand side of assignment \
+ or as an operand of ++ or -- operator.
+
+msg.bad.default.value =\
+ Object''s getDefaultValue() method returned an object.
+
+msg.instanceof.not.object = \
+ Can''t use instanceof on a non-object.
+
+msg.instanceof.bad.prototype = \
+ ''prototype'' property of {0} is not an object.
+
+msg.bad.radix = \
+ illegal radix {0}.
+
+# ScriptableObject
+msg.default.value =\
+ Cannot find default value for object.
+
+msg.zero.arg.ctor =\
+ Cannot load class "{0}" which has no zero-parameter constructor.
+
+duplicate.defineClass.name =\
+ Invalid method "{0}": name "{1}" is already in use.
+
+msg.ctor.multiple.parms =\
+ Can''t define constructor or class {0} since more than one \
+ constructor has multiple parameters.
+
+msg.extend.scriptable =\
+ {0} must extend ScriptableObject in order to define property {1}.
+
+msg.bad.getter.parms =\
+ In order to define a property, getter {0} must have zero parameters \
+ or a single ScriptableObject parameter.
+
+msg.obj.getter.parms =\
+ Expected static or delegated getter {0} to take a ScriptableObject parameter.
+
+msg.getter.static =\
+ Getter and setter must both be static or neither be static.
+
+msg.setter.return =\
+ Setter must have void return type: {0}
+
+msg.setter2.parms =\
+ Two-parameter setter must take a ScriptableObject as its first parameter.
+
+msg.setter1.parms =\
+ Expected single parameter setter for {0}
+
+msg.setter2.expected =\
+ Expected static or delegated setter {0} to take two parameters.
+
+msg.setter.parms =\
+ Expected either one or two parameters for setter.
+
+msg.setter.bad.type =\
+ Unsupported parameter type "{0}" in setter "{1}".
+
+msg.add.sealed =\
+ Cannot add a property to a sealed object: {0}.
+
+msg.remove.sealed =\
+ Cannot remove a property from a sealed object: {0}.
+
+msg.modify.sealed =\
+ Cannot modify a property of a sealed object: {0}.
+
+msg.modify.readonly =\
+ Cannot modify readonly property: {0}.
+
+# TokenStream
+msg.missing.exponent =\
+ missing exponent
+
+msg.caught.nfe =\
+ number format error
+
+msg.unterminated.string.lit =\
+ unterminated string literal
+
+msg.unterminated.comment =\
+ unterminated comment
+
+msg.unterminated.re.lit =\
+ unterminated regular expression literal
+
+msg.invalid.re.flag =\
+ invalid flag after regular expression
+
+msg.no.re.input.for =\
+ no input for {0}
+
+msg.illegal.character =\
+ illegal character
+
+msg.invalid.escape =\
+ invalid Unicode escape sequence
+
+msg.bad.namespace =\
+ not a valid default namespace statement. \
+ Syntax is: default xml namespace = EXPRESSION;
+
+# TokensStream warnings
+msg.bad.octal.literal =\
+ illegal octal literal digit {0}; interpreting it as a decimal digit
+
+msg.reserved.keyword =\
+ illegal usage of future reserved keyword {0}; interpreting it as ordinary identifier
+
+# LiveConnect errors
+msg.java.internal.field.type =\
+ Internal error: type conversion of {0} to assign to {1} on {2} failed.
+
+msg.java.conversion.implicit_method =\
+ Can''t find converter method "{0}" on class {1}.
+
+msg.java.method.assign =\
+ Java method "{0}" cannot be assigned to.
+
+msg.java.internal.private =\
+ Internal error: attempt to access private/protected field "{0}".
+
+msg.java.no_such_method =\
+ Can''t find method {0}.
+
+msg.script.is.not.constructor =\
+ Script objects are not constructors.
+
+msg.nonjava.method =\
+ Java method "{0}" was invoked with {1} as "this" value that can not be converted to Java type {2}.
+
+msg.java.member.not.found =\
+ Java class "{0}" has no public instance field or method named "{1}".
+
+msg.java.array.index.out.of.bounds =\
+ Array index {0} is out of bounds [0..{1}].
+
+msg.java.array.member.not.found =\
+ Java arrays have no public instance fields or methods named "{0}".
+
+msg.pkg.int =\
+ Java package names may not be numbers.
+
+msg.access.prohibited =\
+ Access to Java class "{0}" is prohibited.
+
+# ImporterTopLevel
+msg.ambig.import =\
+ Ambiguous import: "{0}" and and "{1}".
+
+msg.not.pkg =\
+ Function importPackage must be called with a package; had "{0}" instead.
+
+msg.not.class =\
+ Function importClass must be called with a class; had "{0}" instead.
+
+msg.not.class.not.pkg =\
+ "{0}" is neither a class nor a package.
+
+msg.prop.defined =\
+ Cannot import "{0}" since a property by that name is already defined.
+
+#JavaAdapter
+msg.adapter.zero.args =\
+ JavaAdapter requires at least one argument.
+
+msg.not.java.class.arg = \
+Argument {0} is not Java class: {1}.
+
+#JavaAdapter
+msg.only.one.super = \
+Only one class may be extended by a JavaAdapter. Had {0} and {1}.
+
+
+# Arrays
+msg.arraylength.bad =\
+ Inappropriate array length.
+
+# Arrays
+msg.arraylength.too.big =\
+ Array length {0} exceeds supported capacity limit.
+
+# URI
+msg.bad.uri =\
+ Malformed URI sequence.
+
+# Number
+msg.bad.precision =\
+ Precision {0} out of range.
+
+# NativeGenerator
+msg.send.newborn =\
+ Attempt to send value to newborn generator
+
+msg.already.exec.gen =\
+ Already executing generator
+
+msg.StopIteration.invalid =\
+ StopIteration may not be changed to an arbitrary object.
+
+# Interpreter
+msg.yield.closing =\
+ Yield from closing generator
diff --git a/src/org/mozilla/javascript/resources/Messages_fr.properties b/src/org/mozilla/javascript/resources/Messages_fr.properties
new file mode 100644
index 0000000..fc87c97
--- /dev/null
+++ b/src/org/mozilla/javascript/resources/Messages_fr.properties
@@ -0,0 +1,329 @@
+#
+# French JavaScript messages file.
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Aviva Inc. code, released
+# March 5, 2004.
+#
+# The Initial Developer of the Original Code is
+# Aviva Inc.
+# Portions created by the Initial Developer are Copyright (C) 2004
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Eugene Aresteanu
+#
+# Alternatively, the contents of this file may be used under the terms of
+# the GNU General Public License Version 2 or later (the "GPL"), in which
+# case the provisions of the GPL are applicable instead of those above. If
+# you wish to allow use of your version of this file only under the terms of
+# the GPL and not to allow others to use your version of this file under the
+# MPL, indicate your decision by deleting the provisions above and replacing
+# them with the notice and other provisions required by the GPL. If you do
+# not delete the provisions above, a recipient may use your version of this
+# file under either the MPL or the GPL.
+#
+# ***** END LICENSE BLOCK *****
+
+msg.dup.parms =\
+ Le nom de param\u00E8tre "{0}" existe d\u00E9j\u00E0.
+msg.too.big.jump =\
+ Programme trop complexe�: d\u00E9calage de saut trop important
+msg.too.big.index =\
+ Programme trop complexe�: l''indice interne d\u00E9passe la limite de 64�ko
+msg.ctor.not.found =\
+ Le constructeur de "{0}" est introuvable
+msg.not.ctor =\
+ {0} n''est pas un constructeur
+msg.varargs.ctor =\
+ La m\u00E9thode ou le constructeur "{0}" doit \u00EAtre statique avec la signature "(Context cx, arguments Object[], Function ctorObj, boolean inNewExpr)" pour d\u00E9finir un constructeur d''arguments de variable.
+msg.varargs.fun =\
+ La m\u00E9thode "{0}" doit \u00EAtre statique avec la signature "(Context cx, Scriptable thisObj, arguments Object[], Function funObj)" pour d\u00E9finir une fonction d''arguments de variable
+msg.incompat.call =\
+ La m\u00E9thode "{0}" a \u00E9t\u00E9 appel\u00E9e dans un objet non compatible
+msg.bad.parms =\
+ Les param\u00E8tres de la m\u00E9thode sont incorrects pour "{0}"
+msg.no.overload =\
+ La m\u00E9thode "{0}" appara\u00EEt plusieurs fois dans la classe "{1}"
+msg.method.not.found =\
+ La m\u00E9thode "{0}" est introuvable dans "{1}"
+msg.bad.for.in.lhs =\
+ La partie gauche de la boucle for..in est incorrecte
+msg.bad.lhs.assign =\
+ La partie gauche de l''affectation est incorrecte
+msg.mult.index =\
+ Une seule variable est autoris\u00E9e dans la boucle for..in
+msg.cant.convert =\
+ La conversion en type "{0}" est impossible
+msg.cant.call.indirect =\
+ La fonction "{0}" doit \u00EAtre appel\u00E9e directement et non par l''interm\u00E9diaire d''une fonction portant un autre nom
+msg.eval.nonstring =\
+ Si vous appelez la fonction eval() avec une valeur qui n''appartient pas \u00E0 une cha\u00EEne primitive, c''est la valeur en question qui est renvoy\u00E9e. \u00E9tait-ce votre intention�?
+msg.only.from.new =\
+ {0} ne peut \u00EAtre appel\u00E9e qu''\u00E0 partir d''une "nouvelle" expression.
+msg.deprec.ctor =\
+ Le constructeur "{0}" est d\u00E9conseill\u00E9
+msg.no.function.ref.found =\
+ aucune source n''a \u00E9t\u00E9 trouv\u00E9e pour d\u00E9compiler la r\u00E9f\u00E9rence de fonction {0}
+msg.arg.isnt.array =\
+ le second argument de la m\u00E9thode Function.prototype.apply doit \u00EAtre un tableau
+msg.bad.esc.mask =\
+ le masque d''\u00E9chappement de cha\u00EEne est incorrect
+msg.cant.instantiate =\
+ erreur lors de l''instanciation ({0})�: la classe {1} est une classe interface ou abstract
+msg.bad.ctor.sig =\
+ Un constructeur avec une signature incorrecte a \u00E9t\u00E9 d\u00E9tect\u00E9�: {0} qui appelle {1} avec la signature {2}
+msg.not.java.obj =\
+ L''argument attendu pour la fonction getClass() doit \u00EAtre un objet Java
+msg.no.java.ctor =\
+ Le constructeur Java de "{0}" avec les arguments "{1}" est introuvable
+msg.method.ambiguous =\
+ Le choix de la m\u00E9thode Java {0}.{1} correspondant aux types d''argument JavaScript ({2}) est ambigu. Les m\u00E9thodes propos\u00E9es sont les suivantes�: {3}
+msg.constructor.ambiguous =\
+ Le choix du constructeur Java {0} correspondant aux types d''argument JavaScript ({1}) est ambigu. Les constructeurs propos\u00E9s sont les suivants�: {2}
+msg.conversion.not.allowed =\
+ Impossible de convertir {0} en {1}
+msg.not.classloader =\
+ Le constructeur de "Packages" attend un argument de type java.lang.Classloader
+msg.bad.quant =\
+ Le quantificateur {0} est incorrect
+msg.overlarge.max =\
+ Le maximum {0} est trop important
+msg.zero.quant =\
+ Le quantificateur {0} est nul
+msg.max.lt.min =\
+ Le maximum {0} est inf\u00E9rieur au minimum
+msg.unterm.quant =\
+ Le quantificateur {0} n''a pas de limite
+msg.unterm.paren =\
+ Les parenth\u00E8ses {0} n''ont pas de limite
+msg.unterm.class =\
+ La classe de caract\u00E8res {0} n''a pas de limite
+msg.bad.range =\
+ La classe de caract\u00E8res contient une plage de valeurs incorrecte
+msg.trail.backslash =\
+ \\ au d\u00E9but d''une expression r\u00E9guli\u00E8re
+msg.no.regexp =\
+ Les expressions r\u00E9guli\u00E8res ne sont pas disponibles
+msg.bad.backref =\
+ la r\u00E9f\u00E9rence ant\u00E9rieure d\u00E9passe le nombre de parenth\u00E8ses de capture
+msg.dup.label =\
+ Le libell\u00E9 {0} existe d\u00E9j\u00E0
+msg.undef.label =\
+ Le libell\u00E9 {0} n''est pas d\u00E9fini
+msg.bad.break =\
+ Le saut non libell\u00E9 doit se trouver dans la boucle ou dans l''aiguillage
+msg.continue.outside =\
+ continue doit se trouver dans la boucle
+msg.continue.nonloop =\
+ Il n''est possible de continuer que dans l''instruction d''it\u00E9ration libell\u00E9e
+msg.fn.redecl =\
+ La fonction "{0}" a \u00E9t\u00E9 de nouveau d\u00E9clar\u00E9e. La d\u00E9finition pr\u00E9c\u00E9dente sera ignor\u00E9e
+msg.no.paren.parms =\
+ il manque ''('' avant les param\u00E8tres de la fonction
+msg.no.parm =\
+ il manque un param\u00E8tre de forme
+msg.no.paren.after.parms =\
+ il manque '')'' apr\u00E8s les param\u00E8tres de forme
+msg.no.brace.body =\
+ il manque '{' avant le corps d''une fonction
+msg.no.brace.after.body =\
+ il manque ''}'' apr\u00E8s le corps d''une fonction
+msg.no.paren.cond =\
+ il manque ''('' avant une condition
+msg.no.paren.after.cond =\
+ il manque '')'' apr\u00E8s une condition
+msg.no.semi.stmt =\
+ il manque '';'' avant une instruction
+msg.no.name.after.dot =\
+ il manque un nom apr\u00E8s un op\u00E9rateur ''.''
+msg.no.bracket.index =\
+ il manque '']'' dans l''expression de l''indice
+msg.no.paren.switch =\
+ il manque ''('' avant l''expression d''un aiguillage
+msg.no.paren.after.switch =\
+ il manque '')'' apr\u00E8s l''expression d''un aiguillage
+msg.no.brace.switch =\
+ il manque '{' avant le corps d''un aiguillage
+msg.bad.switch =\
+ l''instruction d''aiguillage est incorrecte
+msg.no.colon.case =\
+ il manque '':'' apr\u00E8s l''expression d''un cas
+msg.no.while.do =\
+ il manque ''while'' apr\u00E8s le corps d''une boucle do-loop
+msg.no.paren.for =\
+ il manque ''('' apr\u00E8s for
+msg.no.semi.for =\
+ Il manque '';'' apr\u00E8s l''initialiseur for-loop
+msg.no.semi.for.cond =\
+ il manque '';'' apr\u00E8s la condition for-loop
+msg.no.paren.for.ctrl =\
+ il manque '')'' apr\u00E8s le contr�le for-loop
+msg.no.paren.with =\
+ il manque ''('' avant un objet with-statement
+msg.no.paren.after.with =\
+ il manque '')'' apr\u00E8s un objet with-statement
+msg.bad.return =\
+ la valeur renvoy\u00E9e est incorrecte
+msg.no.brace.block =\
+ il manque ''}'' dans une instruction compos\u00E9e
+msg.bad.label =\
+ le libell\u00E9 est incorrect
+msg.bad.var =\
+ il manque un nom de variable
+msg.bad.var.init =\
+ l''initialisation de la variable est incorrecte
+msg.no.colon.cond =\
+ il manque '':'' dans une expression conditionnelle
+msg.no.paren.arg =\
+ il manque '')'' apr\u00E8s une liste d''arguments
+msg.no.bracket.arg =\
+ il manque '']'' apr\u00E8s une liste d''\u00E9l\u00E9ments
+msg.bad.prop =\
+ l''identifiant de propri\u00E9t\u00E9 est incorrect
+msg.no.colon.prop =\
+ il manque '':'' apr\u00E8s un identifiant de propri\u00E9t\u00E9
+msg.no.brace.prop =\
+ il manque ''}'' apr\u00E8s une liste de propri\u00E9t\u00E9s
+msg.no.paren =\
+ il manque '')'' dans des parenth\u00E8ses
+msg.reserved.id =\
+ l''identifiant est un mot r\u00E9serv\u00E9
+msg.no.paren.catch =\
+ il manque ''('' avant une condition catch-block
+msg.bad.catchcond =\
+ la condition catch-block est incorrecte
+msg.catch.unreachable =\
+ aucune clause catch suivant une interception non qualifi\u00E9e ne peut \u00EAtre atteinte
+msg.no.brace.catchblock =\
+ il manque '{' avant le corps catch-block
+msg.try.no.catchfinally =\
+ ''try'' a \u00E9t\u00E9 d\u00E9tect\u00E9 sans ''catch'' ni ''finally''
+msg.syntax =\
+ erreur de syntaxe
+msg.assn.create =\
+ Une variable va \u00EAtre cr\u00E9\u00E9e en raison de l''affectation \u00E0 un ''{0}'' non d\u00E9fini. Ajoutez une instruction de variable \u00E0 la port\u00E9e sup\u00E9rieure pour que cet avertissement ne soit plus affich\u00E9
+msg.prop.not.found =\
+ La propri\u00E9t\u00E9 est introuvable
+msg.invalid.type =\
+ Valeur JavaScript de type {0} incorrecte
+msg.primitive.expected =\
+ Un type primitif \u00E9tait attendu (et non {0})
+msg.null.to.object =\
+ Il est impossible de convertir la valeur null en objet
+msg.undef.to.object =\
+ Il est impossible de convertir une valeur non d\u00E9finie en objet
+msg.cyclic.value =\
+ La valeur cyclique {0} n''est pas autoris\u00E9e
+msg.is.not.defined =\
+ "{0}" n''est pas d\u00E9fini
+msg.isnt.function =\
+ {0} n''est pas une fonction, est un {1}
+msg.bad.default.value =\
+ La m\u00E9thode getDefaultValue() de l''objet a renvoy\u00E9 un objet
+msg.instanceof.not.object =\
+ Il est impossible d''utiliser une instance d''un \u00E9l\u00E9ment autre qu''un objet
+msg.instanceof.bad.prototype =\
+ La propri\u00E9t\u00E9 ''prototype'' de {0} n''est pas un objet
+msg.bad.radix =\
+ la base {0} n''est pas autoris\u00E9e
+msg.default.value =\
+ La valeur par d\u00E9faut de l''objet est introuvable
+msg.zero.arg.ctor =\
+ Il est impossible de charger la classe "{0}", qui ne poss\u00E8de pas de constructeur de param\u00E8tre z\u00E9ro
+msg.multiple.ctors =\
+ Les m\u00E9thodes {0} et {1} ont \u00E9t\u00E9 d\u00E9tect\u00E9es alors qu''il est impossible d''utiliser plusieurs m\u00E9thodes constructor
+msg.ctor.multiple.parms =\
+ Il est impossible de d\u00E9finir le constructeur ou la classe {0} car plusieurs constructeurs poss\u00E8dent plusieurs param\u00E8tres
+msg.extend.scriptable =\
+ {0} doit \u00E9tendre ScriptableObject afin de d\u00E9finir la propri\u00E9t\u00E9 {1}
+msg.bad.getter.parms =\
+ Pour d\u00E9finir une propri\u00E9t\u00E9, la m\u00E9thode d''obtention {0} doit avoir des param\u00E8tres z\u00E9ro ou un seul param\u00E8tre ScriptableObject
+msg.obj.getter.parms =\
+ La m\u00E9thode d''obtention statique ou d\u00E9l\u00E9gu\u00E9e {0} doit utiliser un param\u00E8tre ScriptableObject
+msg.getter.static =\
+ La m\u00E9thode d''obtention et la m\u00E9thode de d\u00E9finition doivent toutes deux avoir le m\u00EAme \u00E9tat (statique ou non)
+msg.setter2.parms =\
+ La m\u00E9thode de d\u00E9finition \u00E0 deux param\u00E8tres doit utiliser un param\u00E8tre ScriptableObject comme premier param\u00E8tre
+msg.setter1.parms =\
+ Une m\u00E9thode d''obtention \u00E0 param\u00E8tre unique est attendue pour {0}
+msg.setter2.expected =\
+ La m\u00E9thode de d\u00E9finition statique ou d\u00E9l\u00E9gu\u00E9e {0} doit utiliser deux param\u00E8tres
+msg.setter.parms =\
+ Un ou deux param\u00E8tres sont attendus pour la m\u00E9thode de d\u00E9finition
+msg.add.sealed =\
+ Il est impossible d''ajouter une propri\u00E9t\u00E9 \u00E0 un objet ferm\u00E9
+msg.remove.sealed =\
+ Il est impossible de supprimer une propri\u00E9t\u00E9 d''un objet ferm\u00E9
+msg.token.replaces.pushback =\
+ le jeton de non-obtention {0} remplace le jeton de renvoi {1}
+msg.missing.exponent =\
+ il manque un exposant
+msg.caught.nfe =\
+ erreur de format de nombre�: {0}
+msg.unterminated.string.lit =\
+ le litt\u00E9ral de la cha\u00EEne n''a pas de limite
+msg.unterminated.comment =\
+ le commentaire n''a pas de limite
+msg.unterminated.re.lit =\
+ le litt\u00E9ral de l''expression r\u00E9guli\u00E8re n''a pas de limite
+msg.invalid.re.flag =\
+ une expression r\u00E9guli\u00E8re est suivie d''un indicateur incorrect
+msg.no.re.input.for =\
+ il n''y a pas d''entr\u00E9e pour {0}
+msg.illegal.character =\
+ caract\u00E8re non autoris\u00E9
+msg.invalid.escape =\
+ la s\u00E9quence d''\u00E9chappement Unicode est incorrecte
+msg.bad.octal.literal =\
+ le chiffre octal du litt\u00E9ral, {0}, n''est pas autoris\u00E9 et sera interpr\u00E9t\u00E9 comme un chiffre d\u00E9cimal
+msg.reserved.keyword =\
+ l''utilisation du futur mot-cl\u00E9 r\u00E9serv\u00E9 {0} n''est pas autoris\u00E9e et celui-ci sera interpr\u00E9t\u00E9 comme un identifiant ordinaire
+msg.undefined =\
+ La valeur non d\u00E9finie ne poss\u00E8de pas de propri\u00E9t\u00E9
+msg.java.internal.field.type =\
+ Erreur interne�: la conversion de type de {0} afin d''affecter {1} \u00E0 {2} a \u00E9chou\u00E9
+msg.java.conversion.implicit_method =\
+ La m\u00E9thode de conversion "{0}" est introuvable dans la classe {1}
+sg.java.method.assign =\
+ La m\u00E9thode Java "{0}" ne peut pas \u00EAtre affect\u00E9e \u00E0
+msg.java.internal.private =\
+ Erreur interne�: une tentative d''acc\u00E9der \u00E0 un champ "{0}" priv\u00E9/prot\u00E9g\u00E9 a \u00E9t\u00E9 d\u00E9tect\u00E9e
+msg.java.no_such_method =\
+ La m\u00E9thode ''{0}'' est introuvable
+msg.script.is.not.constructor =\
+ Les objets Script ne sont pas des constructeurs
+msg.nonjava.method =\
+ La m\u00E9thode Java "{0}" a \u00E9t\u00E9 appel\u00E9e avec une valeur ''this'' qui n''est pas un objet Java
+msg.java.member.not.found =\
+ La classe Java "{0}" ne poss\u00E8de aucun champ ou aucune m\u00E9thode d''instance publique appel\u00E9 "{1}"
+msg.java.array.index.out.of.bounds =\
+ Array index {0} is out of bounds [0..{1}].
+msg.pkg.int =\
+ Les noms de package Java ne peuvent pas \u00EAtre des nombres
+msg.ambig.import =\
+ Importation ambigu\u00EB�: "{0}" et "{1}"
+msg.not.pkg =\
+ La fonction importPackage doit \u00EAtre appel\u00E9e avec un package et non avec "{0}"
+msg.not.class =\
+ La fonction importClass doit \u00EAtre appel\u00E9e avec une classe et non avec "{0}"
+msg.prop.defined =\
+ Il est impossible d''importer "{0}" car une propri\u00E9t\u00E9 portant le m\u00EAme nom a d\u00E9j\u00E0 \u00E9t\u00E9 d\u00E9finie
+sg.arraylength.bad =\
+ La longueur du tableau n''est pas appropri\u00E9e
+msg.bad.uri =\
+ La s\u00E9quence URI n''est pas form\u00E9e correctement
+msg.bad.precision =\
+ La pr\u00E9cision {0} ne se trouve pas dans la plage de valeurs
diff --git a/src/org/mozilla/javascript/serialize/ScriptableInputStream.java b/src/org/mozilla/javascript/serialize/ScriptableInputStream.java
new file mode 100644
index 0000000..efb40b8
--- /dev/null
+++ b/src/org/mozilla/javascript/serialize/ScriptableInputStream.java
@@ -0,0 +1,114 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino serialization code, released
+ * Sept. 25, 2001.
+ *
+ * The Initial Developer of the Original Code is
+ * Norris Boyd.
+ * Portions created by the Initial Developer are Copyright (C) 2001
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Igor Bukanov
+ * Attila Szegedi
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// API class
+
+package org.mozilla.javascript.serialize;
+
+import java.io.*;
+
+import org.mozilla.javascript.*;
+
+/**
+ * Class ScriptableInputStream is used to read in a JavaScript
+ * object or function previously serialized with a ScriptableOutputStream.
+ * References to names in the exclusion list
+ * replaced with references to the top-level scope specified during
+ * creation of the ScriptableInputStream.
+ *
+ * @author Norris Boyd
+ */
+
+public class ScriptableInputStream extends ObjectInputStream {
+
+ /**
+ * Create a ScriptableInputStream.
+ * @param in the InputStream to read from.
+ * @param scope the top-level scope to create the object in.
+ */
+ public ScriptableInputStream(InputStream in, Scriptable scope)
+ throws IOException
+ {
+ super(in);
+ this.scope = scope;
+ enableResolveObject(true);
+ Context cx = Context.getCurrentContext();
+ if (cx != null) {
+ this.classLoader = cx.getApplicationClassLoader();
+ }
+ }
+
+ @Override
+ protected Class<?> resolveClass(ObjectStreamClass desc)
+ throws IOException, ClassNotFoundException
+ {
+ String name = desc.getName();
+ if (classLoader != null) {
+ try {
+ return classLoader.loadClass(name);
+ } catch (ClassNotFoundException ex) {
+ // fall through to default loading
+ }
+ }
+ return super.resolveClass(desc);
+ }
+
+ @Override
+ protected Object resolveObject(Object obj)
+ throws IOException
+ {
+ if (obj instanceof ScriptableOutputStream.PendingLookup) {
+ String name = ((ScriptableOutputStream.PendingLookup)obj).getName();
+ obj = ScriptableOutputStream.lookupQualifiedName(scope, name);
+ if (obj == Scriptable.NOT_FOUND) {
+ throw new IOException("Object " + name + " not found upon " +
+ "deserialization.");
+ }
+ }else if (obj instanceof UniqueTag) {
+ obj = ((UniqueTag)obj).readResolve();
+ }else if (obj instanceof Undefined) {
+ obj = ((Undefined)obj).readResolve();
+ }
+ return obj;
+ }
+
+ private Scriptable scope;
+ private ClassLoader classLoader;
+}
diff --git a/src/org/mozilla/javascript/serialize/ScriptableOutputStream.java b/src/org/mozilla/javascript/serialize/ScriptableOutputStream.java
new file mode 100644
index 0000000..2b95efd
--- /dev/null
+++ b/src/org/mozilla/javascript/serialize/ScriptableOutputStream.java
@@ -0,0 +1,220 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino serialization code, released
+ * Sept. 25, 2001.
+ *
+ * The Initial Developer of the Original Code is
+ * Norris Boyd.
+ * Portions created by the Initial Developer are Copyright (C) 2001
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Norris Boyd
+ * Attila Szegedi
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.serialize;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+import java.io.*;
+
+import org.mozilla.javascript.*;
+
+/**
+ * Class ScriptableOutputStream is an ObjectOutputStream used
+ * to serialize JavaScript objects and functions. Note that
+ * compiled functions currently cannot be serialized, only
+ * interpreted functions. The top-level scope containing the
+ * object is not written out, but is instead replaced with
+ * another top-level object when the ScriptableInputStream
+ * reads in this object. Also, object corresponding to names
+ * added to the exclude list are not written out but instead
+ * are looked up during deserialization. This approach avoids
+ * the creation of duplicate copies of standard objects
+ * during deserialization.
+ *
+ * @author Norris Boyd
+ */
+
+// API class
+
+public class ScriptableOutputStream extends ObjectOutputStream {
+
+ /**
+ * ScriptableOutputStream constructor.
+ * Creates a ScriptableOutputStream for use in serializing
+ * JavaScript objects. Calls excludeStandardObjectNames.
+ *
+ * @param out the OutputStream to write to.
+ * @param scope the scope containing the object.
+ */
+ public ScriptableOutputStream(OutputStream out, Scriptable scope)
+ throws IOException
+ {
+ super(out);
+ this.scope = scope;
+ table = new HashMap<Object,String>();
+ table.put(scope, "");
+ enableReplaceObject(true);
+ excludeStandardObjectNames(); // XXX
+ }
+
+ public void excludeAllIds(Object[] ids) {
+ for (Object id: ids) {
+ if (id instanceof String &&
+ (scope.get((String) id, scope) instanceof Scriptable))
+ {
+ this.addExcludedName((String)id);
+ }
+ }
+ }
+
+ /**
+ * Adds a qualified name to the list of object to be excluded from
+ * serialization. Names excluded from serialization are looked up
+ * in the new scope and replaced upon deserialization.
+ * @param name a fully qualified name (of the form "a.b.c", where
+ * "a" must be a property of the top-level object). The object
+ * need not exist, in which case the name is ignored.
+ * @throws IllegalArgumentException if the object is not a
+ * {@link Scriptable}.
+ */
+ public void addOptionalExcludedName(String name) {
+ Object obj = lookupQualifiedName(scope, name);
+ if(obj != null && obj != UniqueTag.NOT_FOUND) {
+ if (!(obj instanceof Scriptable)) {
+ throw new IllegalArgumentException(
+ "Object for excluded name " + name +
+ " is not a Scriptable, it is " +
+ obj.getClass().getName());
+ }
+ table.put(obj, name);
+ }
+ }
+
+ /**
+ * Adds a qualified name to the list of objects to be excluded from
+ * serialization. Names excluded from serialization are looked up
+ * in the new scope and replaced upon deserialization.
+ * @param name a fully qualified name (of the form "a.b.c", where
+ * "a" must be a property of the top-level object)
+ * @throws IllegalArgumentException if the object is not found or is not
+ * a {@link Scriptable}.
+ */
+ public void addExcludedName(String name) {
+ Object obj = lookupQualifiedName(scope, name);
+ if (!(obj instanceof Scriptable)) {
+ throw new IllegalArgumentException("Object for excluded name " +
+ name + " not found.");
+ }
+ table.put(obj, name);
+ }
+
+ /**
+ * Returns true if the name is excluded from serialization.
+ */
+ public boolean hasExcludedName(String name) {
+ return table.get(name) != null;
+ }
+
+ /**
+ * Removes a name from the list of names to exclude.
+ */
+ public void removeExcludedName(String name) {
+ table.remove(name);
+ }
+
+ /**
+ * Adds the names of the standard objects and their
+ * prototypes to the list of excluded names.
+ */
+ public void excludeStandardObjectNames() {
+ String[] names = { "Object", "Object.prototype",
+ "Function", "Function.prototype",
+ "String", "String.prototype",
+ "Math", // no Math.prototype
+ "Array", "Array.prototype",
+ "Error", "Error.prototype",
+ "Number", "Number.prototype",
+ "Date", "Date.prototype",
+ "RegExp", "RegExp.prototype",
+ "Script", "Script.prototype",
+ "Continuation", "Continuation.prototype",
+ };
+ for (int i=0; i < names.length; i++) {
+ addExcludedName(names[i]);
+ }
+
+ String[] optionalNames = {
+ "XML", "XML.prototype",
+ "XMLList", "XMLList.prototype",
+ };
+ for (int i=0; i < optionalNames.length; i++) {
+ addOptionalExcludedName(optionalNames[i]);
+ }
+ }
+
+ static Object lookupQualifiedName(Scriptable scope,
+ String qualifiedName)
+ {
+ StringTokenizer st = new StringTokenizer(qualifiedName, ".");
+ Object result = scope;
+ while (st.hasMoreTokens()) {
+ String s = st.nextToken();
+ result = ScriptableObject.getProperty((Scriptable)result, s);
+ if (result == null || !(result instanceof Scriptable))
+ break;
+ }
+ return result;
+ }
+
+ static class PendingLookup implements Serializable
+ {
+ static final long serialVersionUID = -2692990309789917727L;
+
+ PendingLookup(String name) { this.name = name; }
+
+ String getName() { return name; }
+
+ private String name;
+ }
+
+ @Override
+ protected Object replaceObject(Object obj) throws IOException
+ {
+ if (false) throw new IOException(); // suppress warning
+ String name = table.get(obj);
+ if (name == null)
+ return obj;
+ return new PendingLookup(name);
+ }
+
+ private Scriptable scope;
+ private Map<Object,String> table;
+}
diff --git a/src/org/mozilla/javascript/xml/XMLLib.java b/src/org/mozilla/javascript/xml/XMLLib.java
new file mode 100644
index 0000000..343c938
--- /dev/null
+++ b/src/org/mozilla/javascript/xml/XMLLib.java
@@ -0,0 +1,173 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.xml;
+
+import org.mozilla.javascript.*;
+
+public abstract class XMLLib
+{
+ private static final Object XML_LIB_KEY = new Object();
+
+ /**
+ An object which specifies an XMLLib implementation to be used at runtime.
+
+ This interface should be considered experimental. It may be better
+ (and certainly more flexible) to write an interface that returns an
+ XMLLib object rather than a class name, for example. But that would
+ cause many more ripple effects in the code, all the way back to
+ {@link ScriptRuntime}.
+ */
+ public static abstract class Factory {
+ public static Factory create(final String className) {
+ return new Factory() {
+ @Override
+ public String getImplementationClassName() {
+ return className;
+ }
+ };
+ }
+
+ public abstract String getImplementationClassName();
+ }
+
+ public static XMLLib extractFromScopeOrNull(Scriptable scope)
+ {
+ ScriptableObject so = ScriptRuntime.getLibraryScopeOrNull(scope);
+ if (so == null) {
+ // If library is not yet initialized, return null
+ return null;
+ }
+
+ // Ensure lazily initialization of real XML library instance
+ // which is done on first access to XML property
+ ScriptableObject.getProperty(so, "XML");
+
+ return (XMLLib)so.getAssociatedValue(XML_LIB_KEY);
+ }
+
+ public static XMLLib extractFromScope(Scriptable scope)
+ {
+ XMLLib lib = extractFromScopeOrNull(scope);
+ if (lib != null) {
+ return lib;
+ }
+ String msg = ScriptRuntime.getMessage0("msg.XML.not.available");
+ throw Context.reportRuntimeError(msg);
+ }
+
+ protected final XMLLib bindToScope(Scriptable scope)
+ {
+ ScriptableObject so = ScriptRuntime.getLibraryScopeOrNull(scope);
+ if (so == null) {
+ // standard library should be initialized at this point
+ throw new IllegalStateException();
+ }
+ return (XMLLib)so.associateValue(XML_LIB_KEY, this);
+ }
+
+ public abstract boolean isXMLName(Context cx, Object name);
+
+ public abstract Ref nameRef(Context cx, Object name,
+ Scriptable scope, int memberTypeFlags);
+
+ public abstract Ref nameRef(Context cx, Object namespace, Object name,
+ Scriptable scope, int memberTypeFlags);
+
+ /**
+ * Escapes the reserved characters in a value of an attribute.
+ *
+ * @param value Unescaped text
+ * @return The escaped text
+ */
+ public abstract String escapeAttributeValue(Object value);
+
+ /**
+ * Escapes the reserved characters in a value of a text node.
+ *
+ * @param value Unescaped text
+ * @return The escaped text
+ */
+ public abstract String escapeTextValue(Object value);
+
+
+ /**
+ * Construct namespace for default xml statement.
+ */
+ public abstract Object toDefaultXmlNamespace(Context cx, Object uriValue);
+
+ public void setIgnoreComments(boolean b) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setIgnoreWhitespace(boolean b) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setIgnoreProcessingInstructions(boolean b) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setPrettyPrinting(boolean b) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setPrettyIndent(int i) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isIgnoreComments() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isIgnoreProcessingInstructions() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isIgnoreWhitespace() {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean isPrettyPrinting() {
+ throw new UnsupportedOperationException();
+ }
+
+ public int getPrettyIndent() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/org/mozilla/javascript/xml/XMLObject.java b/src/org/mozilla/javascript/xml/XMLObject.java
new file mode 100644
index 0000000..5033564
--- /dev/null
+++ b/src/org/mozilla/javascript/xml/XMLObject.java
@@ -0,0 +1,128 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Rhino code, released
+ * May 6, 1999.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1997-2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Igor Bukanov
+ * Ethan Hugg
+ * Terry Lucas
+ * Milen Nankov
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * the GNU General Public License Version 2 or later (the "GPL"), in which
+ * case the provisions of the GPL are applicable instead of those above. If
+ * you wish to allow use of your version of this file only under the terms of
+ * the GPL and not to allow others to use your version of this file under the
+ * MPL, indicate your decision by deleting the provisions above and replacing
+ * them with the notice and other provisions required by the GPL. If you do
+ * not delete the provisions above, a recipient may use your version of this
+ * file under either the MPL or the GPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.javascript.xml;
+
+import org.mozilla.javascript.*;
+
+/**
+ * This Interface describes what all XML objects (XML, XMLList) should have in common.
+ *
+ */
+public abstract class XMLObject extends IdScriptableObject
+{
+ public XMLObject()
+ {
+ }
+
+ public XMLObject(Scriptable scope, Scriptable prototype)
+ {
+ super(scope, prototype);
+ }
+
+ /**
+ * Implementation of ECMAScript [[Has]].
+ */
+ public abstract boolean ecmaHas(Context cx, Object id);
+
+ /**
+ * Implementation of ECMAScript [[Get]].
+ */
+ public abstract Object ecmaGet(Context cx, Object id);
+
+ /**
+ * Implementation of ECMAScript [[Put]].
+ */
+ public abstract void ecmaPut(Context cx, Object id, Object value);
+
+ /**
+ * Implementation of ECMAScript [[Delete]].
+ */
+ public abstract boolean ecmaDelete(Context cx, Object id);
+
+ /**
+ * Return an additional object to look for methods that runtime should
+ * consider during method search. Return null if no such object available.
+ */
+ public abstract Scriptable getExtraMethodSource(Context cx);
+
+ /**
+ * Generic reference to implement x. at y, x..y etc.
+ */
+ public abstract Ref memberRef(Context cx, Object elem,
+ int memberTypeFlags);
+
+ /**
+ * Generic reference to implement x::ns, x. at ns::y, x.. at ns::y etc.
+ */
+ public abstract Ref memberRef(Context cx, Object namespace, Object elem,
+ int memberTypeFlags);
+
+ /**
+ * Wrap this object into NativeWith to implement the with statement.
+ */
+ public abstract NativeWith enterWith(Scriptable scope);
+
+ /**
+ * Wrap this object into NativeWith to implement the .() query.
+ */
+ public abstract NativeWith enterDotQuery(Scriptable scope);
+
+ /**
+ * Custom <tt>+</tt> operator.
+ * Should return {@link Scriptable#NOT_FOUND} if this object does not have
+ * custom addition operator for the given value,
+ * or the result of the addition operation.
+ * <p>
+ * The default implementation returns {@link Scriptable#NOT_FOUND}
+ * to indicate no custom addition operation.
+ *
+ * @param cx the Context object associated with the current thread.
+ * @param thisIsLeft if true, the object should calculate this + value
+ * if false, the object should calculate value + this.
+ * @param value the second argument for addition operation.
+ */
+ public Object addValues(Context cx, boolean thisIsLeft, Object value)
+ {
+ return Scriptable.NOT_FOUND;
+ }
+
+}
diff --git a/tests/README b/tests/README
new file mode 100644
index 0000000..12f9be7
--- /dev/null
+++ b/tests/README
@@ -0,0 +1,6 @@
+To add a test:
+
+1. Create a "blah.css" or "blah.js" file.
+2. Create a "blah.css.min" or "blah.js.min" file, containing the expected minified output.
+
+That's all!
\ No newline at end of file
diff --git a/tests/_munge.js b/tests/_munge.js
new file mode 100644
index 0000000..003f714
--- /dev/null
+++ b/tests/_munge.js
@@ -0,0 +1,8 @@
+(function() {
+ var w = window;
+
+ w.hello = function(a, abc) {
+ "a:nomunge";
+ w.alert("Hello, " + a);
+ };
+})();
diff --git a/tests/_munge.js.min b/tests/_munge.js.min
new file mode 100644
index 0000000..ecb225c
--- /dev/null
+++ b/tests/_munge.js.min
@@ -0,0 +1 @@
+(function(){var a=window;a.hello=function(a,b){a.alert("Hello, "+a)}})();
\ No newline at end of file
diff --git a/tests/_string_combo.js b/tests/_string_combo.js
new file mode 100644
index 0000000..37dd000
--- /dev/null
+++ b/tests/_string_combo.js
@@ -0,0 +1,5 @@
+function test(){
+ var a = "a" +
+ "b" +
+ "c";
+}
diff --git a/tests/_string_combo.js.min b/tests/_string_combo.js.min
new file mode 100644
index 0000000..ef05005
--- /dev/null
+++ b/tests/_string_combo.js.min
@@ -0,0 +1 @@
+function test(){var b="abc"};
\ No newline at end of file
diff --git a/tests/_syntax_error.js b/tests/_syntax_error.js
new file mode 100644
index 0000000..6819d72
--- /dev/null
+++ b/tests/_syntax_error.js
@@ -0,0 +1,73 @@
+
+window.$ = $telerik.$;
+$(document).ready(function() {
+movePageElements();
+
+var text = $('textarea').val();
+
+if (text != "")
+$('textarea').attr("style", "display: block;");
+else
+$('textarea').attr("style", "display: none;");
+
+//cleanup
+text = null;
+});
+
+function movePageElements() {
+var num = null;
+var pagenum = $(".pagecontrolscontainer");
+if (pagenum.length > 0) {
+var num = pagenum.attr("pagenumber");
+if ((num > 5) && (num < 28)) {
+var x = $('div#commentbutton');
+$("div.buttonContainer").prepend(x);
+}
+else {
+$('div#commentbutton').attr("style", "display: none;");
+}
+}
+
+//Add in dropshadowing
+if ((num > 5) && (num < 28)) {
+var top = $('.dropshadow-top');
+var middle = $('#dropshadow');
+var bottom = $('.dropshadow-bottom');
+$('#page').prepend(top);
+$('#topcontainer').after(middle);
+middle.append($('#topcontainer'));
+middle.after(bottom);
+}
+
+//cleanup
+num = null;
+pagenum = null;
+top = null;
+middle = null;
+bottom=null;
+}
+
+function expandCollapseDiv(id) {
+$telerik.$(id).slideToggle("slow");
+}
+
+function expandCollapseHelp() {
+$('.helpitems').slideToggle("slow");
+
+//Add in dropshadowing
+if ($('#helpcontainer').length) {
+$('#help-dropshadow-bot').insertAfter('#helpcontainer');
+$('#help-dropshadow-bot').removeAttr("style");
+}
+}
+
+function expandCollapseComments() {
+var style = $('textarea').attr("style");
+if (style == "display: none;")
+$('textarea').fadeIn().focus();
+else
+$('textarea').fadeOut();
+
+//cleanup
+style = null;
+}
diff --git a/tests/_syntax_error.js.min b/tests/_syntax_error.js.min
new file mode 100644
index 0000000..bd6ac03
--- /dev/null
+++ b/tests/_syntax_error.js.min
@@ -0,0 +1 @@
+window.$=$telerik.$;$(document).ready(function(){movePageElements();var a=$("textarea").val();if(a!=""){$("textarea").attr("style","display: block;")}else{$("textarea").attr("style","display: none;")}a=null});function movePageElements(){var e=null;var b=$(".pagecontrolscontainer");if(b.length>0){var e=b.attr("pagenumber");if((e>5)&&(e<28)){var a=$("div#commentbutton");$("div.buttonContainer").prepend(a)}else{$("div#commentbutton").attr("style","display: none;")}}if((e>5)&&(e<28)){var f=$ [...]
\ No newline at end of file
diff --git a/tests/background-position.css b/tests/background-position.css
new file mode 100644
index 0000000..4cdff82
--- /dev/null
+++ b/tests/background-position.css
@@ -0,0 +1,2 @@
+a {background-position: 0 0 0 0;}
+b {BACKGROUND-POSITION: 0 0;}
\ No newline at end of file
diff --git a/tests/background-position.css.min b/tests/background-position.css.min
new file mode 100644
index 0000000..0895e1a
--- /dev/null
+++ b/tests/background-position.css.min
@@ -0,0 +1 @@
+a{background-position:0 0}b{background-position:0 0}
\ No newline at end of file
diff --git a/tests/border-none.css b/tests/border-none.css
new file mode 100644
index 0000000..03d4b67
--- /dev/null
+++ b/tests/border-none.css
@@ -0,0 +1,10 @@
+a {
+ border: none;
+}
+b {BACKGROUND:none}
+s {
+ border-top: none;
+ border-right: none;
+ border-bottom:none;
+ border-left: none
+}
\ No newline at end of file
diff --git a/tests/border-none.css.min b/tests/border-none.css.min
new file mode 100644
index 0000000..1018b4c
--- /dev/null
+++ b/tests/border-none.css.min
@@ -0,0 +1 @@
+a{border:0}b{background:0}s{border-top:0;border-right:0;border-bottom:0;border-left:0}
\ No newline at end of file
diff --git a/tests/box-model-hack.css b/tests/box-model-hack.css
new file mode 100644
index 0000000..c00e32f
--- /dev/null
+++ b/tests/box-model-hack.css
@@ -0,0 +1,9 @@
+#elem {
+ width: 100px;
+ voice-family: "\"}\"";
+ voice-family:inherit;
+ width: 200px;
+}
+html>body #elem {
+ width: 200px;
+}
diff --git a/tests/box-model-hack.css.min b/tests/box-model-hack.css.min
new file mode 100644
index 0000000..3340179
--- /dev/null
+++ b/tests/box-model-hack.css.min
@@ -0,0 +1 @@
+#elem{width:100px;voice-family:"\"}\"";voice-family:inherit;width:200px}html>body #elem{width:200px}
\ No newline at end of file
diff --git a/tests/bug2527974.css b/tests/bug2527974.css
new file mode 100644
index 0000000..b3bc2c8
--- /dev/null
+++ b/tests/bug2527974.css
@@ -0,0 +1,10 @@
+/* this file contains no css, it exists purely to put the revision number into the
+ combined css before uploading it to SiteManager. The exclaimation at the start
+ of the comment informs yuicompressor not to strip the comment out */
+
+/*! $LastChangedRevision: 81 $ $LastChangedDate: 2009-05-27 17:41:02 +0100 (Wed, 27 May 2009) $ */
+
+body {
+ yo: cats;
+}
+ul[id$=foo] label:hover {yo: yo;}
\ No newline at end of file
diff --git a/tests/bug2527974.css.min b/tests/bug2527974.css.min
new file mode 100644
index 0000000..00cc007
--- /dev/null
+++ b/tests/bug2527974.css.min
@@ -0,0 +1 @@
+/*! $LastChangedRevision: 81 $ $LastChangedDate: 2009-05-27 17:41:02 +0100 (Wed, 27 May 2009) $ */body{yo:cats}ul[id$=foo] label:hover{yo:yo}
\ No newline at end of file
diff --git a/tests/bug2527991.css b/tests/bug2527991.css
new file mode 100644
index 0000000..d4c80ff
--- /dev/null
+++ b/tests/bug2527991.css
@@ -0,0 +1,19 @@
+ at media screen and/*!YUI-Compresser */(-webkit-min-device-pixel-ratio:0) {
+ a{
+ b: 1;
+ }
+}
+
+
+ at media screen and/*! */ /*! */(-webkit-min-device-pixel-ratio:0) {
+ a{
+ b: 1;
+ }
+}
+
+
+ at media -webkit-min-device-pixel-ratio:0 {
+ a{
+ b: 1;
+ }
+}
\ No newline at end of file
diff --git a/tests/bug2527991.css.min b/tests/bug2527991.css.min
new file mode 100644
index 0000000..965755a
--- /dev/null
+++ b/tests/bug2527991.css.min
@@ -0,0 +1 @@
+ at media screen and/*!YUI-Compresser */(-webkit-min-device-pixel-ratio:0){a{b:1}}@media screen and/*! *//*! */(-webkit-min-device-pixel-ratio:0){a{b:1}}@media -webkit-min-device-pixel-ratio:0{a{b:1}}
\ No newline at end of file
diff --git a/tests/bug2527998.css b/tests/bug2527998.css
new file mode 100644
index 0000000..9c6c00e
--- /dev/null
+++ b/tests/bug2527998.css
@@ -0,0 +1,4 @@
+/*! special */
+body {
+
+}
diff --git a/tests/bug2527998.css.min b/tests/bug2527998.css.min
new file mode 100644
index 0000000..7fabf8a
--- /dev/null
+++ b/tests/bug2527998.css.min
@@ -0,0 +1 @@
+/*! special */
\ No newline at end of file
diff --git a/tests/bug2528034.css b/tests/bug2528034.css
new file mode 100644
index 0000000..c315cb1
--- /dev/null
+++ b/tests/bug2528034.css
@@ -0,0 +1,5 @@
+a[href$="/test/"] span:first-child { b:1; }
+a[href$="/test/"] span:first-child { }
+
+
+
diff --git a/tests/bug2528034.css.min b/tests/bug2528034.css.min
new file mode 100644
index 0000000..1543777
--- /dev/null
+++ b/tests/bug2528034.css.min
@@ -0,0 +1 @@
+a[href$="/test/"] span:first-child{b:1}
\ No newline at end of file
diff --git a/tests/charset-media.css b/tests/charset-media.css
new file mode 100644
index 0000000..bd02f38
--- /dev/null
+++ b/tests/charset-media.css
@@ -0,0 +1,9 @@
+/* re: 2495387 */
+ at charset 'utf-8';
+ at media all {
+body {
+}
+body {
+background-color: gold;
+}
+}
\ No newline at end of file
diff --git a/tests/charset-media.css.min b/tests/charset-media.css.min
new file mode 100644
index 0000000..dcaf49d
--- /dev/null
+++ b/tests/charset-media.css.min
@@ -0,0 +1 @@
+ at charset 'utf-8';@media all{body{background-color:gold}}
\ No newline at end of file
diff --git a/tests/color-keyword.css b/tests/color-keyword.css
new file mode 100644
index 0000000..0051ea7
--- /dev/null
+++ b/tests/color-keyword.css
@@ -0,0 +1 @@
+.c1{color:#FF0000}.c2{color:#000080}.c3{color:#808080}.c4{color:#808000}.c5{color:#800080}.c6{color:#C0C0C0}.c7{color:#008080}.c8{color:#FFA500}.c9{color:#800000}
diff --git a/tests/color-keyword.css.min b/tests/color-keyword.css.min
new file mode 100644
index 0000000..c98f662
--- /dev/null
+++ b/tests/color-keyword.css.min
@@ -0,0 +1 @@
+.c1{color:red}.c2{color:navy}.c3{color:gray}.c4{color:olive}.c5{color:purple}.c6{color:silver}.c7{color:teal}.c8{color:orange}.c9{color:maroon}
diff --git a/tests/color-simple.css b/tests/color-simple.css
new file mode 100644
index 0000000..bb33ec3
--- /dev/null
+++ b/tests/color-simple.css
@@ -0,0 +1,8 @@
+.foo, #AABBCC {
+ background-color:#aabbcc;
+ border-color:#Ee66aA #ABCDEF #FeAb2C;
+ filter:chroma(color = #FFFFFF );
+ filter:chroma(color="#AABBCC");
+ filter:chroma(color='#BBDDEE');
+ color:#112233
+}
\ No newline at end of file
diff --git a/tests/color-simple.css.min b/tests/color-simple.css.min
new file mode 100644
index 0000000..1e39e23
--- /dev/null
+++ b/tests/color-simple.css.min
@@ -0,0 +1 @@
+.foo,#AABBCC{background-color:#abc;border-color:#e6a #abcdef #feab2c;filter:chroma(color = #FFFFFF);filter:chroma(color="#AABBCC");filter:chroma(color='#BBDDEE');color:#123}
diff --git a/tests/color.css b/tests/color.css
new file mode 100644
index 0000000..2f1254b
--- /dev/null
+++ b/tests/color.css
@@ -0,0 +1,47 @@
+.color {
+ me: rgb(123, 123, 123);
+ impressed: #FfEedD;
+ again: #ABCDEF;
+ andagain:#aa66cc;
+ background-color:#aa66ccc;
+ filter: chroma(color="#FFFFFF");
+ background: none repeat scroll 0 0 rgb(255, 0,0);
+ alpha: rgba(1, 2, 3, 4);
+ border-color: RGBA(1,2,3,4); /* tests uppercase RGBA() */
+ color:#1122aa
+}
+
+#AABBCC {
+ background-color:#ffee11;
+ filter: chroma(color = #FFFFFF );
+ color:#441122;
+ foo:#00fF11 #ABC #AABbCc #123344;
+ border-color:#aa66ccC
+}
+
+.foo #AABBCC {
+ background-color:#fFEe11;
+ color:#441122;
+ border-color:#AbC;
+ filter: chroma(color= #FFFFFF)
+}
+
+.bar, #AABBCC {
+ background-color:#FFee11;
+ border-color:#00fF11 #ABCDEF;
+ filter: chroma(color=#11FFFFFF);
+ color:#441122;
+}
+
+.foo, #AABBCC.foobar {
+ background-color:#ffee11;
+ border-color:#00fF11 #ABCDEF #AABbCc;
+ color:#441122;
+}
+
+ at media screen {
+ .bar, #AABBCC {
+ background-color:#ffEE11;
+ color:#441122
+ }
+}
diff --git a/tests/color.css.min b/tests/color.css.min
new file mode 100644
index 0000000..47690ae
--- /dev/null
+++ b/tests/color.css.min
@@ -0,0 +1 @@
+.color{me:#7b7b7b;impressed:#fed;again:#abcdef;andagain:#a6c;background-color:#aa66ccc;filter:chroma(color="#FFFFFF");background:none repeat scroll 0 0 red;alpha:rgba(1,2,3,4);border-color:rgba(1,2,3,4);color:#12a}#AABBCC{background-color:#fe1;filter:chroma(color = #FFFFFF);color:#412;foo:#0f1 #ABC #abc #123344;border-color:#aa66ccC}.foo #AABBCC{background-color:#fe1;color:#412;border-color:#AbC;filter:chroma(color= #FFFFFF)}.bar,#AABBCC{background-color:#fe1;border-color:#0f1 #abcdef;fi [...]
\ No newline at end of file
diff --git a/tests/comment.css b/tests/comment.css
new file mode 100644
index 0000000..7073b9e
--- /dev/null
+++ b/tests/comment.css
@@ -0,0 +1,3 @@
+html >/**/ body p {
+ color: blue;
+}
diff --git a/tests/comment.css.min b/tests/comment.css.min
new file mode 100644
index 0000000..b280371
--- /dev/null
+++ b/tests/comment.css.min
@@ -0,0 +1 @@
+html>/**/body p{color:blue}
\ No newline at end of file
diff --git a/tests/concat-charset.css b/tests/concat-charset.css
new file mode 100644
index 0000000..96964ea
--- /dev/null
+++ b/tests/concat-charset.css
@@ -0,0 +1,15 @@
+/* This is invalid CSS, but frequently happens as a result of concatenation. */
+ at CHARSET "utf-8";
+#foo {
+ border-width:1px;
+}
+/*
+Note that this is erroneous!
+The actual CSS file can only have a single charset.
+However, this is the job of the author/application.
+The compressor should not get involved.
+*/
+ at charset "another one";
+#bar {
+ border-width:10px;
+}
diff --git a/tests/concat-charset.css.min b/tests/concat-charset.css.min
new file mode 100644
index 0000000..73e8d3b
--- /dev/null
+++ b/tests/concat-charset.css.min
@@ -0,0 +1 @@
+ at charset "utf-8";#foo{border-width:1px}#bar{border-width:10px}
\ No newline at end of file
diff --git a/tests/dataurl-base64-doublequotes.css b/tests/dataurl-base64-doublequotes.css
new file mode 100644
index 0000000..49a1315
--- /dev/null
+++ b/tests/dataurl-base64-doublequotes.css
@@ -0,0 +1,23 @@
+.yui3-skin-night .yui3-dial-ring-vml,
+.yui3-skin-night .yui3-dial-center-button-vml,
+.yui3-skin-night .yui3-dial-marker v\:oval.yui3-dial-marker-max-min,
+.yui3-skin-night v\:oval.yui3-dial-marker-max-min,
+.yui3-skin-night .yui3-dial-marker-vml,
+.yui3-skin-night .yui3-dial-handle-vml {
+ background: none;
+ opacity:1;
+}
+
+div.base64-doublequotes {
+ width:100px;
+ height:100px;
+ background-image:url( "data:image/jpeg;base64,%2F9j%2F4AAQSkZJRgABAgEAZABkAAD%2F4RfJRXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAExAAIAAAAeAAAAcgEyAAIAAAAUAAAAkIdpAAQAAAABAAAApAAAANAAD0JAAAAnEAAPQkAAACcQQWRvYmUgUGhvdG9zaG9wIENTMiBNYWNpbnRvc2gAMjAwODowNzoxOSAxNDo1ODowNQAAA6ABAAMAAAAB%2F%2F8AAKACAAQAAAABAAABwqADAAQAAAABAAABRQAAAAAAAAAGAQMAAwAAAAEABgAAARoABQAAAAEAAAEeARsABQAAAAEAAAEmASgAAwAAAAEAAgAAAgEABAAAAAEAAAEuAgIABAAAAAEAABaTAAAAAAAAAEgAAA [...]
+ background-position:center center;
+ border:1px solid #00aa00;
+}
+
+.yui-skin-sam .yui-h-slider {
+ background: url(bg-h.gif) no-repeat 5px 0;
+ height: 28px;
+ width: 228px;
+}
diff --git a/tests/dataurl-base64-doublequotes.css.min b/tests/dataurl-base64-doublequotes.css.min
new file mode 100644
index 0000000..223d27a
--- /dev/null
+++ b/tests/dataurl-base64-doublequotes.css.min
@@ -0,0 +1 @@
+.yui3-skin-night .yui3-dial-ring-vml,.yui3-skin-night .yui3-dial-center-button-vml,.yui3-skin-night .yui3-dial-marker v\:oval.yui3-dial-marker-max-min,.yui3-skin-night v\:oval.yui3-dial-marker-max-min,.yui3-skin-night .yui3-dial-marker-vml,.yui3-skin-night .yui3-dial-handle-vml{background:0;opacity:1}div.base64-doublequotes{width:100px;height:100px;background-image:url("data:image/jpeg;base64,%2F9j%2F4AAQSkZJRgABAgEAZABkAAD%2F4RfJRXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAA [...]
\ No newline at end of file
diff --git a/tests/dataurl-base64-eof.css b/tests/dataurl-base64-eof.css
new file mode 100644
index 0000000..a50ad77
--- /dev/null
+++ b/tests/dataurl-base64-eof.css
@@ -0,0 +1,10 @@
+div.base64-singlequotes {
+ width:100px;
+ height:100px;
+ background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QAAAAAAAD5Q7t%2FAAAACXBIWXMAAA3WAAAN1gGQb3mcAAAFrUlEQVRYw%2B2Xz28kRxXHP%2B9Vdc8vj2fG9uIkm2yUeFGEhNCKQwBpj1yRUBAnpJU4ceZP4MxfkBxy2GO45bQXuEGQohUKigQ%2BsBBE1sbYXv8Yz%2FRMd9fjUN09PV5nFZA4kZZa79W3quu9er%2FqNfy%2FPwJgZty%2Ffz%2FZ29vrpmmqhlWTgmFg1UpbfWhYhG6Yq2cFi%2FNrj9nJyWnx%2BPHjeafbMTEzPvjVB9%2B6d%2B%2FezweDwV1BfHubivkC3lZya%2F4m7Np8UZYXhweHH7733rvvC0Kyv7%2F%2F7p07d34qIjyvr63RNb4l4CbsRUrNrq [...]
+ background-position:center center;
+ border:1px solid #00aa00;
+}
+div.otherdataurl {
+ background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9kFBQ0WDWwqwjwAAANMSURBVEjHrdZbaFxVFAbgb2aSTG6GTi6mVIwxNxF9qFI0RQnFUqiYamutVutLa2t9EY0oPggFoYgPRR%2FaghYviA%2BiIAYvmBJKoYWiiBCigVTT1FisbUhrEtNkJpc5PuQkjGEmJqkLFmdz2Hv%2Fa%2F3rX3tvlm95oS%2FLokuZtIpbdvAs7KFtL22wjb3V1C41upy2ke1DXC2k%2FBjv1HHXDrbkEamg7lX2P8QTldQ2UtfOB8uiJsHNiB%2Fik0GmO%2BgZIxgnGA59nGCMoJPeQaYP047iBDXZEohkAYof4%2FNyKlZRdR%2F1ASZC [...]
+}
\ No newline at end of file
diff --git a/tests/dataurl-base64-eof.css.min b/tests/dataurl-base64-eof.css.min
new file mode 100644
index 0000000..1f6d2e2
--- /dev/null
+++ b/tests/dataurl-base64-eof.css.min
@@ -0,0 +1 @@
+div.base64-singlequotes{width:100px;height:100px;background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QAAAAAAAD5Q7t%2FAAAACXBIWXMAAA3WAAAN1gGQb3mcAAAFrUlEQVRYw%2B2Xz28kRxXHP%2B9Vdc8vj2fG9uIkm2yUeFGEhNCKQwBpj1yRUBAnpJU4ceZP4MxfkBxy2GO45bQXuEGQohUKigQ%2BsBBE1sbYXv8Yz%2FRMd9fjUN09PV5nFZA4kZZa79W3quu9er%2FqNfy%2FPwJgZty%2Ffz%2FZ29vrpmmqhlWTgmFg1UpbfWhYhG6Yq2cFi%2FNrj9nJyWnx%2BPHjeafbMTEzPvjVB9%2B6d%2B%2FezweDwV1BfHubivkC3lZya%2F4m7Np8UZYXhweHH7733 [...]
\ No newline at end of file
diff --git a/tests/dataurl-base64-linebreakindata.css b/tests/dataurl-base64-linebreakindata.css
new file mode 100644
index 0000000..c3f686f
--- /dev/null
+++ b/tests/dataurl-base64-linebreakindata.css
@@ -0,0 +1,34 @@
+.yui3-skin-night .yui3-dial-ring-vml,
+.yui3-skin-night .yui3-dial-center-button-vml,
+.yui3-skin-night .yui3-dial-marker v\:oval.yui3-dial-marker-max-min,
+.yui3-skin-night v\:oval.yui3-dial-marker-max-min,
+.yui3-skin-night .yui3-dial-marker-vml,
+.yui3-skin-night .yui3-dial-handle-vml {
+ background: none;
+ opacity:1;
+}
+
+div.base64-doublequotes {
+ width:100px;
+ height:100px;
+ background-image:url( "data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9kFBQ0WDWwq
+ wjwAAANMSURBVEjHrdZbaFxVFAbgb2aSTG6GTi6mVIwxNxF9qFI0RQnFUqiYamutVutLa2t9EY0oPggFoYgPRR%2FaghYviA%2BiIAYvmBJKoYWi
+ iBCigVTT1FisbUhrEtNkJpc5PuQkjGEmJqkLFmdz2Hv%2Fa%2F3rX3tvlm95oS%2FLokuZtIpbdvAs7KFtL22wjb3V1C41upy2ke1DXC2k%2FBjv
+ 1HHXDrbkEamg7lX2P8QTldQ2UtfOB8uiJsHNiB%2Fik0GmO%2BgZIxgnGA59nGCMoJPeQaYP047iBDXZEohkAYof4%2FNyKlZRdR%2F1ASZCnoOM
+ hWkUheMz9F1laJSRZ3gEqVw1ipZQjcoBRrbT3Ez9OJLhZkHG5CD8l8Q47qXhMZp%2FYxhVFaxBLBtQejdtA%2FTtZPMIJnOknI2WSYzicTYN8OtT
+ vICZbECxdr5Pkm6iPL0C3c%2BgkfIJgi%2F4LnP%2FudRKD3K4jf1VJGLEAiuz6VnA4AGam1h7gpNIzSFe66D3NurLKVhJNkHo07N9V9BE3XHOYm
+ yeuirqG1l9mdHgOkDSGd8%2FGWtg9Roa56lrYdchDtRQPLlCkEywKVRScDfrurnwC2diiPTRe47iVtbnLZDxckGCkKYpPM%2FRr3kbyRhsYOtRDi
+ QonFoBSHrBOI18rOeOPvr76YrCeUbf5fTvjOddJ0gQ1uMPku9z6hwjiEZhgOMn%2BaaUeHQJIOkMD7KMA5QQP01HP18hPbfvDTvZPETqb2YiS1BW
+ rozM6jk9SPJJHkZZ5qFagtoaNnbyZg1FE4sUPRdlAQpwkdSjvDZAJ%2FoxNt%2Bw6NlGbQVFl5iKLKKsXCAwyFQZ8S3ciu65ho1lBJ5%2FkZk3OB
+ GjpJWGmVCmsjTkQvA8JHCUU7s5eImevzg%2Fd7BGFhzCARIf8uVN3J5Heh1VM%2BHlko2y%2FHBxF0NJolfo38eDuJJxbf0ro%2FnMurh8hM%2Fu
+ pH4tdT8zciOFsTC8SAgQoJfRxKzCuvfw0k%2F8MDx7xqUyez%2BS48ESIN7Ky6OUpfAtrxzhx03UR4m0c%2FZF7tnKW2mma4l9yuthTSIZIvxPi6
+ EcpZW0PM0xtOzjo%2Bf4GPfv4r1qNqAUFYs9diJLBC1CIa7FZx8fUlwI22LuNv%2FfLbKMAOftH9TwRXg6%2FiCDAAAAAElFTkSuQmCC" );
+ background-position:center center;
+ border:1px solid #00aa00;
+}
+
+.yui-skin-sam .yui-h-slider {
+ background: url(bg-h.gif) no-repeat 5px 0;
+ height: 28px;
+ width: 228px;
+}
diff --git a/tests/dataurl-base64-linebreakindata.css.min b/tests/dataurl-base64-linebreakindata.css.min
new file mode 100644
index 0000000..1ac0e17
--- /dev/null
+++ b/tests/dataurl-base64-linebreakindata.css.min
@@ -0,0 +1 @@
+.yui3-skin-night .yui3-dial-ring-vml,.yui3-skin-night .yui3-dial-center-button-vml,.yui3-skin-night .yui3-dial-marker v\:oval.yui3-dial-marker-max-min,.yui3-skin-night v\:oval.yui3-dial-marker-max-min,.yui3-skin-night .yui3-dial-marker-vml,.yui3-skin-night .yui3-dial-handle-vml{background:0;opacity:1}div.base64-doublequotes{width:100px;height:100px;background-image:url("data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0 [...]
\ No newline at end of file
diff --git a/tests/dataurl-base64-noquotes.css b/tests/dataurl-base64-noquotes.css
new file mode 100644
index 0000000..71b0962
--- /dev/null
+++ b/tests/dataurl-base64-noquotes.css
@@ -0,0 +1,26 @@
+.yui3-skin-night .yui3-dial-ring-vml,
+.yui3-skin-night .yui3-dial-center-button-vml,
+.yui3-skin-night .yui3-dial-marker v\:oval.yui3-dial-marker-max-min,
+.yui3-skin-night v\:oval.yui3-dial-marker-max-min,
+.yui3-skin-night .yui3-dial-marker-vml,
+.yui3-skin-night .yui3-dial-handle-vml {
+ background: none;
+ opacity:1;
+}
+
+div.base64-noquotes {
+ width:100px;
+ height:100px;
+ background-image:url(
+ data:image/jpeg;base64,
+ %2F9j%2F4AAQSkZJRgABAgEAZABkAAD%2F4RfJRXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAExAAIAAAAeAAAAcgEyAAIAAAAUAAAAkIdpAAQAAAABAAAApAAAANAAD0JAAAAnEAAPQkAAACcQQWRvYmUgUGhvdG9zaG9wIENTMiBNYWNpbnRvc2gAMjAwODowNzoxOSAxNDo1ODowNQAAA6ABAAMAAAAB%2F%2F8AAKACAAQAAAABAAABwqADAAQAAAABAAABRQAAAAAAAAAGAQMAAwAAAAEABgAAARoABQAAAAEAAAEeARsABQAAAAEAAAEmASgAAwAAAAEAAgAAAgEABAAAAAEAAAEuAgIABAAAAAEAABaTAAAAAAAAAEgAAAABAAAASAAAAAH%2F2P%2FgABBKRklGAAECAABIAEgAAP [...]
+ );
+ background-position:center center;
+ border:1px solid #00aa00;
+}
+
+.yui-skin-sam .yui-h-slider {
+ background: url(bg-h.gif) no-repeat 5px 0;
+ height: 28px;
+ width: 228px;
+}
\ No newline at end of file
diff --git a/tests/dataurl-base64-noquotes.css.min b/tests/dataurl-base64-noquotes.css.min
new file mode 100644
index 0000000..f57be99
--- /dev/null
+++ b/tests/dataurl-base64-noquotes.css.min
@@ -0,0 +1 @@
+.yui3-skin-night .yui3-dial-ring-vml,.yui3-skin-night .yui3-dial-center-button-vml,.yui3-skin-night .yui3-dial-marker v\:oval.yui3-dial-marker-max-min,.yui3-skin-night v\:oval.yui3-dial-marker-max-min,.yui3-skin-night .yui3-dial-marker-vml,.yui3-skin-night .yui3-dial-handle-vml{background:0;opacity:1}div.base64-noquotes{width:100px;height:100px;background-image:url(data:image/jpeg;base64,%2F9j%2F4AAQSkZJRgABAgEAZABkAAD%2F4RfJRXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAA [...]
\ No newline at end of file
diff --git a/tests/dataurl-base64-singlequotes.css b/tests/dataurl-base64-singlequotes.css
new file mode 100644
index 0000000..1ec9f67
--- /dev/null
+++ b/tests/dataurl-base64-singlequotes.css
@@ -0,0 +1,23 @@
+.yui3-skin-night .yui3-dial-ring-vml,
+.yui3-skin-night .yui3-dial-center-button-vml,
+.yui3-skin-night .yui3-dial-marker v\:oval.yui3-dial-marker-max-min,
+.yui3-skin-night v\:oval.yui3-dial-marker-max-min,
+.yui3-skin-night .yui3-dial-marker-vml,
+.yui3-skin-night .yui3-dial-handle-vml {
+ background: none;
+ opacity:1;
+}
+
+div.base64-singlequotes {
+ width:100px;
+ height:100px;
+ background-image:url('data:image/jpeg;base64,%2F9j%2F4AAQSkZJRgABAgEAZABkAAD%2F4RfJRXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAExAAIAAAAeAAAAcgEyAAIAAAAUAAAAkIdpAAQAAAABAAAApAAAANAAD0JAAAAnEAAPQkAAACcQQWRvYmUgUGhvdG9zaG9wIENTMiBNYWNpbnRvc2gAMjAwODowNzoxOSAxNDo1ODowNQAAA6ABAAMAAAAB%2F%2F8AAKACAAQAAAABAAABwqADAAQAAAABAAABRQAAAAAAAAAGAQMAAwAAAAEABgAAARoABQAAAAEAAAEeARsABQAAAAEAAAEmASgAAwAAAAEAAgAAAgEABAAAAAEAAAEuAgIABAAAAAEAABaTAAAAAAAAAEgAAAABA [...]
+ background-position:center center;
+ border:1px solid #00aa00;
+}
+
+.yui-skin-sam .yui-h-slider {
+ background: url(bg-h.gif) no-repeat 5px 0;
+ height: 28px;
+ width: 228px;
+}
\ No newline at end of file
diff --git a/tests/dataurl-base64-singlequotes.css.min b/tests/dataurl-base64-singlequotes.css.min
new file mode 100644
index 0000000..8f3398d
--- /dev/null
+++ b/tests/dataurl-base64-singlequotes.css.min
@@ -0,0 +1 @@
+.yui3-skin-night .yui3-dial-ring-vml,.yui3-skin-night .yui3-dial-center-button-vml,.yui3-skin-night .yui3-dial-marker v\:oval.yui3-dial-marker-max-min,.yui3-skin-night v\:oval.yui3-dial-marker-max-min,.yui3-skin-night .yui3-dial-marker-vml,.yui3-skin-night .yui3-dial-handle-vml{background:0;opacity:1}div.base64-singlequotes{width:100px;height:100px;background-image:url('data:image/jpeg;base64,%2F9j%2F4AAQSkZJRgABAgEAZABkAAD%2F4RfJRXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAA [...]
\ No newline at end of file
diff --git a/tests/dataurl-base64-twourls.css b/tests/dataurl-base64-twourls.css
new file mode 100644
index 0000000..222342f
--- /dev/null
+++ b/tests/dataurl-base64-twourls.css
@@ -0,0 +1,27 @@
+.yui3-skin-night .yui3-dial-ring-vml,
+.yui3-skin-night .yui3-dial-center-button-vml,
+.yui3-skin-night .yui3-dial-marker v\:oval.yui3-dial-marker-max-min,
+.yui3-skin-night v\:oval.yui3-dial-marker-max-min,
+.yui3-skin-night .yui3-dial-marker-vml,
+.yui3-skin-night .yui3-dial-handle-vml {
+ background: none;
+ opacity:1;
+}
+
+div.base64-singlequotes {
+ width:100px;
+ height:100px;
+ background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QAAAAAAAD5Q7t%2FAAAACXBIWXMAAA3WAAAN1gGQb3mcAAAFrUlEQVRYw%2B2Xz28kRxXHP%2B9Vdc8vj2fG9uIkm2yUeFGEhNCKQwBpj1yRUBAnpJU4ceZP4MxfkBxy2GO45bQXuEGQohUKigQ%2BsBBE1sbYXv8Yz%2FRMd9fjUN09PV5nFZA4kZZa79W3quu9er%2FqNfy%2FPwJgZty%2Ffz%2FZ29vrpmmqhlWTgmFg1UpbfWhYhG6Yq2cFi%2FNrj9nJyWnx%2BPHjeafbMTEzPvjVB9%2B6d%2B%2FezweDwV1BfHubivkC3lZya%2F4m7Np8UZYXhweHH7733rvvC0Kyv7%2F%2F7p07d34qIjyvr63RNb4l4CbsRUrNrq [...]
+ background-position:center center;
+ border:1px solid #00aa00;
+}
+
+div.otherdataurl {
+ background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9kFBQ0WDWwqwjwAAANMSURBVEjHrdZbaFxVFAbgb2aSTG6GTi6mVIwxNxF9qFI0RQnFUqiYamutVutLa2t9EY0oPggFoYgPRR%2FaghYviA%2BiIAYvmBJKoYWiiBCigVTT1FisbUhrEtNkJpc5PuQkjGEmJqkLFmdz2Hv%2Fa%2F3rX3tvlm95oS%2FLokuZtIpbdvAs7KFtL22wjb3V1C41upy2ke1DXC2k%2FBjv1HHXDrbkEamg7lX2P8QTldQ2UtfOB8uiJsHNiB%2Fik0GmO%2BgZIxgnGA59nGCMoJPeQaYP047iBDXZEohkAYof4%2FNyKlZRdR%2F1ASZC [...]
+}
+
+.yui-skin-sam .yui-h-slider {
+ background: url(bg-h.gif) no-repeat 5px 0;
+ height: 28px;
+ width: 228px;
+}
\ No newline at end of file
diff --git a/tests/dataurl-base64-twourls.css.min b/tests/dataurl-base64-twourls.css.min
new file mode 100644
index 0000000..d919bca
--- /dev/null
+++ b/tests/dataurl-base64-twourls.css.min
@@ -0,0 +1 @@
+.yui3-skin-night .yui3-dial-ring-vml,.yui3-skin-night .yui3-dial-center-button-vml,.yui3-skin-night .yui3-dial-marker v\:oval.yui3-dial-marker-max-min,.yui3-skin-night v\:oval.yui3-dial-marker-max-min,.yui3-skin-night .yui3-dial-marker-vml,.yui3-skin-night .yui3-dial-handle-vml{background:0;opacity:1}div.base64-singlequotes{width:100px;height:100px;background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QAAAAAAAD5Q7t%2FAAAACXBIWXMAAA3WAAAN1gGQb3m [...]
\ No newline at end of file
diff --git a/tests/dataurl-dbquote-font.css b/tests/dataurl-dbquote-font.css
new file mode 100644
index 0000000..f9799d7
--- /dev/null
+++ b/tests/dataurl-dbquote-font.css
@@ -0,0 +1,30 @@
+/*csslint fontfamily: true*/
+
+/**
+ * Foo
+ */
+
+.y-ff-1 {
+ font-family:"Foo Bar",Helvetica,Arial;
+ text-rendering: optimizeLegibility;
+}
+
+.ua-op .y-ff-1 {
+ /* Some Comment */
+ font-family:Helvetica,Arial;
+}
+
+/*
+Foo
+
+Bar
+*/
+
+ at font-face {
+ font-family: "Foo Bar";
+ src: url("data:font/truetype;base64,gRbIUFAIrsQNGditEWbAUKwAA") format("truetype"),
+ url("http://yuilibrary.com/fonts/foo-bar.svg#webfontse22fewwr") format("svg");
+ font-weight: normal;
+ font-style: normal;
+}
+
diff --git a/tests/dataurl-dbquote-font.css.min b/tests/dataurl-dbquote-font.css.min
new file mode 100644
index 0000000..7c4c0ed
--- /dev/null
+++ b/tests/dataurl-dbquote-font.css.min
@@ -0,0 +1,5 @@
+.y-ff-1{font-family:"Foo Bar",Helvetica,Arial;text-rendering:optimizeLegibility}.ua-op .y-ff-1{font-family:Helvetica,Arial}@font-face{font-family:"Foo Bar";src:url("data:font/truetype;base64,gRbIUFAIrsQNGditEWbAUKwAA") format("truetype"),url("http://yuilibrary.com/fonts/foo-bar.svg#webfontse22fewwr") format("svg");font-weight:normal;font-style:normal}
+
+
+
+
diff --git a/tests/dataurl-nonbase64-doublequotes.css b/tests/dataurl-nonbase64-doublequotes.css
new file mode 100644
index 0000000..0d45c94
--- /dev/null
+++ b/tests/dataurl-nonbase64-doublequotes.css
@@ -0,0 +1,13 @@
+div.nonbase64-doublequotes {
+ width:100px;
+ height:100px;
+ background-image:url(
+ "data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%0C%00%00%00%0E%08%03%00%00%00%2Cc%0D%DE%00%00%00%A2PLTEQQQ%FA%FA%FA%FC%FC%FC%EE%EE%EE%A9%A9%A9%E9%E9%E9%0A%0A%0A%0D%0D%0D444PPP%CD%CD%CD%CC%CC%CC%F5%F5%F5UUU%D0%D0%D0'''%F9%F9%F9%A6%A6%A6%40%40%40FFF%A0%A0%A0%89%89%89%8D%8D%8D%20%20%20%14%14%14%DA%DA%DA%B6%B6%B6%02%02%02%87%87%87%81%81%81%AC%AC%AC%0E%0E%0E111%7D%7D%7D%92%92%92333%B9%B9%B9%BC%BC%BChhh)))%E1%E1%E1%03%03%03%CB%CB%CB%EB%EB%EB%FD%FD%FD%A3%A3%A3(((%04%04%04%CA [...]
+ );
+ border:1px solid #00aa00;
+}
+
+span.othercss {
+ font-family:"Times New Roman";
+ font-weight:inherit;
+}
diff --git a/tests/dataurl-nonbase64-doublequotes.css.min b/tests/dataurl-nonbase64-doublequotes.css.min
new file mode 100644
index 0000000..1acc41d
--- /dev/null
+++ b/tests/dataurl-nonbase64-doublequotes.css.min
@@ -0,0 +1 @@
+div.nonbase64-doublequotes{width:100px;height:100px;background-image:url("data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%0C%00%00%00%0E%08%03%00%00%00%2Cc%0D%DE%00%00%00%A2PLTEQQQ%FA%FA%FA%FC%FC%FC%EE%EE%EE%A9%A9%A9%E9%E9%E9%0A%0A%0A%0D%0D%0D444PPP%CD%CD%CD%CC%CC%CC%F5%F5%F5UUU%D0%D0%D0'''%F9%F9%F9%A6%A6%A6%40%40%40FFF%A0%A0%A0%89%89%89%8D%8D%8D%20%20%20%14%14%14%DA%DA%DA%B6%B6%B6%02%02%02%87%87%87%81%81%81%AC%AC%AC%0E%0E%0E111%7D%7D%7D%92%92%92333%B9%B9%B9%BC%BC%BChhh)))%E1% [...]
diff --git a/tests/dataurl-nonbase64-noquotes.css b/tests/dataurl-nonbase64-noquotes.css
new file mode 100644
index 0000000..b4bc9b2
--- /dev/null
+++ b/tests/dataurl-nonbase64-noquotes.css
@@ -0,0 +1,11 @@
+div.nonbase64-noquotes {
+ width:100px;
+ height:100px;
+ background-image:url( data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%0C%00%00%00%0E%08%03%00%00%00%2Cc%0D%DE%00%00%00%A2PLTEQQQ%FA%FA%FA%FC%FC%FC%EE%EE%EE%A9%A9%A9%E9%E9%E9%0A%0A%0A%0D%0D%0D444PPP%CD%CD%CD%CC%CC%CC%F5%F5%F5UUU%D0%D0%D0\'\'\'%F9%F9%F9%A6%A6%A6%40%40%40FFF%A0%A0%A0%89%89%89%8D%8D%8D%20%20%20%14%14%14%DA%DA%DA%B6%B6%B6%02%02%02%87%87%87%81%81%81%AC%AC%AC%0E%0E%0E111%7D%7D%7D%92%92%92333%B9%B9%B9%BC%BC%BChhh\)\)\)%E1%E1%E1%03%03%03%CB%CB%CB%EB%EB%EB%FD%FD%FD% [...]
+ border:1px solid red;
+}
+
+span.othercss {
+ font-family:"Times New Roman";
+ font-weight:inherit;
+}
diff --git a/tests/dataurl-nonbase64-noquotes.css.min b/tests/dataurl-nonbase64-noquotes.css.min
new file mode 100644
index 0000000..8f4bf08
--- /dev/null
+++ b/tests/dataurl-nonbase64-noquotes.css.min
@@ -0,0 +1 @@
+div.nonbase64-noquotes{width:100px;height:100px;background-image:url(data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%0C%00%00%00%0E%08%03%00%00%00%2Cc%0D%DE%00%00%00%A2PLTEQQQ%FA%FA%FA%FC%FC%FC%EE%EE%EE%A9%A9%A9%E9%E9%E9%0A%0A%0A%0D%0D%0D444PPP%CD%CD%CD%CC%CC%CC%F5%F5%F5UUU%D0%D0%D0\'\'\'%F9%F9%F9%A6%A6%A6%40%40%40FFF%A0%A0%A0%89%89%89%8D%8D%8D%20%20%20%14%14%14%DA%DA%DA%B6%B6%B6%02%02%02%87%87%87%81%81%81%AC%AC%AC%0E%0E%0E111%7D%7D%7D%92%92%92333%B9%B9%B9%BC%BC%BChhh\)\)\)%E1 [...]
diff --git a/tests/dataurl-nonbase64-singlequotes.css b/tests/dataurl-nonbase64-singlequotes.css
new file mode 100644
index 0000000..0488549
--- /dev/null
+++ b/tests/dataurl-nonbase64-singlequotes.css
@@ -0,0 +1,15 @@
+/* Some Comment */
+
+div.nonbase64-singlequotes {
+ width:100px;
+ height:100px;
+ background-image:url('data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%0C%00%00%00%0E%08%03%00%00%00%2Cc%0D%DE%00%00%00%A2PLTEQQQ%FA%FA%FA%FC%FC%FC%EE%EE%EE%A9%A9%A9%E9%E9%E9%0A%0A%0A%0D%0D%0D444PPP%CD%CD%CD%CC%CC%CC%F5%F5%F5UUU%D0%D0%D0\'\'\'%F9%F9%F9%A6%A6%A6%40%40%40FFF%A0%A0%A0%89%89%89%8D%8D%8D%20%20%20%14%14%14%DA%DA%DA%B6%B6%B6%02%02%02%87%87%87%81%81%81%AC%AC%AC%0E%0E%0E111%7D%7D%7D%92%92%92333%B9%B9%B9%BC%BC%BChhh)))%E1%E1%E1%03%03%03%CB%CB%CB%EB%EB%EB%FD%FD%FD%A3% [...]
+ border:1px solid #0000aa;
+}
+
+/* Some Other Comment */
+
+span.othercss {
+ font-family:"Times New Roman";
+ font-weight:inherit;
+}
diff --git a/tests/dataurl-nonbase64-singlequotes.css.min b/tests/dataurl-nonbase64-singlequotes.css.min
new file mode 100644
index 0000000..badbf06
--- /dev/null
+++ b/tests/dataurl-nonbase64-singlequotes.css.min
@@ -0,0 +1,2 @@
+div.nonbase64-singlequotes{width:100px;height:100px;background-image:url('data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%0C%00%00%00%0E%08%03%00%00%00%2Cc%0D%DE%00%00%00%A2PLTEQQQ%FA%FA%FA%FC%FC%FC%EE%EE%EE%A9%A9%A9%E9%E9%E9%0A%0A%0A%0D%0D%0D444PPP%CD%CD%CD%CC%CC%CC%F5%F5%F5UUU%D0%D0%D0\'\'\'%F9%F9%F9%A6%A6%A6%40%40%40FFF%A0%A0%A0%89%89%89%8D%8D%8D%20%20%20%14%14%14%DA%DA%DA%B6%B6%B6%02%02%02%87%87%87%81%81%81%AC%AC%AC%0E%0E%0E111%7D%7D%7D%92%92%92333%B9%B9%B9%BC%BC%BChhh)))% [...]
+
diff --git a/tests/dataurl-noquote-multiline-font.css b/tests/dataurl-noquote-multiline-font.css
new file mode 100644
index 0000000..722c7ed
--- /dev/null
+++ b/tests/dataurl-noquote-multiline-font.css
@@ -0,0 +1,31 @@
+/*csslint fontfamily: true*/
+
+/**
+ * Foo
+ */
+
+.y-ff-1 {
+ font-family:"Foo Bar",Helvetica,Arial;
+ text-rendering: optimizeLegibility;
+}
+
+.ua-op .y-ff-1 {
+ /* Some Comment */
+ font-family:Helvetica,Arial;
+}
+
+/*
+Foo
+
+Bar
+*/
+
+ at font-face {
+ font-family: "Foo Bar";
+ src: url(
+ data:font/truetype;base64,gRbIUFAIrsQNGditEWbAUKwAA) format("truetype"),
+ url("http://yuilibrary.com/fonts/foo-bar.svg#webfontse22fewwr") format("svg");
+ font-weight: normal;
+ font-style: normal;
+}
+
diff --git a/tests/dataurl-noquote-multiline-font.css.min b/tests/dataurl-noquote-multiline-font.css.min
new file mode 100644
index 0000000..6b32e33
--- /dev/null
+++ b/tests/dataurl-noquote-multiline-font.css.min
@@ -0,0 +1,3 @@
+.y-ff-1{font-family:"Foo Bar",Helvetica,Arial;text-rendering:optimizeLegibility}.ua-op .y-ff-1{font-family:Helvetica,Arial}@font-face{font-family:"Foo Bar";src:url(data:font/truetype;base64,gRbIUFAIrsQNGditEWbAUKwAA) format("truetype"),url("http://yuilibrary.com/fonts/foo-bar.svg#webfontse22fewwr") format("svg");font-weight:normal;font-style:normal}
+
+
diff --git a/tests/dataurl-realdata-doublequotes.css b/tests/dataurl-realdata-doublequotes.css
new file mode 100644
index 0000000..e86097c
--- /dev/null
+++ b/tests/dataurl-realdata-doublequotes.css
@@ -0,0 +1,90 @@
+.yui3-skin-sam .yui3-scrollview-scrollbar {
+ -webkit-transform: translate3d(0, 0, 0);
+ -moz-transform: translate(0, 0);
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-first,
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-middle,
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-last {
+ border-radius:3px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAABCAYAAAD9yd/wAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABJJREFUeNpiZGBgSGPAAgACDAAIkABoFyloZQAAAABJRU5ErkJggg==");
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-first,
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-last {
+ border-bottom-right-radius:0;
+ border-bottom-left-radius:0;
+
+ -webkit-border-bottom-right-radius:0;
+ -webkit-border-bottom-left-radius:0;
+
+ -moz-border-radius-bottomright:0;
+ -moz-border-radius-bottomleft:0;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-last {
+ border-radius:0;
+ border-bottom-right-radius:3px;
+ border-bottom-left-radius:3px;
+
+ -webkit-border-radius:0;
+ -webkit-border-bottom-right-radius:3px;
+ -webkit-border-bottom-left-radius:3px;
+ -webkit-transform: translate3d(0, 0, 0);
+
+ -moz-border-radius:0;
+ -moz-border-radius-bottomright:3px;
+ -moz-border-radius-bottomleft:3px;
+ -moz-transform: translate(0, 0);
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-middle {
+ border-radius:0;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+
+ -webkit-transform: translate3d(0,0,0) scaleY(1);
+ -webkit-transform-origin-y: 0;
+
+ -moz-transform: translate(0,0) scaleY(1);
+ -moz-transform-origin: 0 0;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz .yui3-scrollview-first,
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz .yui3-scrollview-last {
+ border-top-right-radius: 0;
+ border-bottom-left-radius: 3px;
+
+ -webkit-border-top-right-radius: 0;
+ -webkit-border-bottom-left-radius: 3px;
+
+ -moz-border-radius-topright: 0;
+ -moz-border-radius-bottomleft: 3px;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz .yui3-scrollview-last {
+ border-bottom-left-radius: 0;
+ border-top-right-radius: 3px;
+
+ -webkit-border-bottom-left-radius: 0;
+ -webkit-border-top-right-radius: 3px;
+
+ -moz-border-radius-bottomleft: 0;
+ -moz-border-radius-topright: 3px;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz .yui3-scrollview-middle {
+ -webkit-transform: translate3d(0,0,0) scaleX(1);
+ -webkit-transform-origin: 0 0;
+
+ -moz-transform: translate(0,0) scaleX(1);
+ -moz-transform-origin: 0 0;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar-vert-basic .yui3-scrollview-child,
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz-basic .yui3-scrollview-child {
+ background-color: #aaa;
+ background-image: none;
+}
\ No newline at end of file
diff --git a/tests/dataurl-realdata-doublequotes.css.min b/tests/dataurl-realdata-doublequotes.css.min
new file mode 100644
index 0000000..f9e7600
--- /dev/null
+++ b/tests/dataurl-realdata-doublequotes.css.min
@@ -0,0 +1 @@
+.yui3-skin-sam .yui3-scrollview-scrollbar{-webkit-transform:translate3d(0,0,0);-moz-transform:translate(0,0)}.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-first,.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-middle,.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-last{border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAABCAYAAAD9yd/wAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJb [...]
diff --git a/tests/dataurl-realdata-noquotes.css b/tests/dataurl-realdata-noquotes.css
new file mode 100644
index 0000000..ddf720e
--- /dev/null
+++ b/tests/dataurl-realdata-noquotes.css
@@ -0,0 +1,90 @@
+.yui3-skin-sam .yui3-scrollview-scrollbar {
+ -webkit-transform: translate3d(0, 0, 0);
+ -moz-transform: translate(0, 0);
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-first,
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-middle,
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-last {
+ border-radius:3px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAABCAYAAAD9yd/wAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABJJREFUeNpiZGBgSGPAAgACDAAIkABoFyloZQAAAABJRU5ErkJggg==);
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-first,
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-last {
+ border-bottom-right-radius:0;
+ border-bottom-left-radius:0;
+
+ -webkit-border-bottom-right-radius:0;
+ -webkit-border-bottom-left-radius:0;
+
+ -moz-border-radius-bottomright:0;
+ -moz-border-radius-bottomleft:0;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-last {
+ border-radius:0;
+ border-bottom-right-radius:3px;
+ border-bottom-left-radius:3px;
+
+ -webkit-border-radius:0;
+ -webkit-border-bottom-right-radius:3px;
+ -webkit-border-bottom-left-radius:3px;
+ -webkit-transform: translate3d(0, 0, 0);
+
+ -moz-border-radius:0;
+ -moz-border-radius-bottomright:3px;
+ -moz-border-radius-bottomleft:3px;
+ -moz-transform: translate(0, 0);
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-middle {
+ border-radius:0;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+
+ -webkit-transform: translate3d(0,0,0) scaleY(1);
+ -webkit-transform-origin-y: 0;
+
+ -moz-transform: translate(0,0) scaleY(1);
+ -moz-transform-origin: 0 0;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz .yui3-scrollview-first,
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz .yui3-scrollview-last {
+ border-top-right-radius: 0;
+ border-bottom-left-radius: 3px;
+
+ -webkit-border-top-right-radius: 0;
+ -webkit-border-bottom-left-radius: 3px;
+
+ -moz-border-radius-topright: 0;
+ -moz-border-radius-bottomleft: 3px;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz .yui3-scrollview-last {
+ border-bottom-left-radius: 0;
+ border-top-right-radius: 3px;
+
+ -webkit-border-bottom-left-radius: 0;
+ -webkit-border-top-right-radius: 3px;
+
+ -moz-border-radius-bottomleft: 0;
+ -moz-border-radius-topright: 3px;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz .yui3-scrollview-middle {
+ -webkit-transform: translate3d(0,0,0) scaleX(1);
+ -webkit-transform-origin: 0 0;
+
+ -moz-transform: translate(0,0) scaleX(1);
+ -moz-transform-origin: 0 0;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar-vert-basic .yui3-scrollview-child,
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz-basic .yui3-scrollview-child {
+ background-color: #aaa;
+ background-image: none;
+}
\ No newline at end of file
diff --git a/tests/dataurl-realdata-noquotes.css.min b/tests/dataurl-realdata-noquotes.css.min
new file mode 100644
index 0000000..110f9fc
--- /dev/null
+++ b/tests/dataurl-realdata-noquotes.css.min
@@ -0,0 +1 @@
+.yui3-skin-sam .yui3-scrollview-scrollbar{-webkit-transform:translate3d(0,0,0);-moz-transform:translate(0,0)}.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-first,.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-middle,.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-last{border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAABCAYAAAD9yd/wAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbW [...]
\ No newline at end of file
diff --git a/tests/dataurl-realdata-singlequotes.css b/tests/dataurl-realdata-singlequotes.css
new file mode 100644
index 0000000..9d6ec7a
--- /dev/null
+++ b/tests/dataurl-realdata-singlequotes.css
@@ -0,0 +1,90 @@
+.yui3-skin-sam .yui3-scrollview-scrollbar {
+ -webkit-transform: translate3d(0, 0, 0);
+ -moz-transform: translate(0, 0);
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-first,
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-middle,
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-last {
+ border-radius:3px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAABCAYAAAD9yd/wAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABJJREFUeNpiZGBgSGPAAgACDAAIkABoFyloZQAAAABJRU5ErkJggg==');
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-first,
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-last {
+ border-bottom-right-radius:0;
+ border-bottom-left-radius:0;
+
+ -webkit-border-bottom-right-radius:0;
+ -webkit-border-bottom-left-radius:0;
+
+ -moz-border-radius-bottomright:0;
+ -moz-border-radius-bottomleft:0;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-last {
+ border-radius:0;
+ border-bottom-right-radius:3px;
+ border-bottom-left-radius:3px;
+
+ -webkit-border-radius:0;
+ -webkit-border-bottom-right-radius:3px;
+ -webkit-border-bottom-left-radius:3px;
+ -webkit-transform: translate3d(0, 0, 0);
+
+ -moz-border-radius:0;
+ -moz-border-radius-bottomright:3px;
+ -moz-border-radius-bottomleft:3px;
+ -moz-transform: translate(0, 0);
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-middle {
+ border-radius:0;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+
+ -webkit-transform: translate3d(0,0,0) scaleY(1);
+ -webkit-transform-origin-y: 0;
+
+ -moz-transform: translate(0,0) scaleY(1);
+ -moz-transform-origin: 0 0;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz .yui3-scrollview-first,
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz .yui3-scrollview-last {
+ border-top-right-radius: 0;
+ border-bottom-left-radius: 3px;
+
+ -webkit-border-top-right-radius: 0;
+ -webkit-border-bottom-left-radius: 3px;
+
+ -moz-border-radius-topright: 0;
+ -moz-border-radius-bottomleft: 3px;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz .yui3-scrollview-last {
+ border-bottom-left-radius: 0;
+ border-top-right-radius: 3px;
+
+ -webkit-border-bottom-left-radius: 0;
+ -webkit-border-top-right-radius: 3px;
+
+ -moz-border-radius-bottomleft: 0;
+ -moz-border-radius-topright: 3px;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz .yui3-scrollview-middle {
+ -webkit-transform: translate3d(0,0,0) scaleX(1);
+ -webkit-transform-origin: 0 0;
+
+ -moz-transform: translate(0,0) scaleX(1);
+ -moz-transform-origin: 0 0;
+}
+
+.yui3-skin-sam .yui3-scrollview-scrollbar-vert-basic .yui3-scrollview-child,
+.yui3-skin-sam .yui3-scrollview-scrollbar-horiz-basic .yui3-scrollview-child {
+ background-color: #aaa;
+ background-image: none;
+}
\ No newline at end of file
diff --git a/tests/dataurl-realdata-singlequotes.css.min b/tests/dataurl-realdata-singlequotes.css.min
new file mode 100644
index 0000000..1a4e2c6
--- /dev/null
+++ b/tests/dataurl-realdata-singlequotes.css.min
@@ -0,0 +1 @@
+.yui3-skin-sam .yui3-scrollview-scrollbar{-webkit-transform:translate3d(0,0,0);-moz-transform:translate(0,0)}.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-first,.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-middle,.yui3-skin-sam .yui3-scrollview-scrollbar .yui3-scrollview-last{border-radius:3px;-webkit-border-radius:3px;-moz-border-radius:3px;background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAABCAYAAAD9yd/wAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJb [...]
\ No newline at end of file
diff --git a/tests/dataurl-realdata-yuiapp.css b/tests/dataurl-realdata-yuiapp.css
new file mode 100644
index 0000000..78d615d
--- /dev/null
+++ b/tests/dataurl-realdata-yuiapp.css
@@ -0,0 +1,106 @@
+html {
+ background: #fff;
+ color: #555;
+ height: 100%;
+}
+
+#hd, #bd, #ft {
+ padding: 0 50px;
+}
+
+#bd {
+ padding-bottom: 50px;
+ border-bottom: 1px solid #006e9c;
+}
+
+#ft {
+ background: transparent no-repeat 0% 100%;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADcAAAAeCAMAAAC/pnaKAAAAwFBMVEUkXIAoYYQoYYQoYYQyao02bpE6cpQ6cpREe51LgqNUiqtUiqtUiqtZj7BckrNlmrtlmrtlmrtroMB3qsp3qsp3qsp+sdCHudiHudiHudiHudiPwN+YyOaYyOaYyOaf0O2g0O6n1vOn1vOn1vOn1vOw3vu45f245f245f3B7f7K9f7K9f7K9f7X/v7X/v7p//7p//79//79//4AAABhAGMAbwBzAC4AcwB0AHIAaQBuAGcAcwABAAAAwIwmGwEAAABQQKFUAAAAQHRSTlP///////////////////////////////////////////////////////////////////8A////////////////0pfSbwAAAdtJREFUOI2NlNGqgzAMhv/C [...]
+ /* image width: 55px */
+ padding: 0 0 40px 0;
+ margin: 50px;
+}
+
+#hd, #bd {
+ background: #f9f9f9;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font: 12px "Helvetica Nueue", Arial, sans-serif;
+}
+
+#hd {
+ color: #fff;
+ padding-top: 50px;
+ margin: 0;
+}
+
+#hd, h1, h2, p, .color {
+ margin: auto;
+}
+
+h1, h2, a {
+ color: #006e9c;
+}
+
+h1, h2 {
+ margin-top: 0;
+}
+
+h4 .title {
+ font-weight: bold;
+ letter-spacing: -2px;
+ font-size: 47px;
+ text-shadow: 0 1px 0 #369;
+ background: #006e9d;
+ color: #fff;
+ padding: 0 10px;
+}
+
+h4 {
+ display: block;
+ float: right;
+ margin: 0 0 0 20px;
+}
+
+h4 .what {
+ display: block;
+ padding: 4px;
+ text-align: center;
+ font-weight: normal;
+}
+
+h4 .version {
+ font-size: 11px;
+ color: #ccc;
+}
+
+h2 {
+ font-size: 40px;
+ font-family: "HelveticaNeue-Light", "Helvetica Neue Light",
+ "Helvetica Neue", sans-serif;
+ font-weight: 300;
+}
+
+h4, p {
+ padding: 6px 0 6px;
+}
+
+#ft p.fine, #ft p.fine a {
+ color: #999;
+}
+
+#ft p.intro {
+ font-size: 12px;
+}
+
+#bd {
+ font-size: 14px;
+ color: #666;
+}
+
+#ft p {
+ font-size: 11px;
+}
diff --git a/tests/dataurl-realdata-yuiapp.css.min b/tests/dataurl-realdata-yuiapp.css.min
new file mode 100644
index 0000000..8d58663
--- /dev/null
+++ b/tests/dataurl-realdata-yuiapp.css.min
@@ -0,0 +1 @@
+html{background:#fff;color:#555;height:100%}#hd,#bd,#ft{padding:0 50px}#bd{padding-bottom:50px;border-bottom:1px solid #006e9c}#ft{background:transparent no-repeat 0 100%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADcAAAAeCAMAAAC/pnaKAAAAwFBMVEUkXIAoYYQoYYQoYYQyao02bpE6cpQ6cpREe51LgqNUiqtUiqtUiqtZj7BckrNlmrtlmrtlmrtroMB3qsp3qsp3qsp+sdCHudiHudiHudiHudiPwN+YyOaYyOaYyOaf0O2g0O6n1vOn1vOn1vOn1vOw3vu45f245f245f3B7f7K9f7K9f7K9f7X/v7X/v7p//7p//79//79//4AAABhAGMAbwBzAC4Acw [...]
diff --git a/tests/dataurl-singlequote-font.css b/tests/dataurl-singlequote-font.css
new file mode 100644
index 0000000..4548feb
--- /dev/null
+++ b/tests/dataurl-singlequote-font.css
@@ -0,0 +1,30 @@
+/*csslint fontfamily: true*/
+
+/**
+ * Foo
+ */
+
+.y-ff-1 {
+ font-family:"Foo Bar",Helvetica,Arial;
+ text-rendering: optimizeLegibility;
+}
+
+.ua-op .y-ff-1 {
+ /* Some Comment */
+ font-family:Helvetica,Arial;
+}
+
+/*
+Foo
+
+Bar
+*/
+
+ at font-face {
+ font-family: "Foo Bar";
+ src: URL('data:font/truetype;base64,gRbIUFAIrsQNGditEWbAUKwAA') format("truetype"),
+ url("http://yuilibrary.com/fonts/foo-bar.svg#webfontse22fewwr") format("svg");
+ font-weight: normal;
+ font-style: normal;
+}
+
diff --git a/tests/dataurl-singlequote-font.css.min b/tests/dataurl-singlequote-font.css.min
new file mode 100644
index 0000000..fd51d54
--- /dev/null
+++ b/tests/dataurl-singlequote-font.css.min
@@ -0,0 +1,3 @@
+.y-ff-1{font-family:"Foo Bar",Helvetica,Arial;text-rendering:optimizeLegibility}.ua-op .y-ff-1{font-family:Helvetica,Arial}@font-face{font-family:"Foo Bar";src:url('data:font/truetype;base64,gRbIUFAIrsQNGditEWbAUKwAA') format("truetype"),url("http://yuilibrary.com/fonts/foo-bar.svg#webfontse22fewwr") format("svg");font-weight:normal;font-style:normal}
+
+
diff --git a/tests/dataurl-validity.html b/tests/dataurl-validity.html
new file mode 100644
index 0000000..0889132
--- /dev/null
+++ b/tests/dataurl-validity.html
@@ -0,0 +1,29 @@
+<html>
+<head>
+ <style>
+ div {
+ margin:10px;
+ }
+
+ div span {
+ background-color:#fff;
+ font-size:small;
+ }
+ </style>
+ <link rel="stylesheet" href="dataurl-nonbase64-doublequotes.css.min" type="text/css">
+ <link rel="stylesheet" href="dataurl-nonbase64-noquotes.css.min" type="text/css">
+ <link rel="stylesheet" href="dataurl-nonbase64-singlequotes.css.min" type="text/css">
+ <link rel="stylesheet" href="dataurl-base64-doublequotes.css.min" type="text/css">
+ <link rel="stylesheet" href="dataurl-base64-noquotes.css.min" type="text/css">
+ <link rel="stylesheet" href="dataurl-base64-singlequotes.css.min" type="text/css">
+</head>
+<body>
+ <div class="nonbase64-doublequotes"><span>non-base64 ("")</span></div>
+ <div class="nonbase64-noquotes"><span>non-base64 ()</span></div>
+ <div class="nonbase64-singlequotes"><span>non-base64 ('')</span></div>
+
+ <div class="base64-doublequotes"><span>base64 ("")</span></div>
+ <div class="base64-noquotes"><span>base64 ()</span></div>
+ <div class="base64-singlequotes"><span>base64 ('')</span></div>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/decimals.css b/tests/decimals.css
new file mode 100644
index 0000000..9593979
--- /dev/null
+++ b/tests/decimals.css
@@ -0,0 +1,3 @@
+::selection {
+ margin: 0.6px 0.333pt 1.2em 8.8cm;
+}
diff --git a/tests/decimals.css.min b/tests/decimals.css.min
new file mode 100644
index 0000000..4dadedc
--- /dev/null
+++ b/tests/decimals.css.min
@@ -0,0 +1 @@
+::selection{margin:.6px .333pt 1.2em 8.8cm}
\ No newline at end of file
diff --git a/tests/dollar-header.css b/tests/dollar-header.css
new file mode 100644
index 0000000..43999c4
--- /dev/null
+++ b/tests/dollar-header.css
@@ -0,0 +1,7 @@
+/*!
+$Header: /temp/dirname/filename.css 3 2/02/08 3:37p JSmith $
+*/
+
+foo {
+ bar: baz
+}
diff --git a/tests/dollar-header.css.min b/tests/dollar-header.css.min
new file mode 100644
index 0000000..9308100
--- /dev/null
+++ b/tests/dollar-header.css.min
@@ -0,0 +1,3 @@
+/*!
+$Header: /temp/dirname/filename.css 3 2/02/08 3:37p JSmith $
+*/foo{bar:baz}
\ No newline at end of file
diff --git a/tests/float.js b/tests/float.js
new file mode 100644
index 0000000..5e691b8
--- /dev/null
+++ b/tests/float.js
@@ -0,0 +1,2 @@
+obj.css({"float": "left"});
+obj.css({cssFloat:"left"});
\ No newline at end of file
diff --git a/tests/float.js.min b/tests/float.js.min
new file mode 100644
index 0000000..5e28c13
--- /dev/null
+++ b/tests/float.js.min
@@ -0,0 +1 @@
+obj.css({"float":"left"});obj.css({cssFloat:"left"});
\ No newline at end of file
diff --git a/tests/font-face.css b/tests/font-face.css
new file mode 100644
index 0000000..4b6956c
--- /dev/null
+++ b/tests/font-face.css
@@ -0,0 +1,6 @@
+ at font-face {
+ font-family: 'gzipper';
+ src: url(yanone.eot);
+ src: local('gzipper'),
+ url(yanone.ttf) format('truetype');
+}
diff --git a/tests/font-face.css.min b/tests/font-face.css.min
new file mode 100644
index 0000000..3a1077c
--- /dev/null
+++ b/tests/font-face.css.min
@@ -0,0 +1 @@
+ at font-face{font-family:'gzipper';src:url(yanone.eot);src:local('gzipper'),url(yanone.ttf) format('truetype')}
\ No newline at end of file
diff --git a/tests/ie5mac.css b/tests/ie5mac.css
new file mode 100644
index 0000000..e4d5204
--- /dev/null
+++ b/tests/ie5mac.css
@@ -0,0 +1,5 @@
+/* Ignore the next rule in IE mac \*/
+.selector {
+ color: khaki;
+}
+/* Stop ignoring in IE mac */
diff --git a/tests/ie5mac.css.min b/tests/ie5mac.css.min
new file mode 100644
index 0000000..f90df41
--- /dev/null
+++ b/tests/ie5mac.css.min
@@ -0,0 +1 @@
+/*\*/.selector{color:khaki}/**/
\ No newline at end of file
diff --git a/tests/jquery-1.6.4.js b/tests/jquery-1.6.4.js
new file mode 100644
index 0000000..11e6d06
--- /dev/null
+++ b/tests/jquery-1.6.4.js
@@ -0,0 +1,9046 @@
+/*!
+ * jQuery JavaScript Library v1.6.4
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Mon Sep 12 18:54:48 2011 -0400
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document,
+ navigator = window.navigator,
+ location = window.location;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // A simple way to check for HTML strings or ID strings
+ // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+ quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Check if a string has a non-whitespace character in it
+ rnotwhite = /\S/,
+
+ // Used for trimming whitespace
+ trimLeft = /^\s+/,
+ trimRight = /\s+$/,
+
+ // Check for digits
+ rdigit = /\d/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+ // Useragent RegExp
+ rwebkit = /(webkit)[ \/]([\w.]+)/,
+ ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+ rmsie = /(msie) ([\w.]+)/,
+ rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+ // Matches dashed string for camelizing
+ rdashAlpha = /-([a-z]|[0-9])/ig,
+ rmsPrefix = /^-ms-/,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // Keep a UserAgent string for use with jQuery.browser
+ userAgent = navigator.userAgent,
+
+ // For matching the engine and version of the browser
+ browserMatch,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // The ready event handler
+ DOMContentLoaded,
+
+ // Save a reference to some core methods
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ push = Array.prototype.push,
+ slice = Array.prototype.slice,
+ trim = String.prototype.trim,
+ indexOf = Array.prototype.indexOf,
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), or $(undefined)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // The body element only exists once, optimize finding it
+ if ( selector === "body" && !context && document.body ) {
+ this.context = document;
+ this[0] = document.body;
+ this.selector = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ // Are we dealing with HTML string or an ID?
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = quickExpr.exec( selector );
+ }
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = (context ? context.ownerDocument || context : document);
+
+ // If a single string is passed in and it's a single tag
+ // just do a createElement and skip the rest
+ ret = rsingleTag.exec( selector );
+
+ if ( ret ) {
+ if ( jQuery.isPlainObject( context ) ) {
+ selector = [ document.createElement( ret[1] ) ];
+ jQuery.fn.attr.call( selector, context, true );
+
+ } else {
+ selector = [ doc.createElement( ret[1] ) ];
+ }
+
+ } else {
+ ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+ selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $("#id")
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return (context || rootjQuery).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if (selector.selector !== undefined) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.6.4",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return slice.call( this, 0 );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+ // Build a new jQuery matched element set
+ var ret = this.constructor();
+
+ if ( jQuery.isArray( elems ) ) {
+ push.apply( ret, elems );
+
+ } else {
+ jQuery.merge( ret, elems );
+ }
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + (this.selector ? " " : "") + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Attach the listeners
+ jQuery.bindReady();
+
+ // Add the callback
+ readyList.done( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, +i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ),
+ "slice", slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+ // Either a released hold or an DOMready/load event and not yet ready
+ if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger( "ready" ).unbind( "ready" );
+ }
+ }
+ },
+
+ bindReady: function() {
+ if ( readyList ) {
+ return;
+ }
+
+ readyList = jQuery._Deferred();
+
+ // Catch cases where $(document).ready() is called after the
+ // browser event has already occurred.
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Mozilla, Opera and webkit nightlies currently support this event
+ if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else if ( document.attachEvent ) {
+ // ensure firing before onload,
+ // maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var toplevel = false;
+
+ try {
+ toplevel = window.frameElement == null;
+ } catch(e) {}
+
+ if ( document.documentElement.doScroll && toplevel ) {
+ doScrollCheck();
+ }
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ // A crude way of determining if an object is a window
+ isWindow: function( obj ) {
+ return obj && typeof obj === "object" && "setInterval" in obj;
+ },
+
+ isNaN: function( obj ) {
+ return obj == null || !rdigit.test( obj ) || isNaN( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !hasOwn.call(obj, "constructor") &&
+ !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw msg;
+ },
+
+ parseJSON: function( data ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return (new Function( "return " + data ))();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0,
+ length = object.length,
+ isObj = length === undefined || jQuery.isFunction( object );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.apply( object[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( object[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return object;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: trim ?
+ function( text ) {
+ return text == null ?
+ "" :
+ trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( array, results ) {
+ var ret = results || [];
+
+ if ( array != null ) {
+ // The window, strings (and functions) also have 'length'
+ // The extra typeof function check is to prevent crashes
+ // in Safari 2 (See: #3039)
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ var type = jQuery.type( array );
+
+ if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+ push.call( ret, array );
+ } else {
+ jQuery.merge( ret, array );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array ) {
+ if ( !array ) {
+ return -1;
+ }
+
+ if ( indexOf ) {
+ return indexOf.call( array, elem );
+ }
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ if ( array[ i ] === elem ) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var i = first.length,
+ j = 0;
+
+ if ( typeof second.length === "number" ) {
+ for ( var l = second.length; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var ret = [], retVal;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key, ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ if ( typeof context === "string" ) {
+ var tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ var args = slice.call( arguments, 2 ),
+ proxy = function() {
+ return fn.apply( context, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Mutifunctional method to get and set values to a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, key, value, exec, fn, pass ) {
+ var length = elems.length;
+
+ // Setting many attributes
+ if ( typeof key === "object" ) {
+ for ( var k in key ) {
+ jQuery.access( elems, k, key[k], exec, fn, value );
+ }
+ return elems;
+ }
+
+ // Setting one attribute
+ if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = !pass && exec && jQuery.isFunction(value);
+
+ for ( var i = 0; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+
+ return elems;
+ }
+
+ // Getting an attribute
+ return length ? fn( elems[0], key ) : undefined;
+ },
+
+ now: function() {
+ return (new Date()).getTime();
+ },
+
+ // Use of jQuery.browser is frowned upon.
+ // More details: http://docs.jquery.com/Utilities/jQuery.browser
+ uaMatch: function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = rwebkit.exec( ua ) ||
+ ropera.exec( ua ) ||
+ rmsie.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+ [];
+
+ return { browser: match[1] || "", version: match[2] || "0" };
+ },
+
+ sub: function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ return jQuerySub;
+ },
+
+ browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+ jQuery.browser[ browserMatch.browser ] = true;
+ jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+ jQuery.browser.safari = true;
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+ trimLeft = /^[\s\xA0]+/;
+ trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+ DOMContentLoaded = function() {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ };
+
+} else if ( document.attachEvent ) {
+ DOMContentLoaded = function() {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( document.readyState === "complete" ) {
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ };
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+ if ( jQuery.isReady ) {
+ return;
+ }
+
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch(e) {
+ setTimeout( doScrollCheck, 1 );
+ return;
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+}
+
+return jQuery;
+
+})();
+
+
+var // Promise methods
+ promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ),
+ // Static reference to slice
+ sliceDeferred = [].slice;
+
+jQuery.extend({
+ // Create a simple deferred (one callbacks list)
+ _Deferred: function() {
+ var // callbacks list
+ callbacks = [],
+ // stored [ context , args ]
+ fired,
+ // to avoid firing when already doing so
+ firing,
+ // flag to know if the deferred has been cancelled
+ cancelled,
+ // the deferred itself
+ deferred = {
+
+ // done( f1, f2, ...)
+ done: function() {
+ if ( !cancelled ) {
+ var args = arguments,
+ i,
+ length,
+ elem,
+ type,
+ _fired;
+ if ( fired ) {
+ _fired = fired;
+ fired = 0;
+ }
+ for ( i = 0, length = args.length; i < length; i++ ) {
+ elem = args[ i ];
+ type = jQuery.type( elem );
+ if ( type === "array" ) {
+ deferred.done.apply( deferred, elem );
+ } else if ( type === "function" ) {
+ callbacks.push( elem );
+ }
+ }
+ if ( _fired ) {
+ deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
+ }
+ }
+ return this;
+ },
+
+ // resolve with given context and args
+ resolveWith: function( context, args ) {
+ if ( !cancelled && !fired && !firing ) {
+ // make sure args are available (#8421)
+ args = args || [];
+ firing = 1;
+ try {
+ while( callbacks[ 0 ] ) {
+ callbacks.shift().apply( context, args );
+ }
+ }
+ finally {
+ fired = [ context, args ];
+ firing = 0;
+ }
+ }
+ return this;
+ },
+
+ // resolve with this as context and given arguments
+ resolve: function() {
+ deferred.resolveWith( this, arguments );
+ return this;
+ },
+
+ // Has this deferred been resolved?
+ isResolved: function() {
+ return !!( firing || fired );
+ },
+
+ // Cancel
+ cancel: function() {
+ cancelled = 1;
+ callbacks = [];
+ return this;
+ }
+ };
+
+ return deferred;
+ },
+
+ // Full fledged deferred (two callbacks list)
+ Deferred: function( func ) {
+ var deferred = jQuery._Deferred(),
+ failDeferred = jQuery._Deferred(),
+ promise;
+ // Add errorDeferred methods, then and promise
+ jQuery.extend( deferred, {
+ then: function( doneCallbacks, failCallbacks ) {
+ deferred.done( doneCallbacks ).fail( failCallbacks );
+ return this;
+ },
+ always: function() {
+ return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments );
+ },
+ fail: failDeferred.done,
+ rejectWith: failDeferred.resolveWith,
+ reject: failDeferred.resolve,
+ isRejected: failDeferred.isResolved,
+ pipe: function( fnDone, fnFail ) {
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( {
+ done: [ fnDone, "resolve" ],
+ fail: [ fnFail, "reject" ]
+ }, function( handler, data ) {
+ var fn = data[ 0 ],
+ action = data[ 1 ],
+ returned;
+ if ( jQuery.isFunction( fn ) ) {
+ deferred[ handler ](function() {
+ returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise().then( newDefer.resolve, newDefer.reject );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ });
+ } else {
+ deferred[ handler ]( newDefer[ action ] );
+ }
+ });
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ if ( obj == null ) {
+ if ( promise ) {
+ return promise;
+ }
+ promise = obj = {};
+ }
+ var i = promiseMethods.length;
+ while( i-- ) {
+ obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ];
+ }
+ return obj;
+ }
+ });
+ // Make sure only one callback list will be used
+ deferred.done( failDeferred.cancel ).fail( deferred.cancel );
+ // Unexpose cancel
+ delete deferred.cancel;
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( firstParam ) {
+ var args = arguments,
+ i = 0,
+ length = args.length,
+ count = length,
+ deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+ firstParam :
+ jQuery.Deferred();
+ function resolveFunc( i ) {
+ return function( value ) {
+ args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ if ( !( --count ) ) {
+ // Strange bug in FF4:
+ // Values changed onto the arguments object sometimes end up as undefined values
+ // outside the $.when method. Cloning the object into a fresh array solves the issue
+ deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) );
+ }
+ };
+ }
+ if ( length > 1 ) {
+ for( ; i < length; i++ ) {
+ if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) {
+ args[ i ].promise().then( resolveFunc(i), deferred.reject );
+ } else {
+ --count;
+ }
+ }
+ if ( !count ) {
+ deferred.resolveWith( deferred, args );
+ }
+ } else if ( deferred !== firstParam ) {
+ deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+ }
+ return deferred.promise();
+ }
+});
+
+
+
+jQuery.support = (function() {
+
+ var div = document.createElement( "div" ),
+ documentElement = document.documentElement,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ marginDiv,
+ support,
+ fragment,
+ body,
+ testElementParent,
+ testElement,
+ testElementStyle,
+ tds,
+ events,
+ eventName,
+ i,
+ isSupported;
+
+ // Preliminary tests
+ div.setAttribute("className", "t");
+ div.innerHTML = " <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+
+ all = div.getElementsByTagName( "*" );
+ a = div.getElementsByTagName( "a" )[ 0 ];
+
+ // Can't get basic test support
+ if ( !all || !all.length || !a ) {
+ return {};
+ }
+
+ // First batch of supports tests
+ select = document.createElement( "select" );
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName( "input" )[ 0 ];
+
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName( "tbody" ).length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName( "link" ).length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute( "href" ) === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.55$/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true
+ };
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent( "onclick" );
+ }
+
+ // Check if a radio maintains it's value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute("type", "radio");
+ support.radioValue = input.value === "t";
+
+ input.setAttribute("checked", "checked");
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.firstChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ div.innerHTML = "";
+
+ // Figure out if the W3C box model works as expected
+ div.style.width = div.style.paddingLeft = "1px";
+
+ body = document.getElementsByTagName( "body" )[ 0 ];
+ // We use our own, invisible, body unless the body is already present
+ // in which case we use a div (#9239)
+ testElement = document.createElement( body ? "div" : "body" );
+ testElementStyle = {
+ visibility: "hidden",
+ width: 0,
+ height: 0,
+ border: 0,
+ margin: 0,
+ background: "none"
+ };
+ if ( body ) {
+ jQuery.extend( testElementStyle, {
+ position: "absolute",
+ left: "-1000px",
+ top: "-1000px"
+ });
+ }
+ for ( i in testElementStyle ) {
+ testElement.style[ i ] = testElementStyle[ i ];
+ }
+ testElement.appendChild( div );
+ testElementParent = body || documentElement;
+ testElementParent.insertBefore( testElement, testElementParent.firstChild );
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ support.boxModel = div.offsetWidth === 2;
+
+ if ( "zoom" in div.style ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.style.display = "inline";
+ div.style.zoom = 1;
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "";
+ div.innerHTML = "<div style='width:4px;'></div>";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 2 );
+ }
+
+ div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
+ tds = div.getElementsByTagName( "td" );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE < 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+ div.innerHTML = "";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ if ( document.defaultView && document.defaultView.getComputedStyle ) {
+ marginDiv = document.createElement( "div" );
+ marginDiv.style.width = "0";
+ marginDiv.style.marginRight = "0";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ ( parseInt( ( document.defaultView.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+ }
+
+ // Remove the body element we added
+ testElement.innerHTML = "";
+ testElementParent.removeChild( testElement );
+
+ // Technique from Juriy Zaytsev
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for( i in {
+ submit: 1,
+ change: 1,
+ focusin: 1
+ } ) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ // Null connected elements to avoid leaks in IE
+ testElement = fragment = select = opt = body = marginDiv = div = input = null;
+
+ return support;
+})();
+
+// Keep track of boxModel
+jQuery.boxModel = jQuery.support.boxModel;
+
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+ cache: {},
+
+ // Please use with caution
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando;
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || (pvt && id && (cache[ id ] && !cache[ id ][ internalKey ]))) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ jQuery.expando ] = id = ++jQuery.uuid;
+ } else {
+ id = jQuery.expando;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
+ // metadata on plain JS objects when the object is serialized using
+ // JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
+ } else {
+ cache[ id ] = jQuery.extend(cache[ id ], name);
+ }
+ }
+
+ thisCache = cache[ id ];
+
+ // Internal jQuery data is stored in a separate object inside the object's data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data
+ if ( pvt ) {
+ if ( !thisCache[ internalKey ] ) {
+ thisCache[ internalKey ] = {};
+ }
+
+ thisCache = thisCache[ internalKey ];
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should
+ // not attempt to inspect the internal events object using jQuery.data, as this
+ // internal data object is undocumented and subject to change.
+ if ( name === "events" && !thisCache[name] ) {
+ return thisCache[ internalKey ] && thisCache[ internalKey ].events;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache,
+
+ // Reference to internal data cache key
+ internalKey = jQuery.expando,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+
+ // See jQuery.data for more information
+ id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ];
+
+ if ( thisCache ) {
+
+ // Support interoperable removal of hyphenated or camelcased keys
+ if ( !thisCache[ name ] ) {
+ name = jQuery.camelCase( name );
+ }
+
+ delete thisCache[ name ];
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !isEmptyDataObject(thisCache) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( pvt ) {
+ delete cache[ id ][ internalKey ];
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject(cache[ id ]) ) {
+ return;
+ }
+ }
+
+ var internalCache = cache[ id ][ internalKey ];
+
+ // Browsers that fail expando deletion also refuse to delete expandos on
+ // the window, but it will allow it on all other JS objects; other browsers
+ // don't care
+ // Ensure that `cache` is not a window object #10080
+ if ( jQuery.support.deleteExpando || !cache.setInterval ) {
+ delete cache[ id ];
+ } else {
+ cache[ id ] = null;
+ }
+
+ // We destroyed the entire user cache at once because it's faster than
+ // iterating through each key, but we need to continue to persist internal
+ // data if it existed
+ if ( internalCache ) {
+ cache[ id ] = {};
+ // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery
+ // metadata on plain JS objects when the object is serialized using
+ // JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+
+ cache[ id ][ internalKey ] = internalCache;
+
+ // Otherwise, we need to eliminate the expando on the node to avoid
+ // false lookups in the cache for entries that no longer exist
+ } else if ( isNode ) {
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( jQuery.support.deleteExpando ) {
+ delete elem[ jQuery.expando ];
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( jQuery.expando );
+ } else {
+ elem[ jQuery.expando ] = null;
+ }
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ if ( elem.nodeName ) {
+ var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ if ( match ) {
+ return !(match === true || elem.getAttribute("classid") !== match);
+ }
+ }
+
+ return true;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var data = null;
+
+ if ( typeof key === "undefined" ) {
+ if ( this.length ) {
+ data = jQuery.data( this[0] );
+
+ if ( this[0].nodeType === 1 ) {
+ var attr = this[0].attributes, name;
+ for ( var i = 0, l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( this[0], name, data[ name ] );
+ }
+ }
+ }
+ }
+
+ return data;
+
+ } else if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ var parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && this.length ) {
+ data = jQuery.data( this[0], key );
+ data = dataAttr( this[0], key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+
+ } else {
+ return this.each(function() {
+ var $this = jQuery( this ),
+ args = [ parts[0], value ];
+
+ $this.triggerHandler( "setData" + parts[1] + "!", args );
+ jQuery.data( this, key, value );
+ $this.triggerHandler( "changeData" + parts[1] + "!", args );
+ });
+ }
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ !jQuery.isNaN( data ) ? parseFloat( data ) :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON
+// property to be considered empty objects; this property always exists in
+// order to make sure JSON.stringify does not expose internal metadata
+function isEmptyDataObject( obj ) {
+ for ( var name in obj ) {
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+
+
+function handleQueueMarkDefer( elem, type, src ) {
+ var deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ defer = jQuery.data( elem, deferDataKey, undefined, true );
+ if ( defer &&
+ ( src === "queue" || !jQuery.data( elem, queueDataKey, undefined, true ) ) &&
+ ( src === "mark" || !jQuery.data( elem, markDataKey, undefined, true ) ) ) {
+ // Give room for hard-coded callbacks to fire first
+ // and eventually mark/queue something else on the element
+ setTimeout( function() {
+ if ( !jQuery.data( elem, queueDataKey, undefined, true ) &&
+ !jQuery.data( elem, markDataKey, undefined, true ) ) {
+ jQuery.removeData( elem, deferDataKey, true );
+ defer.resolve();
+ }
+ }, 0 );
+ }
+}
+
+jQuery.extend({
+
+ _mark: function( elem, type ) {
+ if ( elem ) {
+ type = (type || "fx") + "mark";
+ jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true );
+ }
+ },
+
+ _unmark: function( force, elem, type ) {
+ if ( force !== true ) {
+ type = elem;
+ elem = force;
+ force = false;
+ }
+ if ( elem ) {
+ type = type || "fx";
+ var key = type + "mark",
+ count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 );
+ if ( count ) {
+ jQuery.data( elem, key, count, true );
+ } else {
+ jQuery.removeData( elem, key, true );
+ handleQueueMarkDefer( elem, type, "mark" );
+ }
+ }
+ },
+
+ queue: function( elem, type, data ) {
+ if ( elem ) {
+ type = (type || "fx") + "queue";
+ var q = jQuery.data( elem, type, undefined, true );
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !q || jQuery.isArray(data) ) {
+ q = jQuery.data( elem, type, jQuery.makeArray(data), true );
+ } else {
+ q.push( data );
+ }
+ }
+ return q || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ fn = queue.shift(),
+ defer;
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ }
+
+ if ( fn ) {
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift("inprogress");
+ }
+
+ fn.call(elem, function() {
+ jQuery.dequeue(elem, type);
+ });
+ }
+
+ if ( !queue.length ) {
+ jQuery.removeData( elem, type + "queue", true );
+ handleQueueMarkDefer( elem, type, "queue" );
+ }
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ }
+
+ if ( data === undefined ) {
+ return jQuery.queue( this[0], type );
+ }
+ return this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function() {
+ var elem = this;
+ setTimeout(function() {
+ jQuery.dequeue( elem, type );
+ }, time );
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, object ) {
+ if ( typeof type !== "string" ) {
+ object = type;
+ type = undefined;
+ }
+ type = type || "fx";
+ var defer = jQuery.Deferred(),
+ elements = this,
+ i = elements.length,
+ count = 1,
+ deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ tmp;
+ function resolve() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ }
+ while( i-- ) {
+ if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
+ ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
+ jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
+ jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) {
+ count++;
+ tmp.done( resolve );
+ }
+ }
+ resolve();
+ return defer.promise();
+ }
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+ rspace = /\s+/,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea)?$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ nodeHook, boolHook;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, name, value, true, jQuery.attr );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, name, value, true, jQuery.prop );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classNames, i, l, elem, className, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( (value && typeof value === "string") || value === undefined ) {
+ classNames = (value || "").split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 && elem.className ) {
+ if ( value ) {
+ className = (" " + elem.className + " ").replace( rclass, " " );
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ className = className.replace(" " + classNames[ c ] + " ", " ");
+ }
+ elem.className = jQuery.trim( className );
+
+ } else {
+ elem.className = "";
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space seperated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ";
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return undefined;
+ }
+
+ var isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var self = jQuery(this), val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value,
+ index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+ var option = options[ i ];
+
+ // Don't return options that are disabled or in a disabled optgroup
+ if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+ (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+ if ( one && !values.length && options.length ) {
+ return jQuery( options[ index ] ).val();
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ attrFn: {
+ val: true,
+ css: true,
+ html: true,
+ text: true,
+ data: true,
+ width: true,
+ height: true,
+ offset: true
+ },
+
+ attrFix: {
+ // Always normalize to ensure hook usage
+ tabindex: "tabIndex"
+ },
+
+ attr: function( elem, name, value, pass ) {
+ var nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return undefined;
+ }
+
+ if ( pass && name in jQuery.attrFn ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( !("getAttribute" in elem) ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ var ret, hooks,
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // Normalize the name if needed
+ if ( notxml ) {
+ name = jQuery.attrFix[ name ] || name;
+
+ hooks = jQuery.attrHooks[ name ];
+
+ if ( !hooks ) {
+ // Use boolHook for boolean attributes
+ if ( rboolean.test( name ) ) {
+ hooks = boolHook;
+
+ // Use nodeHook if available( IE6/7 )
+ } else if ( nodeHook ) {
+ hooks = nodeHook;
+ }
+ }
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return undefined;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, "" + value );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, name ) {
+ var propName;
+ if ( elem.nodeType === 1 ) {
+ name = jQuery.attrFix[ name ] || name;
+
+ jQuery.attr( elem, name, "" );
+ elem.removeAttribute( name );
+
+ // Set corresponding property to false for boolean attributes
+ if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ propFix: {
+ tabindex: "tabIndex",
+ readonly: "readOnly",
+ "for": "htmlFor",
+ "class": "className",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ cellpadding: "cellPadding",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ usemap: "useMap",
+ frameborder: "frameBorder",
+ contenteditable: "contentEditable"
+ },
+
+ prop: function( elem, name, value ) {
+ var nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return undefined;
+ }
+
+ var ret, hooks,
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return (elem[ name ] = value);
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Add the tabindex propHook to attrHooks for back-compat
+jQuery.attrHooks.tabIndex = jQuery.propHooks.tabIndex;
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode;
+ return jQuery.prop( elem, name ) === true || ( attrNode = elem.getAttributeNode( name ) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !jQuery.support.getSetAttribute ) {
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ // Return undefined if nodeValue is empty string
+ return ret && ret.nodeValue !== "" ?
+ ret.nodeValue :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return (ret.nodeValue = value + "");
+ }
+ };
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return (elem.style.cssText = "" + value);
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0);
+ }
+ }
+ });
+});
+
+
+
+
+var rnamespaces = /\.(.*)$/,
+ rformElems = /^(?:textarea|input|select)$/i,
+ rperiod = /\./g,
+ rspaces = / /g,
+ rescape = /[^\w\s.|`]/g,
+ fcleanup = function( nm ) {
+ return nm.replace(rescape, "\\$&");
+ };
+
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code originated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+ // Bind an event to an element
+ // Original by Dean Edwards
+ add: function( elem, types, handler, data ) {
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ if ( handler === false ) {
+ handler = returnFalse;
+ } else if ( !handler ) {
+ // Fixes bug #7229. Fix recommended by jdalton
+ return;
+ }
+
+ var handleObjIn, handleObj;
+
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ }
+
+ // Make sure that the function being executed has a unique ID
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure
+ var elemData = jQuery._data( elem );
+
+ // If no elemData is found then we must be trying to bind to one of the
+ // banned noData elements
+ if ( !elemData ) {
+ return;
+ }
+
+ var events = elemData.events,
+ eventHandle = elemData.handle;
+
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.handle.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ }
+
+ // Add elem as a property of the handle function
+ // This is to prevent a memory leak with non-native events in IE.
+ eventHandle.elem = elem;
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = types.split(" ");
+
+ var type, i = 0, namespaces;
+
+ while ( (type = types[ i++ ]) ) {
+ handleObj = handleObjIn ?
+ jQuery.extend({}, handleObjIn) :
+ { handler: handler, data: data };
+
+ // Namespaced event handlers
+ if ( type.indexOf(".") > -1 ) {
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ handleObj.namespace = namespaces.slice(0).sort().join(".");
+
+ } else {
+ namespaces = [];
+ handleObj.namespace = "";
+ }
+
+ handleObj.type = type;
+ if ( !handleObj.guid ) {
+ handleObj.guid = handler.guid;
+ }
+
+ // Get the current list of functions bound to this event
+ var handlers = events[ type ],
+ special = jQuery.event.special[ type ] || {};
+
+ // Init the event handler queue
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+
+ // Check for a special event handler
+ // Only use addEventListener/attachEvent if the special
+ // events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add the function to the element's handler list
+ handlers.push( handleObj );
+
+ // Keep track of which events have been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, pos ) {
+ // don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ if ( handler === false ) {
+ handler = returnFalse;
+ }
+
+ var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
+ elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+ events = elemData && elemData.events;
+
+ if ( !elemData || !events ) {
+ return;
+ }
+
+ // types is actually an event object here
+ if ( types && types.type ) {
+ handler = types.handler;
+ types = types.type;
+ }
+
+ // Unbind all events for the element
+ if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
+ types = types || "";
+
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types );
+ }
+
+ return;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).unbind("mouseover mouseout", fn);
+ types = types.split(" ");
+
+ while ( (type = types[ i++ ]) ) {
+ origType = type;
+ handleObj = null;
+ all = type.indexOf(".") < 0;
+ namespaces = [];
+
+ if ( !all ) {
+ // Namespaced event handlers
+ namespaces = type.split(".");
+ type = namespaces.shift();
+
+ namespace = new RegExp("(^|\\.)" +
+ jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)");
+ }
+
+ eventType = events[ type ];
+
+ if ( !eventType ) {
+ continue;
+ }
+
+ if ( !handler ) {
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( all || namespace.test( handleObj.namespace ) ) {
+ jQuery.event.remove( elem, origType, handleObj.handler, j );
+ eventType.splice( j--, 1 );
+ }
+ }
+
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+
+ for ( j = pos || 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( handler.guid === handleObj.guid ) {
+ // remove the given handler for the given type
+ if ( all || namespace.test( handleObj.namespace ) ) {
+ if ( pos == null ) {
+ eventType.splice( j--, 1 );
+ }
+
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+
+ if ( pos != null ) {
+ break;
+ }
+ }
+ }
+
+ // remove generic event handler if no more handlers exist
+ if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ ret = null;
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ var handle = elemData.handle;
+ if ( handle ) {
+ handle.elem = null;
+ }
+
+ delete elemData.events;
+ delete elemData.handle;
+
+ if ( jQuery.isEmptyObject( elemData ) ) {
+ jQuery.removeData( elem, undefined, true );
+ }
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Event object or event type
+ var type = event.type || event,
+ namespaces = [],
+ exclusive;
+
+ if ( type.indexOf("!") >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf(".") >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join(".");
+ event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)");
+
+ // triggerHandler() and global events don't bubble or run the default action
+ if ( onlyHandlers || !elem ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ // Handle a global trigger
+ if ( !elem ) {
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ jQuery.each( jQuery.cache, function() {
+ // internalKey variable is just used to make it easier to find
+ // and potentially change this stuff later; currently it just
+ // points to jQuery.expando
+ var internalKey = jQuery.expando,
+ internalCache = this[ internalKey ];
+ if ( internalCache && internalCache.events && internalCache.events[ type ] ) {
+ jQuery.event.trigger( event, data, internalCache.handle.elem );
+ }
+ });
+ return;
+ }
+
+ // Don't do events on text and comment nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ event.target = elem;
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ var cur = elem,
+ // IE doesn't like method names with a colon (#3533, #8272)
+ ontype = type.indexOf(":") < 0 ? "on" + type : "";
+
+ // Fire event on the current element, then bubble up the DOM tree
+ do {
+ var handle = jQuery._data( cur, "handle" );
+
+ event.currentTarget = cur;
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+
+ // Trigger an inline bound script
+ if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) {
+ event.result = false;
+ event.preventDefault();
+ }
+
+ // Bubble up to document, then to window
+ cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window;
+ } while ( cur && !event.isPropagationStopped() );
+
+ // If nobody prevented the default action, do it now
+ if ( !event.isDefaultPrevented() ) {
+ var old,
+ special = jQuery.event.special[ type ] || {};
+
+ if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction)() check here because IE6/7 fails that test.
+ // IE<9 dies on focus to hidden element (#1486), may want to revisit a try/catch.
+ try {
+ if ( ontype && elem[ type ] ) {
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ }
+ } catch ( ieError ) {}
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+
+ jQuery.event.triggered = undefined;
+ }
+ }
+
+ return event.result;
+ },
+
+ handle: function( event ) {
+ event = jQuery.event.fix( event || window.event );
+ // Snapshot the handlers list since a called handler may add/remove events.
+ var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0),
+ run_all = !event.exclusive && !event.namespace,
+ args = Array.prototype.slice.call( arguments, 0 );
+
+ // Use the fix-ed Event rather than the (read-only) native event
+ args[0] = event;
+ event.currentTarget = this;
+
+ for ( var j = 0, l = handlers.length; j < l; j++ ) {
+ var handleObj = handlers[ j ];
+
+ // Triggered event must 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event.
+ if ( run_all || event.namespace_re.test( handleObj.namespace ) ) {
+ // Pass in a reference to the handler function itself
+ // So that we can later remove it
+ event.handler = handleObj.handler;
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ var ret = handleObj.handler.apply( this, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ if ( event.isImmediatePropagationStopped() ) {
+ break;
+ }
+ }
+ }
+ return event.result;
+ },
+
+ props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // store a copy of the original event object
+ // and "clone" to set read-only properties
+ var originalEvent = event;
+ event = jQuery.Event( originalEvent );
+
+ for ( var i = this.props.length, prop; i; ) {
+ prop = this.props[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary
+ if ( !event.target ) {
+ // Fixes #1925 where srcElement might not be defined either
+ event.target = event.srcElement || document;
+ }
+
+ // check if target is a textnode (safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && event.fromElement ) {
+ event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
+ }
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && event.clientX != null ) {
+ var eventDocument = event.target.ownerDocument || document,
+ doc = eventDocument.documentElement,
+ body = eventDocument.body;
+
+ event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
+ event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
+ }
+
+ // Add which for key events
+ if ( event.which == null && (event.charCode != null || event.keyCode != null) ) {
+ event.which = event.charCode != null ? event.charCode : event.keyCode;
+ }
+
+ // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+ if ( !event.metaKey && event.ctrlKey ) {
+ event.metaKey = event.ctrlKey;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && event.button !== undefined ) {
+ event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+ }
+
+ return event;
+ },
+
+ // Deprecated, use jQuery.guid instead
+ guid: 1E8,
+
+ // Deprecated, use jQuery.proxy instead
+ proxy: jQuery.proxy,
+
+ special: {
+ ready: {
+ // Make sure the ready event is setup
+ setup: jQuery.bindReady,
+ teardown: jQuery.noop
+ },
+
+ live: {
+ add: function( handleObj ) {
+ jQuery.event.add( this,
+ liveConvert( handleObj.origType, handleObj.selector ),
+ jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) );
+ },
+
+ remove: function( handleObj ) {
+ jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj );
+ }
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ }
+};
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ if ( elem.detachEvent ) {
+ elem.detachEvent( "on" + type, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !this.preventDefault ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // timeStamp is buggy for some events on Firefox(#3843)
+ // So we won't rely on the native value
+ this.timeStamp = jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function( event ) {
+
+ // Check if mouse(over|out) are still within the same parent element
+ var related = event.relatedTarget,
+ inside = false,
+ eventType = event.type;
+
+ event.type = event.data;
+
+ if ( related !== this ) {
+
+ if ( related ) {
+ inside = jQuery.contains( this, related );
+ }
+
+ if ( !inside ) {
+
+ jQuery.event.handle.apply( this, arguments );
+
+ event.type = eventType;
+ }
+ }
+},
+
+// In case of event delegation, we only need to rename the event.type,
+// liveHandler will take care of the rest.
+delegate = function( event ) {
+ event.type = event.data;
+ jQuery.event.handle.apply( this, arguments );
+};
+
+// Create mouseenter and mouseleave events
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ setup: function( data ) {
+ jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
+ },
+ teardown: function( data ) {
+ jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
+ }
+ };
+});
+
+// submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function( data, namespaces ) {
+ if ( !jQuery.nodeName( this, "form" ) ) {
+ jQuery.event.add(this, "click.specialSubmit", function( e ) {
+ // Avoid triggering error on non-existent type attribute in IE VML (#7071)
+ var elem = e.target,
+ type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : "";
+
+ if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
+ trigger( "submit", this, arguments );
+ }
+ });
+
+ jQuery.event.add(this, "keypress.specialSubmit", function( e ) {
+ var elem = e.target,
+ type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : "";
+
+ if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
+ trigger( "submit", this, arguments );
+ }
+ });
+
+ } else {
+ return false;
+ }
+ },
+
+ teardown: function( namespaces ) {
+ jQuery.event.remove( this, ".specialSubmit" );
+ }
+ };
+
+}
+
+// change delegation, happens here so we have bind.
+if ( !jQuery.support.changeBubbles ) {
+
+ var changeFilters,
+
+ getVal = function( elem ) {
+ var type = jQuery.nodeName( elem, "input" ) ? elem.type : "",
+ val = elem.value;
+
+ if ( type === "radio" || type === "checkbox" ) {
+ val = elem.checked;
+
+ } else if ( type === "select-multiple" ) {
+ val = elem.selectedIndex > -1 ?
+ jQuery.map( elem.options, function( elem ) {
+ return elem.selected;
+ }).join("-") :
+ "";
+
+ } else if ( jQuery.nodeName( elem, "select" ) ) {
+ val = elem.selectedIndex;
+ }
+
+ return val;
+ },
+
+ testChange = function testChange( e ) {
+ var elem = e.target, data, val;
+
+ if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) {
+ return;
+ }
+
+ data = jQuery._data( elem, "_change_data" );
+ val = getVal(elem);
+
+ // the current data will be also retrieved by beforeactivate
+ if ( e.type !== "focusout" || elem.type !== "radio" ) {
+ jQuery._data( elem, "_change_data", val );
+ }
+
+ if ( data === undefined || val === data ) {
+ return;
+ }
+
+ if ( data != null || val ) {
+ e.type = "change";
+ e.liveFired = undefined;
+ jQuery.event.trigger( e, arguments[1], elem );
+ }
+ };
+
+ jQuery.event.special.change = {
+ filters: {
+ focusout: testChange,
+
+ beforedeactivate: testChange,
+
+ click: function( e ) {
+ var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : "";
+
+ if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) {
+ testChange.call( this, e );
+ }
+ },
+
+ // Change has to be called before submit
+ // Keydown will be called before keypress, which is used in submit-event delegation
+ keydown: function( e ) {
+ var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : "";
+
+ if ( (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) ||
+ (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
+ type === "select-multiple" ) {
+ testChange.call( this, e );
+ }
+ },
+
+ // Beforeactivate happens also before the previous element is blurred
+ // with this event you can't trigger a change event, but you can store
+ // information
+ beforeactivate: function( e ) {
+ var elem = e.target;
+ jQuery._data( elem, "_change_data", getVal(elem) );
+ }
+ },
+
+ setup: function( data, namespaces ) {
+ if ( this.type === "file" ) {
+ return false;
+ }
+
+ for ( var type in changeFilters ) {
+ jQuery.event.add( this, type + ".specialChange", changeFilters[type] );
+ }
+
+ return rformElems.test( this.nodeName );
+ },
+
+ teardown: function( namespaces ) {
+ jQuery.event.remove( this, ".specialChange" );
+
+ return rformElems.test( this.nodeName );
+ }
+ };
+
+ changeFilters = jQuery.event.special.change.filters;
+
+ // Handle when the input is .focus()'d
+ changeFilters.focus = changeFilters.beforeactivate;
+}
+
+function trigger( type, elem, args ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ // Don't pass args or remember liveFired; they apply to the donor event.
+ var event = jQuery.extend( {}, args[ 0 ] );
+ event.type = type;
+ event.originalEvent = {};
+ event.liveFired = undefined;
+ jQuery.event.handle.call( elem, event );
+ if ( event.isDefaultPrevented() ) {
+ args[ 0 ].preventDefault();
+ }
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0;
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+
+ function handler( donor ) {
+ // Donor event is always a native one; fix it and switch its type.
+ // Let focusin/out handler cancel the donor focus/blur event.
+ var e = jQuery.event.fix( donor );
+ e.type = fix;
+ e.originalEvent = {};
+ jQuery.event.trigger( e, null, e.target );
+ if ( e.isDefaultPrevented() ) {
+ donor.preventDefault();
+ }
+ }
+ });
+}
+
+jQuery.each(["bind", "one"], function( i, name ) {
+ jQuery.fn[ name ] = function( type, data, fn ) {
+ var handler;
+
+ // Handle object literals
+ if ( typeof type === "object" ) {
+ for ( var key in type ) {
+ this[ name ](key, data, type[key], fn);
+ }
+ return this;
+ }
+
+ if ( arguments.length === 2 || data === false ) {
+ fn = data;
+ data = undefined;
+ }
+
+ if ( name === "one" ) {
+ handler = function( event ) {
+ jQuery( this ).unbind( event, handler );
+ return fn.apply( this, arguments );
+ };
+ handler.guid = fn.guid || jQuery.guid++;
+ } else {
+ handler = fn;
+ }
+
+ if ( type === "unload" && name !== "one" ) {
+ this.one( type, data, fn );
+
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ jQuery.event.add( this[i], type, handler, data );
+ }
+ }
+
+ return this;
+ };
+});
+
+jQuery.fn.extend({
+ unbind: function( type, fn ) {
+ // Handle object literals
+ if ( typeof type === "object" && !type.preventDefault ) {
+ for ( var key in type ) {
+ this.unbind(key, type[key]);
+ }
+
+ } else {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ jQuery.event.remove( this[i], type, fn );
+ }
+ }
+
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.live( types, data, fn, selector );
+ },
+
+ undelegate: function( selector, types, fn ) {
+ if ( arguments.length === 0 ) {
+ return this.unbind( "live" );
+
+ } else {
+ return this.die( types, null, fn, selector );
+ }
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ return jQuery.event.trigger( type, data, this[0], true );
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+var liveMap = {
+ focus: "focusin",
+ blur: "focusout",
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+};
+
+jQuery.each(["live", "die"], function( i, name ) {
+ jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
+ var type, i = 0, match, namespaces, preType,
+ selector = origSelector || this.selector,
+ context = origSelector ? this : jQuery( this.context );
+
+ if ( typeof types === "object" && !types.preventDefault ) {
+ for ( var key in types ) {
+ context[ name ]( key, data, types[key], selector );
+ }
+
+ return this;
+ }
+
+ if ( name === "die" && !types &&
+ origSelector && origSelector.charAt(0) === "." ) {
+
+ context.unbind( origSelector );
+
+ return this;
+ }
+
+ if ( data === false || jQuery.isFunction( data ) ) {
+ fn = data || returnFalse;
+ data = undefined;
+ }
+
+ types = (types || "").split(" ");
+
+ while ( (type = types[ i++ ]) != null ) {
+ match = rnamespaces.exec( type );
+ namespaces = "";
+
+ if ( match ) {
+ namespaces = match[0];
+ type = type.replace( rnamespaces, "" );
+ }
+
+ if ( type === "hover" ) {
+ types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
+ continue;
+ }
+
+ preType = type;
+
+ if ( liveMap[ type ] ) {
+ types.push( liveMap[ type ] + namespaces );
+ type = type + namespaces;
+
+ } else {
+ type = (liveMap[ type ] || type) + namespaces;
+ }
+
+ if ( name === "live" ) {
+ // bind live handler
+ for ( var j = 0, l = context.length; j < l; j++ ) {
+ jQuery.event.add( context[j], "live." + liveConvert( type, selector ),
+ { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
+ }
+
+ } else {
+ // unbind live handler
+ context.unbind( "live." + liveConvert( type, selector ), fn );
+ }
+ }
+
+ return this;
+ };
+});
+
+function liveHandler( event ) {
+ var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
+ elems = [],
+ selectors = [],
+ events = jQuery._data( this, "events" );
+
+ // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)
+ if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) {
+ return;
+ }
+
+ if ( event.namespace ) {
+ namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)");
+ }
+
+ event.liveFired = this;
+
+ var live = events.live.slice(0);
+
+ for ( j = 0; j < live.length; j++ ) {
+ handleObj = live[j];
+
+ if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) {
+ selectors.push( handleObj.selector );
+
+ } else {
+ live.splice( j--, 1 );
+ }
+ }
+
+ match = jQuery( event.target ).closest( selectors, event.currentTarget );
+
+ for ( i = 0, l = match.length; i < l; i++ ) {
+ close = match[i];
+
+ for ( j = 0; j < live.length; j++ ) {
+ handleObj = live[j];
+
+ if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) {
+ elem = close.elem;
+ related = null;
+
+ // Those two events require additional checking
+ if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
+ event.type = handleObj.preType;
+ related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
+
+ // Make sure not to accidentally match a child element with the same selector
+ if ( related && jQuery.contains( elem, related ) ) {
+ related = elem;
+ }
+ }
+
+ if ( !related || related !== elem ) {
+ elems.push({ elem: elem, handleObj: handleObj, level: close.level });
+ }
+ }
+ }
+ }
+
+ for ( i = 0, l = elems.length; i < l; i++ ) {
+ match = elems[i];
+
+ if ( maxLevel && match.level > maxLevel ) {
+ break;
+ }
+
+ event.currentTarget = match.elem;
+ event.data = match.handleObj.data;
+ event.handleObj = match.handleObj;
+
+ ret = match.handleObj.origHandler.apply( match.elem, arguments );
+
+ if ( ret === false || event.isPropagationStopped() ) {
+ maxLevel = match.level;
+
+ if ( ret === false ) {
+ stop = false;
+ }
+ if ( event.isImmediatePropagationStopped() ) {
+ break;
+ }
+ }
+ }
+
+ return stop;
+}
+
+function liveConvert( type, selector ) {
+ return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspaces, "&");
+}
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.bind( name, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( jQuery.attrFn ) {
+ jQuery.attrFn[ name ] = true;
+ }
+});
+
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true,
+ rBackslash = /\\/g,
+ rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+
+ var origContext = context;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var m, set, checkSet, extra, ret, cur, pop, i,
+ prune = true,
+ contextXML = Sizzle.isXML( context ),
+ parts = [],
+ soFar = selector;
+
+ // Reset the position of the chunker regexp (start from head)
+ do {
+ chunker.exec( "" );
+ m = chunker.exec( soFar );
+
+ if ( m ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+ } while ( m );
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context );
+
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] ) {
+ selector += parts.shift();
+ }
+
+ set = posProcess( selector, set );
+ }
+ }
+
+ } else {
+ // Take a shortcut and set the context if the root selector is an ID
+ // (but not if it'll be faster if the inner selector is an ID)
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+ ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set )[0] :
+ ret.set[0];
+ }
+
+ if ( context ) {
+ ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+ set = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set ) :
+ ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray( set );
+
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ cur = parts.pop();
+ pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ Sizzle.error( cur || selector );
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+
+ } else if ( context && context.nodeType === 1 ) {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+
+ } else {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+ return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+ return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+ var set;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+ var match,
+ type = Expr.order[i];
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ var left = match[1];
+ match.splice( 1, 1 );
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace( rBackslash, "" );
+ set = Expr.find[ type ]( match, context, isXML );
+
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = typeof context.getElementsByTagName !== "undefined" ?
+ context.getElementsByTagName( "*" ) :
+ [];
+ }
+
+ return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+ var match, anyFound,
+ old = expr,
+ result = [],
+ curLoop = set,
+ isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+ while ( expr && set.length ) {
+ for ( var type in Expr.filter ) {
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+ var found, item,
+ filter = Expr.filter[ type ],
+ left = match[1];
+
+ anyFound = false;
+
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) === "\\" ) {
+ continue;
+ }
+
+ if ( curLoop === result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ var pass = not ^ !!found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+
+ } else {
+ curLoop[i] = false;
+ }
+
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr === old ) {
+ if ( anyFound == null ) {
+ Sizzle.error( expr );
+
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+ throw "Syntax error, unrecognized expression: " + msg;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+ },
+
+ leftMatch: {},
+
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+
+ attrHandle: {
+ href: function( elem ) {
+ return elem.getAttribute( "href" );
+ },
+ type: function( elem ) {
+ return elem.getAttribute( "type" );
+ }
+ },
+
+ relative: {
+ "+": function(checkSet, part){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !rNonWord.test( part ),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag ) {
+ part = part.toLowerCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+
+ ">": function( checkSet, part ) {
+ var elem,
+ isPartStr = typeof part === "string",
+ i = 0,
+ l = checkSet.length;
+
+ if ( isPartStr && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+ }
+ }
+
+ } else {
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+
+ "": function(checkSet, part, isXML){
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+ },
+
+ "~": function( checkSet, part, isXML ) {
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+ }
+ },
+
+ find: {
+ ID: function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ },
+
+ NAME: function( match, context ) {
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [],
+ results = context.getElementsByName( match[1] );
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+
+ TAG: function( match, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( match[1] );
+ }
+ }
+ },
+ preFilter: {
+ CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+ match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+ if ( !inplace ) {
+ result.push( elem );
+ }
+
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+
+ ID: function( match ) {
+ return match[1].replace( rBackslash, "" );
+ },
+
+ TAG: function( match, curLoop ) {
+ return match[1].replace( rBackslash, "" ).toLowerCase();
+ },
+
+ CHILD: function( match ) {
+ if ( match[1] === "nth" ) {
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ match[2] = match[2].replace(/^\+|\s*/g, '');
+
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+ match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+ else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+
+ ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+ var name = match[1] = match[1].replace( rBackslash, "" );
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ // Handle if an un-quoted value was used
+ match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+
+ PSEUDO: function( match, curLoop, inplace, result, not ) {
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+
+ return false;
+ }
+
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+
+ POS: function( match ) {
+ match.unshift( true );
+
+ return match;
+ }
+ },
+
+ filters: {
+ enabled: function( elem ) {
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+
+ disabled: function( elem ) {
+ return elem.disabled === true;
+ },
+
+ checked: function( elem ) {
+ return elem.checked === true;
+ },
+
+ selected: function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ parent: function( elem ) {
+ return !!elem.firstChild;
+ },
+
+ empty: function( elem ) {
+ return !elem.firstChild;
+ },
+
+ has: function( elem, i, match ) {
+ return !!Sizzle( match[3], elem ).length;
+ },
+
+ header: function( elem ) {
+ return (/h\d/i).test( elem.nodeName );
+ },
+
+ text: function( elem ) {
+ var attr = elem.getAttribute( "type" ), type = elem.type;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+ },
+
+ radio: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+ },
+
+ checkbox: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+ },
+
+ file: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+ },
+
+ password: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+ },
+
+ submit: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "submit" === elem.type;
+ },
+
+ image: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+ },
+
+ reset: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "reset" === elem.type;
+ },
+
+ button: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && "button" === elem.type || name === "button";
+ },
+
+ input: function( elem ) {
+ return (/input|select|textarea|button/i).test( elem.nodeName );
+ },
+
+ focus: function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ }
+ },
+ setFilters: {
+ first: function( elem, i ) {
+ return i === 0;
+ },
+
+ last: function( elem, i, match, array ) {
+ return i === array.length - 1;
+ },
+
+ even: function( elem, i ) {
+ return i % 2 === 0;
+ },
+
+ odd: function( elem, i ) {
+ return i % 2 === 1;
+ },
+
+ lt: function( elem, i, match ) {
+ return i < match[3] - 0;
+ },
+
+ gt: function( elem, i, match ) {
+ return i > match[3] - 0;
+ },
+
+ nth: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ },
+
+ eq: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ }
+ },
+ filter: {
+ PSEUDO: function( elem, match, i, array ) {
+ var name = match[1],
+ filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var j = 0, l = not.length; j < l; j++ ) {
+ if ( not[j] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ } else {
+ Sizzle.error( name );
+ }
+ },
+
+ CHILD: function( elem, match ) {
+ var type = match[1],
+ node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ case "nth":
+ var first = match[2],
+ last = match[3];
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ var doneName = match[0],
+ parent = elem.parentNode;
+
+ if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+ var count = 0;
+
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+
+ parent.sizcache = doneName;
+ }
+
+ var diff = elem.nodeIndex - last;
+
+ if ( first === 0 ) {
+ return diff === 0;
+
+ } else {
+ return ( diff % first === 0 && diff / first >= 0 );
+ }
+ }
+ },
+
+ ID: function( elem, match ) {
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+
+ TAG: function( elem, match ) {
+ return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
+ },
+
+ CLASS: function( elem, match ) {
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+
+ ATTR: function( elem, match ) {
+ var name = match[1],
+ result = Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value !== check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+
+ POS: function( elem, match, i, array ) {
+ var name = match[2],
+ filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS,
+ fescape = function(all, num){
+ return "\\" + (num - 0 + 1);
+ };
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+
+var makeArray = function( array, results ) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+ makeArray = function( array, results ) {
+ var i = 0,
+ ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+
+ } else {
+ for ( ; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ return a.compareDocumentPosition ? -1 : 1;
+ }
+
+ return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+ };
+
+} else {
+ sortOrder = function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+ siblingCheck = function( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+ };
+}
+
+// Utility function for retreiving the text value of an array of DOM nodes
+Sizzle.getText = function( elems ) {
+ var ret = "", elem;
+
+ for ( var i = 0; elems[i]; i++ ) {
+ elem = elems[i];
+
+ // Get the text from text nodes and CDATA nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+ ret += elem.nodeValue;
+
+ // Traverse everything else, except comment nodes
+ } else if ( elem.nodeType !== 8 ) {
+ ret += Sizzle.getText( elem.childNodes );
+ }
+ }
+
+ return ret;
+};
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("div"),
+ id = "script" + (new Date()).getTime(),
+ root = document.documentElement;
+
+ form.innerHTML = "<a name='" + id + "'/>";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( document.getElementById( id ) ) {
+ Expr.find.ID = function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+
+ return m ?
+ m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+ [m] :
+ undefined :
+ [];
+ }
+ };
+
+ Expr.filter.ID = function( elem, match ) {
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+
+ // release memory in IE
+ root = form = null;
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function( match, context ) {
+ var results = context.getElementsByTagName( match[1] );
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "<a href='#'></a>";
+
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+
+ Expr.attrHandle.href = function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ };
+ }
+
+ // release memory in IE
+ div = null;
+})();
+
+if ( document.querySelectorAll ) {
+ (function(){
+ var oldSizzle = Sizzle,
+ div = document.createElement("div"),
+ id = "__sizzle__";
+
+ div.innerHTML = "<p class='TEST'></p>";
+
+ // Safari can't handle uppercase or unicode characters when
+ // in quirks mode.
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function( query, context, extra, seed ) {
+ context = context || document;
+
+ // Only use querySelectorAll on non-XML documents
+ // (ID selectors don't work in non-HTML documents)
+ if ( !seed && !Sizzle.isXML(context) ) {
+ // See if we find a selector to speed up
+ var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+
+ if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+ // Speed-up: Sizzle("TAG")
+ if ( match[1] ) {
+ return makeArray( context.getElementsByTagName( query ), extra );
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+ return makeArray( context.getElementsByClassName( match[2] ), extra );
+ }
+ }
+
+ if ( context.nodeType === 9 ) {
+ // Speed-up: Sizzle("body")
+ // The body element only exists once, optimize finding it
+ if ( query === "body" && context.body ) {
+ return makeArray( [ context.body ], extra );
+
+ // Speed-up: Sizzle("#ID")
+ } else if ( match && match[3] ) {
+ var elem = context.getElementById( match[3] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id === match[3] ) {
+ return makeArray( [ elem ], extra );
+ }
+
+ } else {
+ return makeArray( [], extra );
+ }
+ }
+
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(qsaError) {}
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ var oldContext = context,
+ old = context.getAttribute( "id" ),
+ nid = old || id,
+ hasParent = context.parentNode,
+ relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+ if ( !old ) {
+ context.setAttribute( "id", nid );
+ } else {
+ nid = nid.replace( /'/g, "\\$&" );
+ }
+ if ( relativeHierarchySelector && hasParent ) {
+ context = context.parentNode;
+ }
+
+ try {
+ if ( !relativeHierarchySelector || hasParent ) {
+ return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+ }
+
+ } catch(pseudoError) {
+ } finally {
+ if ( !old ) {
+ oldContext.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ // release memory in IE
+ div = null;
+ })();
+}
+
+(function(){
+ var html = document.documentElement,
+ matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+ if ( matches ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9 fails this)
+ var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+ pseudoWorks = false;
+
+ try {
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( document.documentElement, "[test!='']:sizzle" );
+
+ } catch( pseudoError ) {
+ pseudoWorks = true;
+ }
+
+ Sizzle.matchesSelector = function( node, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+ if ( !Sizzle.isXML( node ) ) {
+ try {
+ if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+ var ret = matches.call( node, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || !disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9, so check for that
+ node.document && node.document.nodeType !== 11 ) {
+ return ret;
+ }
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle(expr, null, null, [node]).length > 0;
+ };
+ }
+})();
+
+(function(){
+ var div = document.createElement("div");
+
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+ // Opera can't find a second classname (in 9.6)
+ // Also, make sure that getElementsByClassName actually exists
+ if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+ return;
+ }
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 ) {
+ return;
+ }
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function( match, context, isXML ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ // release memory in IE
+ div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName.toLowerCase() === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+if ( document.documentElement.contains ) {
+ Sizzle.contains = function( a, b ) {
+ return a !== b && (a.contains ? a.contains(b) : true);
+ };
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+ Sizzle.contains = function( a, b ) {
+ return !!(a.compareDocumentPosition(b) & 16);
+ };
+
+} else {
+ Sizzle.contains = function() {
+ return false;
+ };
+}
+
+Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context ) {
+ var match,
+ tmpSet = [],
+ later = "",
+ root = context.nodeType ? [context] : context;
+
+ // Position selectors must be done after the filter
+ // And so must :not(positional) so we move all PSEUDOs to the end
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+ // Note: This RegExp should be improved, or likely pulled from Sizzle
+ rmultiselector = /,/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ slice = Array.prototype.slice,
+ POS = jQuery.expr.match.POS,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var self = this,
+ i, l;
+
+ if ( typeof selector !== "string" ) {
+ return jQuery( selector ).filter(function() {
+ for ( i = 0, l = self.length; i < l; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ });
+ }
+
+ var ret = this.pushStack( "", "find", selector ),
+ length, n, r;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( n = length; n < ret.length; n++ ) {
+ for ( r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var targets = jQuery( target );
+ return this.filter(function() {
+ for ( var i = 0, l = targets.length; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && ( typeof selector === "string" ?
+ jQuery.filter( selector, this ).length > 0 :
+ this.filter( selector ).length > 0 );
+ },
+
+ closest: function( selectors, context ) {
+ var ret = [], i, l, cur = this[0];
+
+ // Array
+ if ( jQuery.isArray( selectors ) ) {
+ var match, selector,
+ matches = {},
+ level = 1;
+
+ if ( cur && selectors.length ) {
+ for ( i = 0, l = selectors.length; i < l; i++ ) {
+ selector = selectors[i];
+
+ if ( !matches[ selector ] ) {
+ matches[ selector ] = POS.test( selector ) ?
+ jQuery( selector, context || this.context ) :
+ selector;
+ }
+ }
+
+ while ( cur && cur.ownerDocument && cur !== context ) {
+ for ( selector in matches ) {
+ match = matches[ selector ];
+
+ if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) {
+ ret.push({ selector: selector, elem: cur, level: level });
+ }
+ }
+
+ cur = cur.parentNode;
+ level++;
+ }
+ }
+
+ return ret;
+ }
+
+ // String
+ var pos = POS.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+
+ } else {
+ cur = cur.parentNode;
+ if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
+ break;
+ }
+ }
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ }
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return jQuery.nth( elem, 2, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return jQuery.nth( elem, 2, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( elem.parentNode.firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.makeArray( elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until ),
+ // The variable 'args' was introduced in
+ // https://github.com/jquery/jquery/commit/52a0238
+ // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed.
+ // http://code.google.com/p/v8/issues/detail?id=1050
+ args = slice.call(arguments);
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, args.join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function( cur, result, dir, elem ) {
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] ) {
+ if ( cur.nodeType === 1 && ++num === result ) {
+ break;
+ }
+ }
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+ // Can't pass null or undefined to indexOf in Firefox 4
+ // Set to 0 to skip string check
+ qualifier = qualifier || 0;
+
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return (elem === qualifier) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return (jQuery.inArray( elem, qualifier ) >= 0) === keep;
+ });
+}
+
+
+
+
+var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+ rtagName = /<([\w:]+)/,
+ rtbody = /<tbody/i,
+ rhtml = /<|&#?\w+;/,
+ rnocache = /<(?:script|object|embed|option|style)/i,
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rscriptType = /\/(java|ecma)script/i,
+ rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
+ wrapMap = {
+ option: [ 1, "<select multiple='multiple'>", "</select>" ],
+ legend: [ 1, "<fieldset>", "</fieldset>" ],
+ thead: [ 1, "<table>", "</table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+ col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+ area: [ 1, "<map>", "</map>" ],
+ _default: [ 0, "", "" ]
+ };
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+ wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+ text: function( text ) {
+ if ( jQuery.isFunction(text) ) {
+ return this.each(function(i) {
+ var self = jQuery( this );
+
+ self.text( text.call(this, i, self.text()) );
+ });
+ }
+
+ if ( typeof text !== "object" && text !== undefined ) {
+ return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+ }
+
+ return jQuery.text( this );
+ },
+
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ return this.each(function() {
+ jQuery( this ).wrapAll( html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.insertBefore( elem, this.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this );
+ });
+ } else if ( arguments.length ) {
+ var set = jQuery(arguments[0]);
+ set.push.apply( set, this.toArray() );
+ return this.pushStack( set, "before", arguments );
+ }
+ },
+
+ after: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ } else if ( arguments.length ) {
+ var set = this.pushStack( this, "after", arguments );
+ set.push.apply( set, jQuery(arguments[0]).toArray() );
+ return set;
+ }
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ jQuery.cleanData( [ elem ] );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function () {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ if ( value === undefined ) {
+ return this[0] && this[0].nodeType === 1 ?
+ this[0].innerHTML.replace(rinlinejQuery, "") :
+ null;
+
+ // See if we can take a shortcut and just use innerHTML
+ } else if ( typeof value === "string" && !rnocache.test( value ) &&
+ (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
+ !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
+
+ value = value.replace(rxhtmlTag, "<$1></$2>");
+
+ try {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( this[i].nodeType === 1 ) {
+ jQuery.cleanData( this[i].getElementsByTagName("*") );
+ this[i].innerHTML = value;
+ }
+ }
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {
+ this.empty().append( value );
+ }
+
+ } else if ( jQuery.isFunction( value ) ) {
+ this.each(function(i){
+ var self = jQuery( this );
+
+ self.html( value.call(this, i, self.html()) );
+ });
+
+ } else {
+ this.empty().append( value );
+ }
+
+ return this;
+ },
+
+ replaceWith: function( value ) {
+ if ( this[0] && this[0].parentNode ) {
+ // Make sure that the elements are removed from the DOM before they are inserted
+ // this can help fix replacing a parent with child elements
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this), old = self.html();
+ self.replaceWith( value.call( this, i, old ) );
+ });
+ }
+
+ if ( typeof value !== "string" ) {
+ value = jQuery( value ).detach();
+ }
+
+ return this.each(function() {
+ var next = this.nextSibling,
+ parent = this.parentNode;
+
+ jQuery( this ).remove();
+
+ if ( next ) {
+ jQuery(next).before( value );
+ } else {
+ jQuery(parent).append( value );
+ }
+ });
+ } else {
+ return this.length ?
+ this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+ this;
+ }
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, table, callback ) {
+ var results, first, fragment, parent,
+ value = args[0],
+ scripts = [];
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+ return this.each(function() {
+ jQuery(this).domManip( args, table, callback, true );
+ });
+ }
+
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ args[0] = value.call(this, i, table ? self.html() : undefined);
+ self.domManip( args, table, callback );
+ });
+ }
+
+ if ( this[0] ) {
+ parent = value && value.parentNode;
+
+ // If we're in a fragment, just use that instead of building a new one
+ if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+ results = { fragment: parent };
+
+ } else {
+ results = jQuery.buildFragment( args, this, scripts );
+ }
+
+ fragment = results.fragment;
+
+ if ( fragment.childNodes.length === 1 ) {
+ first = fragment = fragment.firstChild;
+ } else {
+ first = fragment.firstChild;
+ }
+
+ if ( first ) {
+ table = table && jQuery.nodeName( first, "tr" );
+
+ for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
+ callback.call(
+ table ?
+ root(this[i], first) :
+ this[i],
+ // Make sure that we do not leak memory by inadvertently discarding
+ // the original fragment (which might have attached data) instead of
+ // using it; in addition, use the original fragment object for the last
+ // item instead of first because it can end up being emptied incorrectly
+ // in certain situations (Bug #8070).
+ // Fragments from the fragment cache must always be cloned and never used
+ // in place.
+ results.cacheable || (l > 1 && i < lastIndex) ?
+ jQuery.clone( fragment, true, true ) :
+ fragment
+ );
+ }
+ }
+
+ if ( scripts.length ) {
+ jQuery.each( scripts, evalScript );
+ }
+ }
+
+ return this;
+ }
+});
+
+function root( elem, cur ) {
+ return jQuery.nodeName(elem, "table") ?
+ (elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+ elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+
+ if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+ return;
+ }
+
+ var internalKey = jQuery.expando,
+ oldData = jQuery.data( src ),
+ curData = jQuery.data( dest, oldData );
+
+ // Switch to use the internal data object, if it exists, for the next
+ // stage of data copying
+ if ( (oldData = oldData[ internalKey ]) ) {
+ var events = oldData.events;
+ curData = curData[ internalKey ] = jQuery.extend({}, oldData);
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( var type in events ) {
+ for ( var i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data );
+ }
+ }
+ }
+ }
+}
+
+function cloneFixAttributes( src, dest ) {
+ var nodeName;
+
+ // We do not need to do anything for non-Elements
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // clearAttributes removes the attributes, which we don't want,
+ // but also removes the attachEvent events, which we *do* want
+ if ( dest.clearAttributes ) {
+ dest.clearAttributes();
+ }
+
+ // mergeAttributes, in contrast, only merges back on the
+ // original attributes, not the events
+ if ( dest.mergeAttributes ) {
+ dest.mergeAttributes( src );
+ }
+
+ nodeName = dest.nodeName.toLowerCase();
+
+ // IE6-8 fail to clone children inside object elements that use
+ // the proprietary classid attribute value (rather than the type
+ // attribute) to identify the type of content to display
+ if ( nodeName === "object" ) {
+ dest.outerHTML = src.outerHTML;
+
+ } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+ // IE6-8 fails to persist the checked state of a cloned checkbox
+ // or radio button. Worse, IE6-7 fail to give the cloned element
+ // a checked appearance if the defaultChecked value isn't also set
+ if ( src.checked ) {
+ dest.defaultChecked = dest.checked = src.checked;
+ }
+
+ // IE6-7 get confused and end up setting the value of a cloned
+ // checkbox/radio button to an empty string instead of "on"
+ if ( dest.value !== src.value ) {
+ dest.value = src.value;
+ }
+
+ // IE6-8 fails to return the selected option to the default selected
+ // state when cloning options
+ } else if ( nodeName === "option" ) {
+ dest.selected = src.defaultSelected;
+
+ // IE6-8 fails to set the defaultValue to the correct value when
+ // cloning other types of input fields
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+ }
+
+ // Event data gets referenced instead of copied if the expando
+ // gets copied too
+ dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+ var fragment, cacheable, cacheresults, doc;
+
+ // nodes may contain either an explicit document object,
+ // a jQuery collection or context object.
+ // If nodes[0] contains a valid object to assign to doc
+ if ( nodes && nodes[0] ) {
+ doc = nodes[0].ownerDocument || nodes[0];
+ }
+
+ // Ensure that an attr object doesn't incorrectly stand in as a document object
+ // Chrome and Firefox seem to allow this to occur and will throw exception
+ // Fixes #8950
+ if ( !doc.createDocumentFragment ) {
+ doc = document;
+ }
+
+ // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+ // Cloning options loses the selected state, so don't cache them
+ // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+ // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+ if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document &&
+ args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
+
+ cacheable = true;
+
+ cacheresults = jQuery.fragments[ args[0] ];
+ if ( cacheresults && cacheresults !== 1 ) {
+ fragment = cacheresults;
+ }
+ }
+
+ if ( !fragment ) {
+ fragment = doc.createDocumentFragment();
+ jQuery.clean( args, doc, fragment, scripts );
+ }
+
+ if ( cacheable ) {
+ jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1;
+ }
+
+ return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = [],
+ insert = jQuery( selector ),
+ parent = this.length === 1 && this[0].parentNode;
+
+ if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+ insert[ original ]( this[0] );
+ return this;
+
+ } else {
+ for ( var i = 0, l = insert.length; i < l; i++ ) {
+ var elems = (i > 0 ? this.clone(true) : this).get();
+ jQuery( insert[i] )[ original ]( elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, insert.selector );
+ }
+ };
+});
+
+function getAll( elem ) {
+ if ( "getElementsByTagName" in elem ) {
+ return elem.getElementsByTagName( "*" );
+
+ } else if ( "querySelectorAll" in elem ) {
+ return elem.querySelectorAll( "*" );
+
+ } else {
+ return [];
+ }
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+ if ( elem.type === "checkbox" || elem.type === "radio" ) {
+ elem.defaultChecked = elem.checked;
+ }
+}
+// Finds all inputs and passes them to fixDefaultChecked
+function findInputs( elem ) {
+ if ( jQuery.nodeName( elem, "input" ) ) {
+ fixDefaultChecked( elem );
+ } else if ( "getElementsByTagName" in elem ) {
+ jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+ }
+}
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var clone = elem.cloneNode(true),
+ srcElements,
+ destElements,
+ i;
+
+ if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+ (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+ // IE copies events bound via attachEvent when using cloneNode.
+ // Calling detachEvent on the clone will also remove the events
+ // from the original. In order to get around this, we use some
+ // proprietary methods to clear the events. Thanks to MooTools
+ // guys for this hotness.
+
+ cloneFixAttributes( elem, clone );
+
+ // Using Sizzle here is crazy slow, so we use getElementsByTagName
+ // instead
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ // Weird iteration because IE will replace the length property
+ // with an element if you are cloning the body and one of the
+ // elements on the page has a name or id of "length"
+ for ( i = 0; srcElements[i]; ++i ) {
+ // Ensure that the destination node is not null; Fixes #9587
+ if ( destElements[i] ) {
+ cloneFixAttributes( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ cloneCopyEvent( elem, clone );
+
+ if ( deepDataAndEvents ) {
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ for ( i = 0; srcElements[i]; ++i ) {
+ cloneCopyEvent( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ srcElements = destElements = null;
+
+ // Return the cloned set
+ return clone;
+ },
+
+ clean: function( elems, context, fragment, scripts ) {
+ var checkScriptType;
+
+ context = context || document;
+
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if ( typeof context.createElement === "undefined" ) {
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+ }
+
+ var ret = [], j;
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ if ( typeof elem === "number" ) {
+ elem += "";
+ }
+
+ if ( !elem ) {
+ continue;
+ }
+
+ // Convert html string into DOM nodes
+ if ( typeof elem === "string" ) {
+ if ( !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+ } else {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
+ wrap = wrapMap[ tag ] || wrapMap._default,
+ depth = wrap[0],
+ div = context.createElement("div");
+
+ // Go to html and back, then peel off extra wrappers
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ var hasBody = rtbody.test(elem),
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] === "<table>" && !hasBody ?
+ div.childNodes :
+ [];
+
+ for ( j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
+ }
+ }
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
+
+ elem = div.childNodes;
+ }
+ }
+
+ // Resets defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ var len;
+ if ( !jQuery.support.appendChecked ) {
+ if ( elem[0] && typeof (len = elem.length) === "number" ) {
+ for ( j = 0; j < len; j++ ) {
+ findInputs( elem[j] );
+ }
+ } else {
+ findInputs( elem );
+ }
+ }
+
+ if ( elem.nodeType ) {
+ ret.push( elem );
+ } else {
+ ret = jQuery.merge( ret, elem );
+ }
+ }
+
+ if ( fragment ) {
+ checkScriptType = function( elem ) {
+ return !elem.type || rscriptType.test( elem.type );
+ };
+ for ( i = 0; ret[i]; i++ ) {
+ if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+ scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+
+ } else {
+ if ( ret[i].nodeType === 1 ) {
+ var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType );
+
+ ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+ }
+ fragment.appendChild( ret[i] );
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ cleanData: function( elems ) {
+ var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special,
+ deleteExpando = jQuery.support.deleteExpando;
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+ continue;
+ }
+
+ id = elem[ jQuery.expando ];
+
+ if ( id ) {
+ data = cache[ id ] && cache[ id ][ internalKey ];
+
+ if ( data && data.events ) {
+ for ( var type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+
+ // Null the DOM reference to avoid IE6/7/8 leak (#7054)
+ if ( data.handle ) {
+ data.handle.elem = null;
+ }
+ }
+
+ if ( deleteExpando ) {
+ delete elem[ jQuery.expando ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( jQuery.expando );
+ }
+
+ delete cache[ id ];
+ }
+ }
+ }
+});
+
+function evalScript( i, elem ) {
+ if ( elem.src ) {
+ jQuery.ajax({
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+ } else {
+ jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+}
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+ ropacity = /opacity=([^)]*)/,
+ // fixed for IE9, see #8346
+ rupper = /([A-Z]|^ms)/g,
+ rnumpx = /^-?\d+(?:px)?$/i,
+ rnum = /^-?\d/,
+ rrelNum = /^([\-+])=([\-+.\de]+)/,
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssWidth = [ "Left", "Right" ],
+ cssHeight = [ "Top", "Bottom" ],
+ curCSS,
+
+ getComputedStyle,
+ currentStyle;
+
+jQuery.fn.css = function( name, value ) {
+ // Setting 'undefined' is a no-op
+ if ( arguments.length === 2 && value === undefined ) {
+ return this;
+ }
+
+ return jQuery.access( this, name, value, true, function( elem, name, value ) {
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ });
+};
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity", "opacity" );
+ return ret === "" ? "1" : ret;
+
+ } else {
+ return elem.style.opacity;
+ }
+ }
+ }
+ },
+
+ // Exclude the following css properties to add px
+ cssNumber: {
+ "fillOpacity": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, origName = jQuery.camelCase( name ),
+ style = elem.style, hooks = jQuery.cssHooks[ origName ];
+
+ name = jQuery.cssProps[ origName ] || origName;
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // convert relative number strings (+= or -=) to relative numbers. #7345
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that NaN and null values aren't set. See: #7116
+ if ( value == null || type === "number" && isNaN( value ) ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+ // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+ // Fixes bug #5509
+ try {
+ style[ name ] = value;
+ } catch(e) {}
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, extra ) {
+ var ret, hooks;
+
+ // Make sure that we're working with the right name
+ name = jQuery.camelCase( name );
+ hooks = jQuery.cssHooks[ name ];
+ name = jQuery.cssProps[ name ] || name;
+
+ // cssFloat needs a special treatment
+ if ( name === "cssFloat" ) {
+ name = "float";
+ }
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
+ return ret;
+
+ // Otherwise, if a way to get the computed value exists, use that
+ } else if ( curCSS ) {
+ return curCSS( elem, name );
+ }
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( var name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+ }
+});
+
+// DEPRECATED, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+jQuery.each(["height", "width"], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ var val;
+
+ if ( computed ) {
+ if ( elem.offsetWidth !== 0 ) {
+ return getWH( elem, name, extra );
+ } else {
+ jQuery.swap( elem, cssShow, function() {
+ val = getWH( elem, name, extra );
+ });
+ }
+
+ return val;
+ }
+ },
+
+ set: function( elem, value ) {
+ if ( rnumpx.test( value ) ) {
+ // ignore negative width and height values #1599
+ value = parseFloat( value );
+
+ if ( value >= 0 ) {
+ return value + "px";
+ }
+
+ } else {
+ return value;
+ }
+ }
+ };
+});
+
+if ( !jQuery.support.opacity ) {
+ jQuery.cssHooks.opacity = {
+ get: function( elem, computed ) {
+ // IE uses filters for opacity
+ return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+ ( parseFloat( RegExp.$1 ) / 100 ) + "" :
+ computed ? "1" : "";
+ },
+
+ set: function( elem, value ) {
+ var style = elem.style,
+ currentStyle = elem.currentStyle,
+ opacity = jQuery.isNaN( value ) ? "" : "alpha(opacity=" + value * 100 + ")",
+ filter = currentStyle && currentStyle.filter || style.filter || "";
+
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+ if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
+
+ // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+ // if "filter:" is present at all, clearType is disabled, we want to avoid this
+ // style.removeAttribute is IE Only, but so apparently is this code path...
+ style.removeAttribute( "filter" );
+
+ // if there there is no filter style applied in a css rule, we are done
+ if ( currentStyle && !currentStyle.filter ) {
+ return;
+ }
+ }
+
+ // otherwise, set new filter values
+ style.filter = ralpha.test( filter ) ?
+ filter.replace( ralpha, opacity ) :
+ filter + " " + opacity;
+ }
+ };
+}
+
+jQuery(function() {
+ // This hook cannot be added until DOM ready because the support test
+ // for it is not run until after DOM ready
+ if ( !jQuery.support.reliableMarginRight ) {
+ jQuery.cssHooks.marginRight = {
+ get: function( elem, computed ) {
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ var ret;
+ jQuery.swap( elem, { "display": "inline-block" }, function() {
+ if ( computed ) {
+ ret = curCSS( elem, "margin-right", "marginRight" );
+ } else {
+ ret = elem.style.marginRight;
+ }
+ });
+ return ret;
+ }
+ };
+ }
+});
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+ getComputedStyle = function( elem, name ) {
+ var ret, defaultView, computedStyle;
+
+ name = name.replace( rupper, "-$1" ).toLowerCase();
+
+ if ( !(defaultView = elem.ownerDocument.defaultView) ) {
+ return undefined;
+ }
+
+ if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+ ret = computedStyle.getPropertyValue( name );
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+ }
+
+ return ret;
+ };
+}
+
+if ( document.documentElement.currentStyle ) {
+ currentStyle = function( elem, name ) {
+ var left,
+ ret = elem.currentStyle && elem.currentStyle[ name ],
+ rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ],
+ style = elem.style;
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
+ // Remember the original values
+ left = style.left;
+
+ // Put in the new values to get a computed value out
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ }
+ style.left = name === "fontSize" ? "1em" : (ret || 0);
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret === "" ? "auto" : ret;
+ };
+}
+
+curCSS = getComputedStyle || currentStyle;
+
+function getWH( elem, name, extra ) {
+
+ // Start with offset property
+ var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ which = name === "width" ? cssWidth : cssHeight;
+
+ if ( val > 0 ) {
+ if ( extra !== "border" ) {
+ jQuery.each( which, function() {
+ if ( !extra ) {
+ val -= parseFloat( jQuery.css( elem, "padding" + this ) ) || 0;
+ }
+ if ( extra === "margin" ) {
+ val += parseFloat( jQuery.css( elem, extra + this ) ) || 0;
+ } else {
+ val -= parseFloat( jQuery.css( elem, "border" + this + "Width" ) ) || 0;
+ }
+ });
+ }
+
+ return val + "px";
+ }
+
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name, name );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ] || 0;
+ }
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+
+ // Add padding, border, margin
+ if ( extra ) {
+ jQuery.each( which, function() {
+ val += parseFloat( jQuery.css( elem, "padding" + this ) ) || 0;
+ if ( extra !== "padding" ) {
+ val += parseFloat( jQuery.css( elem, "border" + this + "Width" ) ) || 0;
+ }
+ if ( extra === "margin" ) {
+ val += parseFloat( jQuery.css( elem, extra + this ) ) || 0;
+ }
+ });
+ }
+
+ return val + "px";
+}
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ var width = elem.offsetWidth,
+ height = elem.offsetHeight;
+
+ return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none");
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+
+
+
+
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rhash = /#.*$/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+ rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rquery = /\?/,
+ rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+ rselectTextarea = /^(?:select|textarea)/i,
+ rspacesAjax = /\s+/,
+ rts = /([?&])_=[^&]*/,
+ rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
+
+ // Keep a copy of the old load method
+ _load = jQuery.fn.load,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Document location
+ ajaxLocation,
+
+ // Document location segments
+ ajaxLocParts,
+
+ // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+ allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+ ajaxLocation = location.href;
+} catch( e ) {
+ // Use the href attribute of an A element
+ // since IE will modify it given document.location
+ ajaxLocation = document.createElement( "a" );
+ ajaxLocation.href = "";
+ ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ if ( jQuery.isFunction( func ) ) {
+ var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
+ i = 0,
+ length = dataTypes.length,
+ dataType,
+ list,
+ placeBefore;
+
+ // For each dataType in the dataTypeExpression
+ for(; i < length; i++ ) {
+ dataType = dataTypes[ i ];
+ // We control if we're asked to add before
+ // any existing element
+ placeBefore = /^\+/.test( dataType );
+ if ( placeBefore ) {
+ dataType = dataType.substr( 1 ) || "*";
+ }
+ list = structure[ dataType ] = structure[ dataType ] || [];
+ // then we add to the structure accordingly
+ list[ placeBefore ? "unshift" : "push" ]( func );
+ }
+ }
+ };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+ dataType /* internal */, inspected /* internal */ ) {
+
+ dataType = dataType || options.dataTypes[ 0 ];
+ inspected = inspected || {};
+
+ inspected[ dataType ] = true;
+
+ var list = structure[ dataType ],
+ i = 0,
+ length = list ? list.length : 0,
+ executeOnly = ( structure === prefilters ),
+ selection;
+
+ for(; i < length && ( executeOnly || !selection ); i++ ) {
+ selection = list[ i ]( options, originalOptions, jqXHR );
+ // If we got redirected to another dataType
+ // we try there if executing only and not done already
+ if ( typeof selection === "string" ) {
+ if ( !executeOnly || inspected[ selection ] ) {
+ selection = undefined;
+ } else {
+ options.dataTypes.unshift( selection );
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, selection, inspected );
+ }
+ }
+ }
+ // If we're only executing or nothing was selected
+ // we try the catchall dataType if not done already
+ if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, "*", inspected );
+ }
+ // unnecessary when only executing (prefilters)
+ // but it'll be ignored by the caller in that case
+ return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+ var key, deep,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
+ for( key in src ) {
+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+ }
+ }
+ if ( deep ) {
+ jQuery.extend( true, target, deep );
+ }
+}
+
+jQuery.fn.extend({
+ load: function( url, params, callback ) {
+ if ( typeof url !== "string" && _load ) {
+ return _load.apply( this, arguments );
+
+ // Don't do a request if no elements are being requested
+ } else if ( !this.length ) {
+ return this;
+ }
+
+ var off = url.indexOf( " " );
+ if ( off >= 0 ) {
+ var selector = url.slice( off, url.length );
+ url = url.slice( 0, off );
+ }
+
+ // Default to a GET request
+ var type = "GET";
+
+ // If the second parameter was provided
+ if ( params ) {
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
+
+ // Otherwise, build a param string
+ } else if ( typeof params === "object" ) {
+ params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+ type = "POST";
+ }
+ }
+
+ var self = this;
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+ type: type,
+ dataType: "html",
+ data: params,
+ // Complete callback (responseText is used internally)
+ complete: function( jqXHR, status, responseText ) {
+ // Store the response as specified by the jqXHR object
+ responseText = jqXHR.responseText;
+ // If successful, inject the HTML into all the matched elements
+ if ( jqXHR.isResolved() ) {
+ // #4825: Get the actual response in case
+ // a dataFilter is present in ajaxSettings
+ jqXHR.done(function( r ) {
+ responseText = r;
+ });
+ // See if a selector was specified
+ self.html( selector ?
+ // Create a dummy div to hold the results
+ jQuery("<div>")
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append(responseText.replace(rscript, ""))
+
+ // Locate the specified elements
+ .find(selector) :
+
+ // If not, just inject the full result
+ responseText );
+ }
+
+ if ( callback ) {
+ self.each( callback, [ responseText, status, jqXHR ] );
+ }
+ }
+ });
+
+ return this;
+ },
+
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+
+ serializeArray: function() {
+ return this.map(function(){
+ return this.elements ? jQuery.makeArray( this.elements ) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ ( this.checked || rselectTextarea.test( this.nodeName ) ||
+ rinput.test( this.type ) );
+ })
+ .map(function( i, elem ){
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val, i ){
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+ jQuery.fn[ o ] = function( f ){
+ return this.bind( o, f );
+ };
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ type: method,
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ };
+});
+
+jQuery.extend({
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ if ( settings ) {
+ // Building a settings object
+ ajaxExtend( target, jQuery.ajaxSettings );
+ } else {
+ // Extending ajaxSettings
+ settings = target;
+ target = jQuery.ajaxSettings;
+ }
+ ajaxExtend( target, settings );
+ return target;
+ },
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ type: "GET",
+ contentType: "application/x-www-form-urlencoded",
+ processData: true,
+ async: true,
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ xml: "application/xml, text/xml",
+ html: "text/html",
+ text: "text/plain",
+ json: "application/json, text/javascript",
+ "*": allTypes
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText"
+ },
+
+ // List of data converters
+ // 1) key format is "source_type destination_type" (a single space in-between)
+ // 2) the catchall symbol "*" can be used for source_type
+ converters: {
+
+ // Convert anything to text
+ "* text": window.String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ context: true,
+ url: true
+ }
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events
+ // It's the callbackContext if one was provided in the options
+ // and if it's a DOM node or a jQuery collection
+ globalEventContext = callbackContext !== s &&
+ ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+ jQuery( callbackContext ) : jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery._Deferred(),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // ifModified key
+ ifModifiedKey,
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // transport
+ transport,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // The jqXHR state
+ state = 0,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Fake xhr
+ jqXHR = {
+
+ readyState: 0,
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ if ( !state ) {
+ var lname = name.toLowerCase();
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match === undefined ? null : match;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ statusText = statusText || "abort";
+ if ( transport ) {
+ transport.abort( statusText );
+ }
+ done( 0, statusText );
+ return this;
+ }
+ };
+
+ // Callback for when everything is done
+ // It is defined here because jslint complains if it is declared
+ // at the end of the function (which would be more logical and readable)
+ function done( status, nativeStatusText, responses, headers ) {
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ var isSuccess,
+ success,
+ error,
+ statusText = nativeStatusText,
+ response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
+ lastModified,
+ etag;
+
+ // If successful, handle type chaining
+ if ( status >= 200 && status < 300 || status === 304 ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+
+ if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
+ jQuery.lastModified[ ifModifiedKey ] = lastModified;
+ }
+ if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
+ jQuery.etag[ ifModifiedKey ] = etag;
+ }
+ }
+
+ // If not modified
+ if ( status === 304 ) {
+
+ statusText = "notmodified";
+ isSuccess = true;
+
+ // If we have data
+ } else {
+
+ try {
+ success = ajaxConvert( s, response );
+ statusText = "success";
+ isSuccess = true;
+ } catch(e) {
+ // We have a parsererror
+ statusText = "parsererror";
+ error = e;
+ }
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if( !statusText || status ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = "" + ( nativeStatusText || statusText );
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+ }
+
+ // Attach deferreds
+ deferred.promise( jqXHR );
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+ jqXHR.complete = completeDeferred.done;
+
+ // Status-dependent callbacks
+ jqXHR.statusCode = function( map ) {
+ if ( map ) {
+ var tmp;
+ if ( state < 2 ) {
+ for( tmp in map ) {
+ statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+ }
+ } else {
+ tmp = map[ jqXHR.status ];
+ jqXHR.then( tmp, tmp );
+ }
+ }
+ return this;
+ };
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+
+ // Determine if a cross-domain request is in order
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefiler, stop there
+ if ( state === 2 ) {
+ return false;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Get ifModifiedKey before adding the anti-cache parameter
+ ifModifiedKey = s.url;
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+
+ var ts = jQuery.now(),
+ // try replacing _= if it is there
+ ret = s.url.replace( rts, "$1_=" + ts );
+
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ ifModifiedKey = ifModifiedKey || s.url;
+ if ( jQuery.lastModified[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+ }
+ if ( jQuery.etag[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+ }
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already
+ jqXHR.abort();
+ return false;
+
+ }
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout( function(){
+ jqXHR.abort( "timeout" );
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch (e) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ jQuery.error( e );
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ // Serialize an array of form elements or a set of
+ // key/values into a query string
+ param: function( a, traditional ) {
+ var s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : value;
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( var prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+ }
+});
+
+function buildParams( prefix, obj, traditional, add ) {
+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // If array item is non-scalar (array or object), encode its
+ // numeric index to resolve deserialization ambiguity issues.
+ // Note that rack (as of 1.0.0) can't currently deserialize
+ // nested arrays properly, and attempting to do so may cause
+ // a server error. Possible fixes are to modify rack's
+ // deserialization algorithm or to provide an option or flag
+ // to force array serialization to be shallow.
+ buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && obj != null && typeof obj === "object" ) {
+ // Serialize object item.
+ for ( var name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+
+// This is still on the jQuery object... for now
+// Want to move this to jQuery.ajax some day
+jQuery.extend({
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var contents = s.contents,
+ dataTypes = s.dataTypes,
+ responseFields = s.responseFields,
+ ct,
+ type,
+ finalDataType,
+ firstDataType;
+
+ // Fill responseXXX fields
+ for( type in responseFields ) {
+ if ( type in responses ) {
+ jqXHR[ responseFields[type] ] = responses[ type ];
+ }
+ }
+
+ // Remove auto dataType and get content-type in the process
+ while( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+ // Apply the dataFilter if provided
+ if ( s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ var dataTypes = s.dataTypes,
+ converters = {},
+ i,
+ key,
+ length = dataTypes.length,
+ tmp,
+ // Current and previous dataTypes
+ current = dataTypes[ 0 ],
+ prev,
+ // Conversion expression
+ conversion,
+ // Conversion function
+ conv,
+ // Conversion functions (transitive conversion)
+ conv1,
+ conv2;
+
+ // For each dataType in the chain
+ for( i = 1; i < length; i++ ) {
+
+ // Create converters map
+ // with lowercased keys
+ if ( i === 1 ) {
+ for( key in s.converters ) {
+ if( typeof key === "string" ) {
+ converters[ key.toLowerCase() ] = s.converters[ key ];
+ }
+ }
+ }
+
+ // Get the dataTypes
+ prev = current;
+ current = dataTypes[ i ];
+
+ // If current is auto dataType, update it to prev
+ if( current === "*" ) {
+ current = prev;
+ // If no auto and dataTypes are actually different
+ } else if ( prev !== "*" && prev !== current ) {
+
+ // Get the converter
+ conversion = prev + " " + current;
+ conv = converters[ conversion ] || converters[ "* " + current ];
+
+ // If there is no direct converter, search transitively
+ if ( !conv ) {
+ conv2 = undefined;
+ for( conv1 in converters ) {
+ tmp = conv1.split( " " );
+ if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
+ conv2 = converters[ tmp[1] + " " + current ];
+ if ( conv2 ) {
+ conv1 = converters[ conv1 ];
+ if ( conv1 === true ) {
+ conv = conv2;
+ } else if ( conv2 === true ) {
+ conv = conv1;
+ }
+ break;
+ }
+ }
+ }
+ }
+ // If we found no converter, dispatch an error
+ if ( !( conv || conv2 ) ) {
+ jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
+ }
+ // If found converter is not an equivalence
+ if ( conv !== true ) {
+ // Convert with 1 or 2 converters accordingly
+ response = conv ? conv( response ) : conv2( conv1(response) );
+ }
+ }
+ }
+ return response;
+}
+
+
+
+
+var jsc = jQuery.now(),
+ jsre = /(\=)\?(&|$)|\?\?/i;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ return jQuery.expando + "_" + ( jsc++ );
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var inspectData = s.contentType === "application/x-www-form-urlencoded" &&
+ ( typeof s.data === "string" );
+
+ if ( s.dataTypes[ 0 ] === "jsonp" ||
+ s.jsonp !== false && ( jsre.test( s.url ) ||
+ inspectData && jsre.test( s.data ) ) ) {
+
+ var responseContainer,
+ jsonpCallback = s.jsonpCallback =
+ jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
+ previous = window[ jsonpCallback ],
+ url = s.url,
+ data = s.data,
+ replace = "$1" + jsonpCallback + "$2";
+
+ if ( s.jsonp !== false ) {
+ url = url.replace( jsre, replace );
+ if ( s.url === url ) {
+ if ( inspectData ) {
+ data = data.replace( jsre, replace );
+ }
+ if ( s.data === data ) {
+ // Add callback manually
+ url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
+ }
+ }
+ }
+
+ s.url = url;
+ s.data = data;
+
+ // Install callback
+ window[ jsonpCallback ] = function( response ) {
+ responseContainer = [ response ];
+ };
+
+ // Clean-up function
+ jqXHR.always(function() {
+ // Set callback back to previous value
+ window[ jsonpCallback ] = previous;
+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( previous ) ) {
+ window[ jsonpCallback ]( responseContainer[ 0 ] );
+ }
+ });
+
+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( jsonpCallback + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Delegate to script
+ return "script";
+ }
+});
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /javascript|ecmascript/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ s.global = false;
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+
+ var script,
+ head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+ return {
+
+ send: function( _, callback ) {
+
+ script = document.createElement( "script" );
+
+ script.async = "async";
+
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ script.src = s.url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( head && script.parentNode ) {
+ head.removeChild( script );
+ }
+
+ // Dereference the script
+ script = undefined;
+
+ // Callback if not abort
+ if ( !isAbort ) {
+ callback( 200, "success" );
+ }
+ }
+ };
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709 and #4378).
+ head.insertBefore( script, head.firstChild );
+ },
+
+ abort: function() {
+ if ( script ) {
+ script.onload( 0, 1 );
+ }
+ }
+ };
+ }
+});
+
+
+
+
+var // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+ xhrOnUnloadAbort = window.ActiveXObject ? function() {
+ // Abort all pending requests
+ for ( var key in xhrCallbacks ) {
+ xhrCallbacks[ key ]( 0, 1 );
+ }
+ } : false,
+ xhrId = 0,
+ xhrCallbacks;
+
+// Functions to create xhrs
+function createStandardXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch( e ) {}
+}
+
+function createActiveXHR() {
+ try {
+ return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+ } catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+ /* Microsoft failed to properly
+ * implement the XMLHttpRequest in IE7 (can't request local files),
+ * so we use the ActiveXObject when it is available
+ * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+ * we need a fallback.
+ */
+ function() {
+ return !this.isLocal && createStandardXHR() || createActiveXHR();
+ } :
+ // For all other browsers, use the standard XMLHttpRequest object
+ createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+ jQuery.extend( jQuery.support, {
+ ajax: !!xhr,
+ cors: !!xhr && ( "withCredentials" in xhr )
+ });
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+ jQuery.ajaxTransport(function( s ) {
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( !s.crossDomain || jQuery.support.cors ) {
+
+ var callback;
+
+ return {
+ send: function( headers, complete ) {
+
+ // Get a new xhr
+ var xhr = s.xhr(),
+ handle,
+ i;
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if ( s.username ) {
+ xhr.open( s.type, s.url, s.async, s.username, s.password );
+ } else {
+ xhr.open( s.type, s.url, s.async );
+ }
+
+ // Apply custom fields if provided
+ if ( s.xhrFields ) {
+ for ( i in s.xhrFields ) {
+ xhr[ i ] = s.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( s.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( s.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+ headers[ "X-Requested-With" ] = "XMLHttpRequest";
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+ } catch( _ ) {}
+
+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( ( s.hasContent && s.data ) || null );
+
+ // Listener
+ callback = function( _, isAbort ) {
+
+ var status,
+ statusText,
+ responseHeaders,
+ responses,
+ xml;
+
+ // Firefox throws exceptions when accessing properties
+ // of an xhr when a network error occured
+ // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+ try {
+
+ // Was never called and is aborted or complete
+ if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+ // Only called once
+ callback = undefined;
+
+ // Do not keep as active anymore
+ if ( handle ) {
+ xhr.onreadystatechange = jQuery.noop;
+ if ( xhrOnUnloadAbort ) {
+ delete xhrCallbacks[ handle ];
+ }
+ }
+
+ // If it's an abort
+ if ( isAbort ) {
+ // Abort it manually if needed
+ if ( xhr.readyState !== 4 ) {
+ xhr.abort();
+ }
+ } else {
+ status = xhr.status;
+ responseHeaders = xhr.getAllResponseHeaders();
+ responses = {};
+ xml = xhr.responseXML;
+
+ // Construct response list
+ if ( xml && xml.documentElement /* #4958 */ ) {
+ responses.xml = xml;
+ }
+ responses.text = xhr.responseText;
+
+ // Firefox throws an exception when accessing
+ // statusText for faulty cross-domain requests
+ try {
+ statusText = xhr.statusText;
+ } catch( e ) {
+ // We normalize with Webkit giving an empty statusText
+ statusText = "";
+ }
+
+ // Filter status for non standard behaviors
+
+ // If the request is local and we have data: assume a success
+ // (success with no data won't get notified, that's the best we
+ // can do given current implementations)
+ if ( !status && s.isLocal && !s.crossDomain ) {
+ status = responses.text ? 200 : 404;
+ // IE - #1450: sometimes returns 1223 when it should be 204
+ } else if ( status === 1223 ) {
+ status = 204;
+ }
+ }
+ }
+ } catch( firefoxAccessException ) {
+ if ( !isAbort ) {
+ complete( -1, firefoxAccessException );
+ }
+ }
+
+ // Call complete if needed
+ if ( responses ) {
+ complete( status, statusText, responses, responseHeaders );
+ }
+ };
+
+ // if we're in sync mode or it's in cache
+ // and has been retrieved directly (IE6 & IE7)
+ // we need to manually fire the callback
+ if ( !s.async || xhr.readyState === 4 ) {
+ callback();
+ } else {
+ handle = ++xhrId;
+ if ( xhrOnUnloadAbort ) {
+ // Create the active xhrs callbacks list if needed
+ // and attach the unload handler
+ if ( !xhrCallbacks ) {
+ xhrCallbacks = {};
+ jQuery( window ).unload( xhrOnUnloadAbort );
+ }
+ // Add to list of active xhrs callbacks
+ xhrCallbacks[ handle ] = callback;
+ }
+ xhr.onreadystatechange = callback;
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback(0,1);
+ }
+ }
+ };
+ }
+ });
+}
+
+
+
+
+var elemdisplay = {},
+ iframe, iframeDoc,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
+ timerId,
+ fxAttrs = [
+ // height animations
+ [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+ // width animations
+ [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+ // opacity animations
+ [ "opacity" ]
+ ],
+ fxNow;
+
+jQuery.fn.extend({
+ show: function( speed, easing, callback ) {
+ var elem, display;
+
+ if ( speed || speed === 0 ) {
+ return this.animate( genFx("show", 3), speed, easing, callback);
+
+ } else {
+ for ( var i = 0, j = this.length; i < j; i++ ) {
+ elem = this[i];
+
+ if ( elem.style ) {
+ display = elem.style.display;
+
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
+ display = elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( display === "" && jQuery.css( elem, "display" ) === "none" ) {
+ jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName));
+ }
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( i = 0; i < j; i++ ) {
+ elem = this[i];
+
+ if ( elem.style ) {
+ display = elem.style.display;
+
+ if ( display === "" || display === "none" ) {
+ elem.style.display = jQuery._data(elem, "olddisplay") || "";
+ }
+ }
+ }
+
+ return this;
+ }
+ },
+
+ hide: function( speed, easing, callback ) {
+ if ( speed || speed === 0 ) {
+ return this.animate( genFx("hide", 3), speed, easing, callback);
+
+ } else {
+ for ( var i = 0, j = this.length; i < j; i++ ) {
+ if ( this[i].style ) {
+ var display = jQuery.css( this[i], "display" );
+
+ if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) {
+ jQuery._data( this[i], "olddisplay", display );
+ }
+ }
+ }
+
+ // Set the display of the elements in a second loop
+ // to avoid the constant reflow
+ for ( i = 0; i < j; i++ ) {
+ if ( this[i].style ) {
+ this[i].style.display = "none";
+ }
+ }
+
+ return this;
+ }
+ },
+
+ // Save the old toggle function
+ _toggle: jQuery.fn.toggle,
+
+ toggle: function( fn, fn2, callback ) {
+ var bool = typeof fn === "boolean";
+
+ if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+ this._toggle.apply( this, arguments );
+
+ } else if ( fn == null || bool ) {
+ this.each(function() {
+ var state = bool ? fn : jQuery(this).is(":hidden");
+ jQuery(this)[ state ? "show" : "hide" ]();
+ });
+
+ } else {
+ this.animate(genFx("toggle", 3), fn, fn2, callback);
+ }
+
+ return this;
+ },
+
+ fadeTo: function( speed, to, easing, callback ) {
+ return this.filter(":hidden").css("opacity", 0).show().end()
+ .animate({opacity: to}, speed, easing, callback);
+ },
+
+ animate: function( prop, speed, easing, callback ) {
+ var optall = jQuery.speed(speed, easing, callback);
+
+ if ( jQuery.isEmptyObject( prop ) ) {
+ return this.each( optall.complete, [ false ] );
+ }
+
+ // Do not change referenced properties as per-property easing will be lost
+ prop = jQuery.extend( {}, prop );
+
+ return this[ optall.queue === false ? "each" : "queue" ](function() {
+ // XXX 'this' does not always have a nodeName when running the
+ // test suite
+
+ if ( optall.queue === false ) {
+ jQuery._mark( this );
+ }
+
+ var opt = jQuery.extend( {}, optall ),
+ isElement = this.nodeType === 1,
+ hidden = isElement && jQuery(this).is(":hidden"),
+ name, val, p,
+ display, e,
+ parts, start, end, unit;
+
+ // will store per property easing and be used to determine when an animation is complete
+ opt.animatedProperties = {};
+
+ for ( p in prop ) {
+
+ // property name normalization
+ name = jQuery.camelCase( p );
+ if ( p !== name ) {
+ prop[ name ] = prop[ p ];
+ delete prop[ p ];
+ }
+
+ val = prop[ name ];
+
+ // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
+ if ( jQuery.isArray( val ) ) {
+ opt.animatedProperties[ name ] = val[ 1 ];
+ val = prop[ name ] = val[ 0 ];
+ } else {
+ opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
+ }
+
+ if ( val === "hide" && hidden || val === "show" && !hidden ) {
+ return opt.complete.call( this );
+ }
+
+ if ( isElement && ( name === "height" || name === "width" ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE does not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height
+ // animated
+ if ( jQuery.css( this, "display" ) === "inline" &&
+ jQuery.css( this, "float" ) === "none" ) {
+ if ( !jQuery.support.inlineBlockNeedsLayout ) {
+ this.style.display = "inline-block";
+
+ } else {
+ display = defaultDisplay( this.nodeName );
+
+ // inline-level elements accept inline-block;
+ // block-level elements need to be inline with layout
+ if ( display === "inline" ) {
+ this.style.display = "inline-block";
+
+ } else {
+ this.style.display = "inline";
+ this.style.zoom = 1;
+ }
+ }
+ }
+ }
+ }
+
+ if ( opt.overflow != null ) {
+ this.style.overflow = "hidden";
+ }
+
+ for ( p in prop ) {
+ e = new jQuery.fx( this, opt, p );
+ val = prop[ p ];
+
+ if ( rfxtypes.test(val) ) {
+ e[ val === "toggle" ? hidden ? "show" : "hide" : val ]();
+
+ } else {
+ parts = rfxnum.exec( val );
+ start = e.cur();
+
+ if ( parts ) {
+ end = parseFloat( parts[2] );
+ unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
+
+ // We need to compute starting value
+ if ( unit !== "px" ) {
+ jQuery.style( this, p, (end || 1) + unit);
+ start = ((end || 1) / e.cur()) * start;
+ jQuery.style( this, p, start + unit);
+ }
+
+ // If a +=/-= token was provided, we're doing a relative animation
+ if ( parts[1] ) {
+ end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
+ }
+
+ e.custom( start, end, unit );
+
+ } else {
+ e.custom( start, val, "" );
+ }
+ }
+ }
+
+ // For JS strict compliance
+ return true;
+ });
+ },
+
+ stop: function( clearQueue, gotoEnd ) {
+ if ( clearQueue ) {
+ this.queue([]);
+ }
+
+ this.each(function() {
+ var timers = jQuery.timers,
+ i = timers.length;
+ // clear marker counters if we know they won't be
+ if ( !gotoEnd ) {
+ jQuery._unmark( true, this );
+ }
+ while ( i-- ) {
+ if ( timers[i].elem === this ) {
+ if (gotoEnd) {
+ // force the next step to be the last
+ timers[i](true);
+ }
+
+ timers.splice(i, 1);
+ }
+ }
+ });
+
+ // start the next in the queue if the last step wasn't forced
+ if ( !gotoEnd ) {
+ this.dequeue();
+ }
+
+ return this;
+ }
+
+});
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout( clearFxNow, 0 );
+ return ( fxNow = jQuery.now() );
+}
+
+function clearFxNow() {
+ fxNow = undefined;
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, num ) {
+ var obj = {};
+
+ jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
+ obj[ this ] = type;
+ });
+
+ return obj;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show", 1),
+ slideUp: genFx("hide", 1),
+ slideToggle: genFx("toggle", 1),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.extend({
+ speed: function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default;
+
+ // Queueing
+ opt.old = opt.complete;
+ opt.complete = function( noUnmark ) {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue !== false ) {
+ jQuery.dequeue( this );
+ } else if ( noUnmark !== false ) {
+ jQuery._unmark( this );
+ }
+ };
+
+ return opt;
+ },
+
+ easing: {
+ linear: function( p, n, firstNum, diff ) {
+ return firstNum + diff * p;
+ },
+ swing: function( p, n, firstNum, diff ) {
+ return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+ }
+ },
+
+ timers: [],
+
+ fx: function( elem, options, prop ) {
+ this.options = options;
+ this.elem = elem;
+ this.prop = prop;
+
+ options.orig = options.orig || {};
+ }
+
+});
+
+jQuery.fx.prototype = {
+ // Simple function for setting a style value
+ update: function() {
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+ },
+
+ // Get the current size
+ cur: function() {
+ if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
+ return this.elem[ this.prop ];
+ }
+
+ var parsed,
+ r = jQuery.css( this.elem, this.prop );
+ // Empty strings, null, undefined and "auto" are converted to 0,
+ // complex values such as "rotate(1rad)" are returned as is,
+ // simple values such as "10px" are parsed to Float.
+ return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
+ },
+
+ // Start an animation from one number to another
+ custom: function( from, to, unit ) {
+ var self = this,
+ fx = jQuery.fx;
+
+ this.startTime = fxNow || createFxNow();
+ this.start = from;
+ this.end = to;
+ this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
+ this.now = this.start;
+ this.pos = this.state = 0;
+
+ function t( gotoEnd ) {
+ return self.step(gotoEnd);
+ }
+
+ t.elem = this.elem;
+
+ if ( t() && jQuery.timers.push(t) && !timerId ) {
+ timerId = setInterval( fx.tick, fx.interval );
+ }
+ },
+
+ // Simple 'show' function
+ show: function() {
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+ this.options.show = true;
+
+ // Begin the animation
+ // Make sure that we start at a small width/height to avoid any
+ // flash of content
+ this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
+
+ // Start by showing the element
+ jQuery( this.elem ).show();
+ },
+
+ // Simple 'hide' function
+ hide: function() {
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+ this.options.hide = true;
+
+ // Begin the animation
+ this.custom(this.cur(), 0);
+ },
+
+ // Each step of an animation
+ step: function( gotoEnd ) {
+ var t = fxNow || createFxNow(),
+ done = true,
+ elem = this.elem,
+ options = this.options,
+ i, n;
+
+ if ( gotoEnd || t >= options.duration + this.startTime ) {
+ this.now = this.end;
+ this.pos = this.state = 1;
+ this.update();
+
+ options.animatedProperties[ this.prop ] = true;
+
+ for ( i in options.animatedProperties ) {
+ if ( options.animatedProperties[i] !== true ) {
+ done = false;
+ }
+ }
+
+ if ( done ) {
+ // Reset the overflow
+ if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+
+ jQuery.each( [ "", "X", "Y" ], function (index, value) {
+ elem.style[ "overflow" + value ] = options.overflow[index];
+ });
+ }
+
+ // Hide the element if the "hide" operation was done
+ if ( options.hide ) {
+ jQuery(elem).hide();
+ }
+
+ // Reset the properties, if the item has been hidden or shown
+ if ( options.hide || options.show ) {
+ for ( var p in options.animatedProperties ) {
+ jQuery.style( elem, p, options.orig[p] );
+ }
+ }
+
+ // Execute the complete function
+ options.complete.call( elem );
+ }
+
+ return false;
+
+ } else {
+ // classical easing cannot be used with an Infinity duration
+ if ( options.duration == Infinity ) {
+ this.now = t;
+ } else {
+ n = t - this.startTime;
+ this.state = n / options.duration;
+
+ // Perform the easing function, defaults to swing
+ this.pos = jQuery.easing[ options.animatedProperties[ this.prop ] ]( this.state, n, 0, 1, options.duration );
+ this.now = this.start + ((this.end - this.start) * this.pos);
+ }
+ // Perform the next step of the animation
+ this.update();
+ }
+
+ return true;
+ }
+};
+
+jQuery.extend( jQuery.fx, {
+ tick: function() {
+ for ( var timers = jQuery.timers, i = 0 ; i < timers.length ; ++i ) {
+ if ( !timers[i]() ) {
+ timers.splice(i--, 1);
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ },
+
+ interval: 13,
+
+ stop: function() {
+ clearInterval( timerId );
+ timerId = null;
+ },
+
+ speeds: {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+ },
+
+ step: {
+ opacity: function( fx ) {
+ jQuery.style( fx.elem, "opacity", fx.now );
+ },
+
+ _default: function( fx ) {
+ if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+ fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
+ } else {
+ fx.elem[ fx.prop ] = fx.now;
+ }
+ }
+ }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
+
+// Try to restore the default display value of an element
+function defaultDisplay( nodeName ) {
+
+ if ( !elemdisplay[ nodeName ] ) {
+
+ var body = document.body,
+ elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
+ display = elem.css( "display" );
+
+ elem.remove();
+
+ // If the simple way fails,
+ // get element's real default display by attaching it to a temp iframe
+ if ( display === "none" || display === "" ) {
+ // No iframe to use yet, so create it
+ if ( !iframe ) {
+ iframe = document.createElement( "iframe" );
+ iframe.frameBorder = iframe.width = iframe.height = 0;
+ }
+
+ body.appendChild( iframe );
+
+ // Create a cacheable copy of the iframe document on first call.
+ // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+ // document to it; WebKit & Firefox won't allow reusing the iframe document.
+ if ( !iframeDoc || !iframe.createElement ) {
+ iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+ iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" );
+ iframeDoc.close();
+ }
+
+ elem = iframeDoc.createElement( nodeName );
+
+ iframeDoc.body.appendChild( elem );
+
+ display = jQuery.css( elem, "display" );
+
+ body.removeChild( iframe );
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+ }
+
+ return elemdisplay[ nodeName ];
+}
+
+
+
+
+var rtable = /^t(?:able|d|h)$/i,
+ rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+ jQuery.fn.offset = function( options ) {
+ var elem = this[0], box;
+
+ if ( options ) {
+ return this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ if ( !elem || !elem.ownerDocument ) {
+ return null;
+ }
+
+ if ( elem === elem.ownerDocument.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ try {
+ box = elem.getBoundingClientRect();
+ } catch(e) {}
+
+ var doc = elem.ownerDocument,
+ docElem = doc.documentElement;
+
+ // Make sure we're not dealing with a disconnected DOM node
+ if ( !box || !jQuery.contains( docElem, elem ) ) {
+ return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+ }
+
+ var body = doc.body,
+ win = getWindow(doc),
+ clientTop = docElem.clientTop || body.clientTop || 0,
+ clientLeft = docElem.clientLeft || body.clientLeft || 0,
+ scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop,
+ scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
+ top = box.top + scrollTop - clientTop,
+ left = box.left + scrollLeft - clientLeft;
+
+ return { top: top, left: left };
+ };
+
+} else {
+ jQuery.fn.offset = function( options ) {
+ var elem = this[0];
+
+ if ( options ) {
+ return this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ if ( !elem || !elem.ownerDocument ) {
+ return null;
+ }
+
+ if ( elem === elem.ownerDocument.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ jQuery.offset.initialize();
+
+ var computedStyle,
+ offsetParent = elem.offsetParent,
+ prevOffsetParent = elem,
+ doc = elem.ownerDocument,
+ docElem = doc.documentElement,
+ body = doc.body,
+ defaultView = doc.defaultView,
+ prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+ top = elem.offsetTop,
+ left = elem.offsetLeft;
+
+ while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+ if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+ break;
+ }
+
+ computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+ top -= elem.scrollTop;
+ left -= elem.scrollLeft;
+
+ if ( elem === offsetParent ) {
+ top += elem.offsetTop;
+ left += elem.offsetLeft;
+
+ if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevOffsetParent = offsetParent;
+ offsetParent = elem.offsetParent;
+ }
+
+ if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevComputedStyle = computedStyle;
+ }
+
+ if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+ top += body.offsetTop;
+ left += body.offsetLeft;
+ }
+
+ if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+ top += Math.max( docElem.scrollTop, body.scrollTop );
+ left += Math.max( docElem.scrollLeft, body.scrollLeft );
+ }
+
+ return { top: top, left: left };
+ };
+}
+
+jQuery.offset = {
+ initialize: function() {
+ var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0,
+ html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+
+ jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } );
+
+ container.innerHTML = html;
+ body.insertBefore( container, body.firstChild );
+ innerDiv = container.firstChild;
+ checkDiv = innerDiv.firstChild;
+ td = innerDiv.nextSibling.firstChild.firstChild;
+
+ this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
+ this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
+
+ checkDiv.style.position = "fixed";
+ checkDiv.style.top = "20px";
+
+ // safari subtracts parent border width here which is 5px
+ this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15);
+ checkDiv.style.position = checkDiv.style.top = "";
+
+ innerDiv.style.overflow = "hidden";
+ innerDiv.style.position = "relative";
+
+ this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
+
+ this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);
+
+ body.removeChild( container );
+ jQuery.offset.initialize = jQuery.noop;
+ },
+
+ bodyOffset: function( body ) {
+ var top = body.offsetTop,
+ left = body.offsetLeft;
+
+ jQuery.offset.initialize();
+
+ if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) {
+ top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+ left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+ }
+
+ return { top: top, left: left };
+ },
+
+ setOffset: function( elem, options, i ) {
+ var position = jQuery.css( elem, "position" );
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ var curElem = jQuery( elem ),
+ curOffset = curElem.offset(),
+ curCSSTop = jQuery.css( elem, "top" ),
+ curCSSLeft = jQuery.css( elem, "left" ),
+ calculatePosition = (position === "absolute" || position === "fixed") && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+ props = {}, curPosition = {}, curTop, curLeft;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if (options.top != null) {
+ props.top = (options.top - curOffset.top) + curTop;
+ }
+ if (options.left != null) {
+ props.left = (options.left - curOffset.left) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+ position: function() {
+ if ( !this[0] ) {
+ return null;
+ }
+
+ var elem = this[0],
+
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+ offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+ // Add offsetParent borders
+ parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+ parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+ // Subtract the two offsets
+ return {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || document.body;
+ while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ["Left", "Top"], function( i, name ) {
+ var method = "scroll" + name;
+
+ jQuery.fn[ method ] = function( val ) {
+ var elem, win;
+
+ if ( val === undefined ) {
+ elem = this[ 0 ];
+
+ if ( !elem ) {
+ return null;
+ }
+
+ win = getWindow( elem );
+
+ // Return the scroll offset
+ return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
+ jQuery.support.boxModel && win.document.documentElement[ method ] ||
+ win.document.body[ method ] :
+ elem[ method ];
+ }
+
+ // Set the scroll offset
+ return this.each(function() {
+ win = getWindow( this );
+
+ if ( win ) {
+ win.scrollTo(
+ !i ? val : jQuery( win ).scrollLeft(),
+ i ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ this[ method ] = val;
+ }
+ });
+ };
+});
+
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+
+
+
+
+// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function( i, name ) {
+
+ var type = name.toLowerCase();
+
+ // innerHeight and innerWidth
+ jQuery.fn[ "inner" + name ] = function() {
+ var elem = this[0];
+ return elem && elem.style ?
+ parseFloat( jQuery.css( elem, type, "padding" ) ) :
+ null;
+ };
+
+ // outerHeight and outerWidth
+ jQuery.fn[ "outer" + name ] = function( margin ) {
+ var elem = this[0];
+ return elem && elem.style ?
+ parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
+ null;
+ };
+
+ jQuery.fn[ type ] = function( size ) {
+ // Get window width or height
+ var elem = this[0];
+ if ( !elem ) {
+ return size == null ? null : this;
+ }
+
+ if ( jQuery.isFunction( size ) ) {
+ return this.each(function( i ) {
+ var self = jQuery( this );
+ self[ type ]( size.call( this, i, self[ type ]() ) );
+ });
+ }
+
+ if ( jQuery.isWindow( elem ) ) {
+ // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+ // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+ var docElemProp = elem.document.documentElement[ "client" + name ],
+ body = elem.document.body;
+ return elem.document.compatMode === "CSS1Compat" && docElemProp ||
+ body && body[ "client" + name ] || docElemProp;
+
+ // Get document width or height
+ } else if ( elem.nodeType === 9 ) {
+ // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+ return Math.max(
+ elem.documentElement["client" + name],
+ elem.body["scroll" + name], elem.documentElement["scroll" + name],
+ elem.body["offset" + name], elem.documentElement["offset" + name]
+ );
+
+ // Get or set width or height on the element
+ } else if ( size === undefined ) {
+ var orig = jQuery.css( elem, type ),
+ ret = parseFloat( orig );
+
+ return jQuery.isNaN( ret ) ? orig : ret;
+
+ // Set the width or height on the element (default to pixels if value is unitless)
+ } else {
+ return this.css( type, typeof size === "string" ? size : size + "px" );
+ }
+ };
+
+});
+
+
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+})(window);
diff --git a/tests/jquery-1.6.4.js.min b/tests/jquery-1.6.4.js.min
new file mode 100644
index 0000000..9fbe4e1
--- /dev/null
+++ b/tests/jquery-1.6.4.js.min
@@ -0,0 +1,23 @@
+/*!
+ * jQuery JavaScript Library v1.6.4
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Mon Sep 12 18:54:48 2011 -0400
+ */
+(function(a7,K){var ap=a7.document,bq=a7.navigator,bh=a7.location;var b=(function(){var bB=function(bX,bY){return new bB.fn.init(bX,bY,bz)},bR=a7.jQuery,bD=a7.$,bz,bV=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,bJ=/\S/,bF=/^\s+/,bA=/\s+$/,bE=/\d/,bw=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,bK=/^[\],:{}\s]*$/,bT=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,bM=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,bG=/(?:^|:|,)(?:\s*\[)+/g,bu=/(webkit)[ \/]([\w.]+)/,bO=/(opera)(?:.*version)?[ \/]( [...]
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){var bB=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,bC=0,bF=Object.prototype.toString,bw=false,bv=true,bD=/\\/g,bJ=/\W/;[0,0].sort(function(){bv=false;return 0});var bt=function(bO,e,bR,bS){bR=bR||[];e=e||ap;var bU=e;if(e.nodeType!==1&&e.nodeType!==9){return[]}if(!bO||typeof bO!=="string"){return bR}var bL,bW,bZ,bK,bV,bY,bX,bQ,bN=true,bM=bt.isXML(e),bP=[],bT=bO;do{bB.exec("");bL=bB.exec(b [...]
\ No newline at end of file
diff --git a/tests/lowercasing.css b/tests/lowercasing.css
new file mode 100644
index 0000000..00a9468
--- /dev/null
+++ b/tests/lowercasing.css
@@ -0,0 +1,63 @@
+ at CHARSET "UTF-8";
+
+ at FONT-FACE {
+ FONT-FAMILY: "YOUR FACE";
+}
+
+ at IMPORT "HTTP://DOMAIN.TLD/OTHER.CSS";
+
+ at MEDIA print {
+ BACKGROUND: NONE;
+ BACKGROUND-POSITION: 0 0;
+}
+
+ at PAGE {
+ CONTENT: ATTR(HREF);
+ HEIGHT: MAX(0, MIN(10, 20));
+ WIDTH: CALC(50% - 10PX);
+}
+
+ at NAMESPACE XHTML "HTTP://WWW.W3.ORG/1999/XHTML";
+
+/* pseudos */
+a:ACTIVE,
+a:AFTER,
+a:BEFORE,
+a:CHECKED,
+a:DISABLED,
+a:EMPTY,
+a:ENABLED,
+a:FIRST-CHILD,
+a:FIRST-LETTER,
+a:FIRST-LINE,
+a:FIRST-OF-TYPE,
+a:FOCUS,
+a:HOVER,
+a:LAST-CHILD,
+a:LAST-OF-TYPE,
+a:LINK,
+a:ONLY-CHILD,
+a:ONLY-OF-TYPE,
+a:ROOT,
+a::SELECTION,
+a:TARGET,
+a:VISITED,
+
+/* pseudo functions */
+a:ANY(A, B, I) STRONG,
+a:LANG(FR),
+a:NOT([HIDDEN]),
+a:NTH-CHILD(2),
+a:NTH-LAST-CHILD(2),
+a:NTH-LAST-OF-TYPE(2),
+a:NTH-OF-TYPE(2) {
+ BACKGROUND: URL(PROTO://DOMAIN.TLD/PATH),
+ REPEATING-LINEAR-GRADIENT(20DEG, GRAY, GREEN, 20PX, WHITE 40PX),
+ -ATSC-LINEAR-GRADIENT(LEFT, BLACK, WHITE),
+ -KHTML-RADIAL-GRADIENT(CENTER 50DEG, CIRCLE CLOSEST-SIDE, BLACK 0, GREEN 100%),
+ -MOZ-RADIAL-GRADIENT(CENTER 45DEG, CIRCLE CLOSEST-SIDE, ORANGE 0%, RED 100%),
+ -MS-LINEAR-GRaDiEnT(LEFT, BLUE, BLACK),
+ -O-REPEATING-RADIAL-GRADIENT(CENTER, CIRCLE CLOSEST-SIDE, PAPAYAWHIP, RED 50%, GAINSBORO),
+ -WAP-LINEAR-GRADIENT(LEFT, BLACK, WHITE),
+ -WEBKIT-GRADIENT(LINEAR, LEFT, FROM(WHITE), TO(RGBA(1,2,3,.4)));
+}
diff --git a/tests/lowercasing.css.min b/tests/lowercasing.css.min
new file mode 100644
index 0000000..dd640db
--- /dev/null
+++ b/tests/lowercasing.css.min
@@ -0,0 +1 @@
+ at charset "UTF-8";@font-face{FONT-FAMILY:"YOUR FACE"}@import "HTTP://DOMAIN.TLD/OTHER.CSS";@media print{background:0;background-position:0 0}@page{CONTENT:attr(HREF);HEIGHT:max(0,min(10,20));WIDTH:calc(50% - 10PX)}@namespace XHTML "HTTP://WWW.W3.ORG/1999/XHTML";a:active,a:after,a:before,a:checked,a:disabled,a:empty,a:enabled,a:first-child,a:first-letter ,a:first-line ,a:first-of-type,a:focus,a:hover,a:last-child,a:last-of-type,a:link,a:only-child,a:only-of-type,a:root,a::selection,a:targe [...]
diff --git a/tests/media-empty-class.css b/tests/media-empty-class.css
new file mode 100644
index 0000000..d2f22d5
--- /dev/null
+++ b/tests/media-empty-class.css
@@ -0,0 +1,16 @@
+/*! preserved */
+emptiness {}
+
+ at import "another.css";
+/* I'm empty - delete me */
+empty { ;}
+
+ at media print {
+ .noprint { display: none; }
+}
+
+ at media screen {
+ /* this rule should be removed, not simply minified.*/
+ .breakme {}
+ .printonly { display: none; }
+}
\ No newline at end of file
diff --git a/tests/media-empty-class.css.min b/tests/media-empty-class.css.min
new file mode 100644
index 0000000..0350c7f
--- /dev/null
+++ b/tests/media-empty-class.css.min
@@ -0,0 +1 @@
+/*! preserved */@import "another.css";@media print{.noprint{display:none}}@media screen{.printonly{display:none}}
\ No newline at end of file
diff --git a/tests/media-multi.css b/tests/media-multi.css
new file mode 100644
index 0000000..c589771
--- /dev/null
+++ b/tests/media-multi.css
@@ -0,0 +1,3 @@
+ at media only all and (max-width:50em), only all and (max-device-width:800px), only all and (max-width:780px) {
+ some-css : here
+}
diff --git a/tests/media-multi.css.min b/tests/media-multi.css.min
new file mode 100644
index 0000000..57b52f7
--- /dev/null
+++ b/tests/media-multi.css.min
@@ -0,0 +1 @@
+ at media only all and (max-width:50em),only all and (max-device-width:800px),only all and (max-width:780px){some-css:here}
\ No newline at end of file
diff --git a/tests/media-test.css b/tests/media-test.css
new file mode 100644
index 0000000..4adb8f7
--- /dev/null
+++ b/tests/media-test.css
@@ -0,0 +1,3 @@
+ at media screen AND (-webkit-min-device-pixel-ratio:0) {
+ some-css : here
+}
diff --git a/tests/media-test.css.min b/tests/media-test.css.min
new file mode 100644
index 0000000..0e7168e
--- /dev/null
+++ b/tests/media-test.css.min
@@ -0,0 +1 @@
+ at media screen and (-webkit-min-device-pixel-ratio:0){some-css:here}
\ No newline at end of file
diff --git a/tests/opacity-filter.css b/tests/opacity-filter.css
new file mode 100644
index 0000000..60deca7
--- /dev/null
+++ b/tests/opacity-filter.css
@@ -0,0 +1,14 @@
+/* example from https://developer.mozilla.org/en/CSS/opacity */
+pre { /* make the box translucent (80% opaque) */
+ border: solid red;
+ opacity: 0.8; /* Firefox, Safari(WebKit), Opera */
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; /* IE 8 */
+ filter: PROGID:DXImageTransform.Microsoft.Alpha(Opacity=80); /* IE 4-7 */
+ zoom: 1; /* set "zoom", "width" or "height" to trigger "hasLayout" in IE 7 and lower */
+}
+
+/** and again */
+code {
+ -ms-filter: "PROGID:DXImageTransform.Microsoft.Alpha(Opacity=80)"; /* IE 8 */
+ filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); /* IE 4-7 */
+}
\ No newline at end of file
diff --git a/tests/opacity-filter.css.min b/tests/opacity-filter.css.min
new file mode 100644
index 0000000..99b4fa8
--- /dev/null
+++ b/tests/opacity-filter.css.min
@@ -0,0 +1 @@
+pre{border:solid red;opacity:.8;-ms-filter:"alpha(opacity=80)";filter:alpha(opacity=80);zoom:1}code{-ms-filter:"alpha(opacity=80)";filter:alpha(opacity=80)}
\ No newline at end of file
diff --git a/tests/opera-pixel-ratio.css b/tests/opera-pixel-ratio.css
new file mode 100644
index 0000000..d4547d0
--- /dev/null
+++ b/tests/opera-pixel-ratio.css
@@ -0,0 +1,14 @@
+ at media
+ (-o-min-device-pixel-ratio:10/4),
+ (-o-max-device-pixel-ratio: 5/4),
+ (-o-device-pixel-ratio: 1/1),
+ (-o-device-pixel-ratio: 1/10),
+ (-o-device-pixel-ratio: 1.25),
+ (device-pixel-ratio:1.5) {
+ /* some:prop; */
+ /* An empty property with a fraction in query would break previously */
+ }
+
+ .something {
+ foo: bar;
+ }
\ No newline at end of file
diff --git a/tests/opera-pixel-ratio.css.min b/tests/opera-pixel-ratio.css.min
new file mode 100644
index 0000000..66a1a99
--- /dev/null
+++ b/tests/opera-pixel-ratio.css.min
@@ -0,0 +1 @@
+.something{foo:bar}
\ No newline at end of file
diff --git a/tests/preserve-case.css b/tests/preserve-case.css
new file mode 100644
index 0000000..06818f0
--- /dev/null
+++ b/tests/preserve-case.css
@@ -0,0 +1,15 @@
+#AddAddressForm {
+ padding: 0;
+}
+#AddAddressForm .messageBoxNeutral {
+ padding: 0;
+}
+#FeedbackMailForm{
+ padding: 0;
+}
+#FeedbackMailForm .classe{
+ margin: 0;
+}
+.classes, #FeedBackMailForm {
+ margin: 0;
+}
diff --git a/tests/preserve-case.css.min b/tests/preserve-case.css.min
new file mode 100644
index 0000000..373bcbb
--- /dev/null
+++ b/tests/preserve-case.css.min
@@ -0,0 +1 @@
+#AddAddressForm{padding:0}#AddAddressForm .messageBoxNeutral{padding:0}#FeedbackMailForm{padding:0}#FeedbackMailForm .classe{margin:0}.classes,#FeedBackMailForm{margin:0}
diff --git a/tests/preserve-important.css b/tests/preserve-important.css
new file mode 100644
index 0000000..2950fd8
--- /dev/null
+++ b/tests/preserve-important.css
@@ -0,0 +1 @@
+.red { color: red !important; }
diff --git a/tests/preserve-important.css.min b/tests/preserve-important.css.min
new file mode 100644
index 0000000..91ae60b
--- /dev/null
+++ b/tests/preserve-important.css.min
@@ -0,0 +1 @@
+.red{color:red !important}
\ No newline at end of file
diff --git a/tests/preserve-new-line.css b/tests/preserve-new-line.css
new file mode 100644
index 0000000..e1f0c92
--- /dev/null
+++ b/tests/preserve-new-line.css
@@ -0,0 +1,6 @@
+#sel-o {
+ content: "on\"ce upon \
+a time";
+ content: 'once upon \
+a ti\'me';
+}
\ No newline at end of file
diff --git a/tests/preserve-new-line.css.min b/tests/preserve-new-line.css.min
new file mode 100644
index 0000000..6ac20b6
--- /dev/null
+++ b/tests/preserve-new-line.css.min
@@ -0,0 +1,3 @@
+#sel-o{content:"on\"ce upon \
+a time";content:'once upon \
+a ti\'me'}
\ No newline at end of file
diff --git a/tests/preserve-strings.css b/tests/preserve-strings.css
new file mode 100644
index 0000000..9151373
--- /dev/null
+++ b/tests/preserve-strings.css
@@ -0,0 +1,7 @@
+/* preserving strings */
+.sele {
+ content: "\"keep \" me";
+ something: '\\\' . . ';
+ else: 'empty{}';
+ content: "/* test */"; /* <---- this is not a comment, should be be kept */
+}
\ No newline at end of file
diff --git a/tests/preserve-strings.css.min b/tests/preserve-strings.css.min
new file mode 100644
index 0000000..3f1d010
--- /dev/null
+++ b/tests/preserve-strings.css.min
@@ -0,0 +1 @@
+.sele{content:"\"keep \" me";something:'\\\' . . ';else:'empty{}';content:"/* test */"}
\ No newline at end of file
diff --git a/tests/pseudo-first.css b/tests/pseudo-first.css
new file mode 100644
index 0000000..ecb06fa
--- /dev/null
+++ b/tests/pseudo-first.css
@@ -0,0 +1,16 @@
+/*
+because of IE6 first-letter and first-line
+must be followed by a space
+http://reference.sitepoint.com/css/pseudoelement-firstletter
+Thanks: P.Sorokin comment at http://www.phpied.com/cssmin-js/
+*/
+p:first-letter{
+ buh: hum;
+}
+p:FIRST-LINE{
+ baa: 1;
+}
+
+p:first-line,a,p:first-letter,b{
+ color: red;
+}
diff --git a/tests/pseudo-first.css.min b/tests/pseudo-first.css.min
new file mode 100644
index 0000000..687117c
--- /dev/null
+++ b/tests/pseudo-first.css.min
@@ -0,0 +1 @@
+p:first-letter {buh:hum}p:first-line {baa:1}p:first-line ,a,p:first-letter ,b{color:red}
\ No newline at end of file
diff --git a/tests/pseudo.css b/tests/pseudo.css
new file mode 100644
index 0000000..126a5b1
--- /dev/null
+++ b/tests/pseudo.css
@@ -0,0 +1,4 @@
+p :link {
+ ba:zinga;;;
+ foo: bar;;;
+}
\ No newline at end of file
diff --git a/tests/pseudo.css.min b/tests/pseudo.css.min
new file mode 100644
index 0000000..bb7f8e7
--- /dev/null
+++ b/tests/pseudo.css.min
@@ -0,0 +1 @@
+p :link{ba:zinga;foo:bar}
\ No newline at end of file
diff --git a/tests/special-comments.css b/tests/special-comments.css
new file mode 100644
index 0000000..4e184ba
--- /dev/null
+++ b/tests/special-comments.css
@@ -0,0 +1,13 @@
+/*!************88****
+ Preserving comments
+ as they are
+ ********************
+ Keep the initial !
+ *******************/
+#yo {
+ ma: "ma";
+}
+/*!
+I said
+pre-
+serve! */
\ No newline at end of file
diff --git a/tests/special-comments.css.min b/tests/special-comments.css.min
new file mode 100644
index 0000000..92ecbac
--- /dev/null
+++ b/tests/special-comments.css.min
@@ -0,0 +1,9 @@
+/*!************88****
+ Preserving comments
+ as they are
+ ********************
+ Keep the initial !
+ *******************/#yo{ma:"ma"}/*!
+I said
+pre-
+serve! */
\ No newline at end of file
diff --git a/tests/star-underscore-hacks.css b/tests/star-underscore-hacks.css
new file mode 100644
index 0000000..8b6e517
--- /dev/null
+++ b/tests/star-underscore-hacks.css
@@ -0,0 +1,5 @@
+#elementarr {
+ width: 1px;
+ *width: 3pt;
+ _width: 2em;
+}
\ No newline at end of file
diff --git a/tests/star-underscore-hacks.css.min b/tests/star-underscore-hacks.css.min
new file mode 100644
index 0000000..0a014c3
--- /dev/null
+++ b/tests/star-underscore-hacks.css.min
@@ -0,0 +1 @@
+#elementarr{width:1px;*width:3pt;_width:2em}
\ No newline at end of file
diff --git a/tests/string-in-comment.css b/tests/string-in-comment.css
new file mode 100644
index 0000000..d94d192
--- /dev/null
+++ b/tests/string-in-comment.css
@@ -0,0 +1,8 @@
+/* te " st */
+a{a:1}
+/*!"preserve" me*/
+b{content: "/**/"}
+/* quite " quote ' \' \" */
+/* ie mac \*/
+c {c : 3}
+/* end hiding */
\ No newline at end of file
diff --git a/tests/string-in-comment.css.min b/tests/string-in-comment.css.min
new file mode 100644
index 0000000..7cdec2d
--- /dev/null
+++ b/tests/string-in-comment.css.min
@@ -0,0 +1 @@
+a{a:1}/*!"preserve" me*/b{content:"/**/"}/*\*/c{c:3}/**/
\ No newline at end of file
diff --git a/tests/suite.rhino b/tests/suite.rhino
new file mode 100644
index 0000000..051ed1b
--- /dev/null
+++ b/tests/suite.rhino
@@ -0,0 +1,3 @@
+input = readFile(arguments[0]);
+load("../ports/js/cssmin.js");
+print(YAHOO.compressor.cssmin(input));
\ No newline at end of file
diff --git a/tests/suite.sh b/tests/suite.sh
new file mode 100755
index 0000000..91e1333
--- /dev/null
+++ b/tests/suite.sh
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+
+cd $(dirname $0)
+
+# Get the jar to use.
+jar="$(ls ../build/*.jar | sort | tail -n1)"
+echo "jar: $jar"
+
+runtest () {
+ testfile="$1"
+ expected=${testfile/\.FAIL/}.min
+ expected="$(
+ cat $expected
+ )"
+ filetype="$(
+ echo $testfile | egrep -o '(cs|j)s'
+ )"
+
+ if [ "$2" == "cssminjs" ]; then
+ actual="$(
+ java -jar ../lib/rhino-1.7R2.jar suite.rhino $testfile
+ )"
+
+ else
+ actual="$(
+ java -jar $jar --type $filetype $testfile
+ )"
+ fi
+
+ if [ "$expected" == "$actual" ]; then
+ echo "Passed: $testfile" > /dev/stderr
+ else
+ (
+ echo "Test failed: $testfile"
+ echo ""
+ echo "Expected:"
+ echo "$expected"
+ echo ""
+ echo "Actual:"
+ echo "$actual"
+ ) > /dev/stderr
+ return 1
+ fi
+}
+
+
+ls *.FAIL 2>/dev/null | while read failtest; do
+ echo "Failing test: " $failtest > /dev/stderr
+ runtest $failtest && echo "Test passed, please remove the '.FAIL' from the filename"
+done
+
+ls *.{css,js} | while read testfile; do
+ runtest $testfile || exit 1
+done
+
+echo
+echo "now testing the JS port of CSSMIN..."
+ls *.css | while read testfile; do
+ runtest $testfile "cssminjs" || exit 1
+done
+
+exit 0
diff --git a/tests/webkit-transform.css b/tests/webkit-transform.css
new file mode 100644
index 0000000..83a50f2
--- /dev/null
+++ b/tests/webkit-transform.css
@@ -0,0 +1,2 @@
+c {-webkit-transform-origin: 0 0;}
+d {-MOZ-TRANSFORM-ORIGIN: 0 0 }
\ No newline at end of file
diff --git a/tests/webkit-transform.css.min b/tests/webkit-transform.css.min
new file mode 100644
index 0000000..b640ddf
--- /dev/null
+++ b/tests/webkit-transform.css.min
@@ -0,0 +1 @@
+c{-webkit-transform-origin:0 0}d{-moz-transform-origin:0 0}
\ No newline at end of file
diff --git a/tests/zeros.css b/tests/zeros.css
new file mode 100644
index 0000000..73bc1d7
--- /dev/null
+++ b/tests/zeros.css
@@ -0,0 +1,12 @@
+a {
+ margin: 0px 0pt 0em 0%;
+ _padding-top: 0ex;
+ background-position: 0 0;
+ padding: 0in 0cm 0mm 0pc;
+ transition: opacity .0s;
+ transition-delay: 0.0ms;
+ transform: rotate3d(0grad, 0rad, 0deg);
+ pitch: 0KHZ;
+ pitch:
+0hz; /* intentionally on next line */
+}
diff --git a/tests/zeros.css.min b/tests/zeros.css.min
new file mode 100644
index 0000000..cad60ba
--- /dev/null
+++ b/tests/zeros.css.min
@@ -0,0 +1 @@
+a{margin:0;_padding-top:0;background-position:0 0;padding:0;transition:opacity 0;transition-delay:0;transform:rotate3d(0,0,0);pitch:0;pitch:0}
diff --git a/tools/cssmin-debugger.html b/tools/cssmin-debugger.html
new file mode 100644
index 0000000..e6374f2
--- /dev/null
+++ b/tools/cssmin-debugger.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+ <head>
+ <link href="http://yui.yahooapis.com/3.4.0/build/cssreset/cssreset.css" type="text/css" rel="stylesheet">
+ <link href="http://yui.yahooapis.com/3.4.0/build/cssbase/cssbase.css" type="text/css" rel="stylesheet">
+
+ <style>
+ body {
+ padding:10px;
+ }
+
+ pre {
+ width:90%;
+ padding:10px;
+ overflow:auto;
+ background-color:#eee;
+ }
+
+ #testFile {
+ margin:2em;
+ }
+
+ #notsupportedmsg.hidden {
+ display:none;
+ }
+
+ #notsupportedmsg {
+ color:red;
+ }
+ </style>
+
+ <script src="../ports/js/cssmin.js"></script>
+ </head>
+ <body>
+ <h1>Use This Page to Debug cssmin.js</h1>
+
+ <h1 id="notsupportedmsg" class="hidden">Your browser does not support the local file access apis used by this page.</h1>
+
+ <p>Select a css file to compress. You can then step through the cssmin.js implementation using your browser's script debugger.</p>
+
+ <p><input type="file" id="testFile"></p>
+
+ <h2>Compressed</h2>
+ <pre id="out"></pre>
+
+ <h2>Original</h2>
+ <pre id="in"></pre>
+
+ <script>
+ (function() {
+
+ var dumpContents = function(node, str) {
+ node.innerHTML = "";
+ node.appendChild(document.createTextNode(str));
+ },
+ testFile,
+ changeHandler;
+
+ if (window.File && window.FileReader) {
+
+ testFile = document.getElementById('testFile');
+
+ changeHandler = function(e) {
+ var file = this.files[0],
+ fr = new FileReader(),
+ input = document.getElementById("in"),
+ output = document.getElementById("out"),
+ contents;
+
+ fr.onload = function(e) {
+ dumpContents(input, e.target.result);
+ var min = YAHOO.compressor.cssmin(e.target.result);
+ dumpContents(output, min);
+ };
+
+ fr.readAsText(file, "utf-8");
+ }
+
+ if (testFile.addEventListener) {
+ testFile.addEventListener('change', changeHandler, false);
+ } else {
+ testFile.attachEvent('onChange', changeHandler);
+ }
+
+ } else {
+ document.getElementById("notsupportedmsg").removeClass("hidden");
+ }
+ })();
+ </script>
+ </body>
+</html>
\ No newline at end of file
diff --git a/yinst/yuicompressor b/yinst/yuicompressor
new file mode 100644
index 0000000..8f9c81b
--- /dev/null
+++ b/yinst/yuicompressor
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+java -jar /home/y/bin/yuicompressor.jar $*
diff --git a/yinst/yuicompressor.yicf b/yinst/yuicompressor.yicf
new file mode 100644
index 0000000..cd45d13
--- /dev/null
+++ b/yinst/yuicompressor.yicf
@@ -0,0 +1,22 @@
+PRODUCT_NAME = yuicompressor
+VERSION = `cat ../ant.properties | grep version.number | egrep -o '[0-9]+(\.[0-9])+' | head -n1`
+LONG_DESC = `cat ../doc/README`
+SHORT_DESC = YUI Compressor
+PERM = 0644
+OWNER = root
+GROUP = users
+CUSTODIAN = ci-tools at yahoo-inc.com http://developer.yahoo.com/yui/compressor/
+
+PACKAGE_OS_SPECIFIC = no
+
+YINST bug-product yui tools
+YINST bug-component Compressor
+
+############################################################
+# Actions
+############################################################
+f 0755 - - bin/yuicompressor yuicompressor
+# Build with ant, piping to stderr, then get the biggest-named jar file.
+# JAR=`( cd .. && (ant 1>&2) && ls $PWD/build/*.jar | sort | tail -n1 && exit 0 ) || ( echo "Build Failed" && exit 1 )`
+JAR=`( cd .. && ls $PWD/build/*.jar | sort | tail -n1 && exit 0 ) || ( echo "Build Failed" && exit 1 )`
+f 0644 - - bin/yuicompressor.jar $(JAR)
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/yui-compressor.git
More information about the pkg-java-commits
mailing list