NativeIterator.java
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
import java.util.Iterator;
/**
* This class implements iterator objects. See <a
* href="http://developer.mozilla.org/en/docs/New_in_JavaScript_1.7#Iterators">Iterators</a>
*
* @author Norris Boyd
*/
public final class NativeIterator extends ScriptableObject {
private static final long serialVersionUID = -4136968203581667681L;
private static final Object ITERATOR_TAG = "Iterator";
private static final String CLASS_NAME = "Iterator";
private Object objectIterator;
static void init(Context cx, ScriptableObject scope, boolean sealed) {
LambdaConstructor constructor =
new LambdaConstructor(
scope,
CLASS_NAME,
2,
NativeIterator::jsConstructorCall,
NativeIterator::jsConstructor);
constructor.setPrototypePropertyAttributes(PERMANENT | READONLY | DONTENUM);
NativeIterator proto = new NativeIterator();
constructor.setPrototypeScriptable(proto);
constructor.definePrototypeMethod(scope, "next", 0, NativeIterator::js_next);
constructor.definePrototypeMethod(
scope, ITERATOR_PROPERTY_NAME, 1, NativeIterator::js_iteratorMethod);
ScriptableObject.defineProperty(scope, CLASS_NAME, constructor, ScriptableObject.DONTENUM);
if (sealed) {
constructor.sealObject();
((ScriptableObject) constructor.getPrototypeProperty()).sealObject();
}
// Generator
if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
ES6Generator.init(scope, sealed);
} else {
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__";
public static class StopIteration extends NativeObject {
private static final long serialVersionUID = 2485151085722377663L;
private Object value = Undefined.instance;
public StopIteration() {}
public StopIteration(Object val) {
this.value = val;
}
public Object getValue() {
return value;
}
@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 CLASS_NAME;
}
private static Object jsConstructorCall(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
Scriptable target = requireIteratorTarget(cx, scope, args);
boolean keyOnly = isKeyOnly(args);
Iterator<?> iterator = getJavaIterator(target);
if (iterator != null) {
Scriptable topScope = ScriptableObject.getTopLevelScope(scope);
return cx.getWrapFactory()
.wrap(
cx,
topScope,
new WrappedJavaIterator(iterator, topScope),
WrappedJavaIterator.class);
}
Scriptable jsIterator = ScriptRuntime.toIterator(cx, target, keyOnly);
if (jsIterator != null) {
return jsIterator;
}
return createNativeIterator(cx, scope, target, keyOnly);
}
private static Scriptable jsConstructor(Context cx, Scriptable scope, Object[] args) {
Scriptable target = requireIteratorTarget(cx, scope, args);
boolean keyOnly = isKeyOnly(args);
return createNativeIterator(cx, scope, target, keyOnly);
}
private static Scriptable requireIteratorTarget(Context cx, Scriptable scope, Object[] args) {
if (args.length == 0 || args[0] == null || args[0] == Undefined.instance) {
Object argument = args.length == 0 ? Undefined.instance : args[0];
throw ScriptRuntime.typeErrorById(
"msg.no.properties", ScriptRuntime.toString(argument));
}
return ScriptRuntime.toObject(cx, scope, args[0]);
}
private static boolean isKeyOnly(Object[] args) {
return args.length > 1 && ScriptRuntime.toBoolean(args[1]);
}
private static Object js_next(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
NativeIterator iterator = realThis(thisObj);
return iterator.next(cx, scope);
}
private static NativeIterator createNativeIterator(
Context cx, Scriptable scope, Scriptable obj, boolean keyOnly) {
Object objectIterator =
ScriptRuntime.enumInit(
obj,
cx,
scope,
keyOnly
? ScriptRuntime.ENUMERATE_KEYS_NO_ITERATOR
: ScriptRuntime.ENUMERATE_ARRAY_NO_ITERATOR);
ScriptRuntime.setEnumNumbers(objectIterator, true);
NativeIterator result = new NativeIterator(objectIterator);
result.setPrototype(ScriptableObject.getClassPrototype(scope, CLASS_NAME));
result.setParentScope(scope);
return result;
}
private static Object js_iteratorMethod(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
return realThis(thisObj);
}
private static NativeIterator realThis(Scriptable thisObj) {
return LambdaConstructor.convertThisObject(thisObj, NativeIterator.class);
}
private Object next(Context cx, Scriptable scope) {
Boolean b = ScriptRuntime.enumNext(this.objectIterator, cx);
if (!b) {
// Out of values. Throw StopIteration.
throw new JavaScriptException(NativeIterator.getStopIterationObject(scope), null, 0);
}
return ScriptRuntime.enumId(this.objectIterator, cx);
}
/**
* If "obj" is a java.util.Iterator or a java.lang.Iterable, return a wrapping as a JavaScript
* Iterator. Otherwise, return null.
*/
private static Iterator<?> getJavaIterator(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;
}
public static class WrappedJavaIterator {
private final Iterator<?> iterator;
private final Scriptable scope;
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;
}
}
}