1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 package org.mozilla.javascript;
8 
9 import java.util.Iterator;
10 
11 /**
12  * This class implements iterator objects. See
13  * http://developer.mozilla.org/en/docs/New_in_JavaScript_1.7#Iterators
14  *
15  * @author Norris Boyd
16  */
17 public final class NativeIterator extends IdScriptableObject {
18     private static final long serialVersionUID = -4136968203581667681L;
19     private static final Object ITERATOR_TAG = "Iterator";
20 
init(ScriptableObject scope, boolean sealed)21     static void init(ScriptableObject scope, boolean sealed) {
22         // Iterator
23         NativeIterator iterator = new NativeIterator();
24         iterator.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
25 
26         // Generator
27         NativeGenerator.init(scope, sealed);
28 
29         // StopIteration
30         NativeObject obj = new StopIteration();
31         obj.setPrototype(getObjectPrototype(scope));
32         obj.setParentScope(scope);
33         if (sealed) { obj.sealObject(); }
34         ScriptableObject.defineProperty(scope, STOP_ITERATION, obj,
35                                         ScriptableObject.DONTENUM);
36         // Use "associateValue" so that generators can continue to
37         // throw StopIteration even if the property of the global
38         // scope is replaced or deleted.
39         scope.associateValue(ITERATOR_TAG, obj);
40     }
41 
42     /**
43      * Only for constructing the prototype object.
44      */
NativeIterator()45     private NativeIterator() {
46     }
47 
NativeIterator(Object objectIterator)48     private NativeIterator(Object objectIterator) {
49       this.objectIterator = objectIterator;
50     }
51 
52     /**
53      * Get the value of the "StopIteration" object. Note that this value
54      * is stored in the top-level scope using "associateValue" so the
55      * value can still be found even if a script overwrites or deletes
56      * the global "StopIteration" property.
57      * @param scope a scope whose parent chain reaches a top-level scope
58      * @return the StopIteration object
59      */
getStopIterationObject(Scriptable scope)60     public static Object getStopIterationObject(Scriptable scope) {
61         Scriptable top = ScriptableObject.getTopLevelScope(scope);
62         return ScriptableObject.getTopScopeValue(top, ITERATOR_TAG);
63     }
64 
65     private static final String STOP_ITERATION = "StopIteration";
66     public static final String ITERATOR_PROPERTY_NAME = "__iterator__";
67 
68     static class StopIteration extends NativeObject {
69         private static final long serialVersionUID = 2485151085722377663L;
70 
71         @Override
getClassName()72         public String getClassName() {
73             return STOP_ITERATION;
74         }
75 
76         /* StopIteration has custom instanceof behavior since it
77          * doesn't have a constructor.
78          */
79         @Override
hasInstance(Scriptable instance)80         public boolean hasInstance(Scriptable instance) {
81             return instance instanceof StopIteration;
82         }
83     }
84 
85     @Override
getClassName()86     public String getClassName() {
87         return "Iterator";
88     }
89 
90     @Override
initPrototypeId(int id)91     protected void initPrototypeId(int id) {
92         String s;
93         int arity;
94         switch (id) {
95           case Id_constructor:    arity=2; s="constructor";          break;
96           case Id_next:           arity=0; s="next";                 break;
97           case Id___iterator__:   arity=1; s=ITERATOR_PROPERTY_NAME; break;
98           default: throw new IllegalArgumentException(String.valueOf(id));
99         }
100         initPrototypeMethod(ITERATOR_TAG, id, s, arity);
101     }
102 
103     @Override
execIdCall(IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args)104     public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
105                              Scriptable thisObj, Object[] args)
106     {
107         if (!f.hasTag(ITERATOR_TAG)) {
108             return super.execIdCall(f, cx, scope, thisObj, args);
109         }
110         int id = f.methodId();
111 
112         if (id == Id_constructor) {
113             return jsConstructor(cx, scope, thisObj, args);
114         }
115 
116         if (!(thisObj instanceof NativeIterator))
117             throw incompatibleCallError(f);
118 
119         NativeIterator iterator = (NativeIterator) thisObj;
120 
121         switch (id) {
122 
123           case Id_next:
124             return iterator.next(cx, scope);
125 
126           case Id___iterator__:
127             /// XXX: what about argument? SpiderMonkey apparently ignores it
128             return thisObj;
129 
130           default:
131             throw new IllegalArgumentException(String.valueOf(id));
132         }
133     }
134 
135     /* The JavaScript constructor */
jsConstructor(Context cx, Scriptable scope, Scriptable thisObj, Object[] args)136     private static Object jsConstructor(Context cx, Scriptable scope,
137                                         Scriptable thisObj, Object[] args)
138     {
139         if (args.length == 0 || args[0] == null ||
140             args[0] == Undefined.instance)
141         {
142             Object argument = args.length == 0 ? Undefined.instance : args[0];
143             throw ScriptRuntime.typeError1("msg.no.properties",
144                                            ScriptRuntime.toString(argument));
145         }
146         Scriptable obj = ScriptRuntime.toObject(scope, args[0]);
147         boolean keyOnly = args.length > 1 && ScriptRuntime.toBoolean(args[1]);
148         if (thisObj != null) {
149             // Called as a function. Convert to iterator if possible.
150 
151             // For objects that implement java.lang.Iterable or
152             // java.util.Iterator, have JavaScript Iterator call the underlying
153             // iteration methods
154             Iterator<?> iterator =
155                 VMBridge.instance.getJavaIterator(cx, scope, obj);
156             if (iterator != null) {
157                 scope = ScriptableObject.getTopLevelScope(scope);
158                 return cx.getWrapFactory().wrap(cx, scope,
159                         new WrappedJavaIterator(iterator, scope),
160                         WrappedJavaIterator.class);
161             }
162 
163             // Otherwise, just call the runtime routine
164             Scriptable jsIterator = ScriptRuntime.toIterator(cx, scope, obj,
165                                                              keyOnly);
166             if (jsIterator != null) {
167                 return jsIterator;
168             }
169         }
170 
171         // Otherwise, just set up to iterate over the properties of the object.
172         // Do not call __iterator__ method.
173         Object objectIterator = ScriptRuntime.enumInit(obj, cx,
174             keyOnly ? ScriptRuntime.ENUMERATE_KEYS_NO_ITERATOR
175                     : ScriptRuntime.ENUMERATE_ARRAY_NO_ITERATOR);
176         ScriptRuntime.setEnumNumbers(objectIterator, true);
177         NativeIterator result = new NativeIterator(objectIterator);
178         result.setPrototype(ScriptableObject.getClassPrototype(scope,
179                                 result.getClassName()));
180         result.setParentScope(scope);
181         return result;
182     }
183 
next(Context cx, Scriptable scope)184     private Object next(Context cx, Scriptable scope) {
185         Boolean b = ScriptRuntime.enumNext(this.objectIterator);
186         if (!b.booleanValue()) {
187             // Out of values. Throw StopIteration.
188             throw new JavaScriptException(
189                 NativeIterator.getStopIterationObject(scope), null, 0);
190         }
191         return ScriptRuntime.enumId(this.objectIterator, cx);
192     }
193 
194     static public class WrappedJavaIterator
195     {
WrappedJavaIterator(Iterator<?> iterator, Scriptable scope)196         WrappedJavaIterator(Iterator<?> iterator, Scriptable scope) {
197             this.iterator = iterator;
198             this.scope = scope;
199         }
200 
next()201         public Object next() {
202             if (!iterator.hasNext()) {
203                 // Out of values. Throw StopIteration.
204                 throw new JavaScriptException(
205                     NativeIterator.getStopIterationObject(scope), null, 0);
206             }
207             return iterator.next();
208         }
209 
__iterator__(boolean b)210         public Object __iterator__(boolean b) {
211             return this;
212         }
213 
214         private Iterator<?> iterator;
215         private Scriptable scope;
216     }
217 
218 // #string_id_map#
219 
220     @Override
findPrototypeId(String s)221     protected int findPrototypeId(String s) {
222         int id;
223 // #generated# Last update: 2007-06-11 09:43:19 EDT
224         L0: { id = 0; String X = null;
225             int s_length = s.length();
226             if (s_length==4) { X="next";id=Id_next; }
227             else if (s_length==11) { X="constructor";id=Id_constructor; }
228             else if (s_length==12) { X="__iterator__";id=Id___iterator__; }
229             if (X!=null && X!=s && !X.equals(s)) id = 0;
230             break L0;
231         }
232 // #/generated#
233         return id;
234     }
235 
236     private static final int
237         Id_constructor           = 1,
238         Id_next                  = 2,
239         Id___iterator__          = 3,
240         MAX_PROTOTYPE_ID         = 3;
241 
242 // #/string_id_map#
243 
244     private Object objectIterator;
245 }
246 
247