1 /*
   2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.objects;
  27 
  28 import java.lang.invoke.MethodHandle;
  29 import java.util.function.Consumer;
  30 import jdk.nashorn.internal.objects.annotations.Attribute;
  31 import jdk.nashorn.internal.objects.annotations.Function;
  32 import jdk.nashorn.internal.objects.annotations.ScriptClass;
  33 import jdk.nashorn.internal.runtime.JSType;
  34 import jdk.nashorn.internal.runtime.PropertyMap;
  35 import jdk.nashorn.internal.runtime.ScriptObject;
  36 import jdk.nashorn.internal.runtime.ScriptRuntime;
  37 import jdk.nashorn.internal.runtime.linker.Bootstrap;
  38 import jdk.nashorn.internal.runtime.linker.InvokeByName;
  39 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
  40 
  41 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  42 
  43 /**
  44  * ECMA6 25.1.2 The %IteratorPrototype% Object
  45  */
  46 @ScriptClass("Iterator")
  47 public abstract class AbstractIterator extends ScriptObject {
  48 
  49     // initialized by nasgen
  50     private static PropertyMap $nasgenmap$;
  51 
  52     private final static Object ITERATOR_INVOKER_KEY = new Object();
  53     private final static Object NEXT_INVOKER_KEY     = new Object();
  54     private final static Object DONE_INVOKER_KEY     = new Object();
  55     private final static Object VALUE_INVOKER_KEY    = new Object();
  56 
  57     /** ECMA6 iteration kinds */
  58     enum IterationKind {
  59         /** key iteration */
  60         KEY,
  61         /** value iteration */
  62         VALUE,
  63         /** key+value iteration */
  64         KEY_VALUE
  65     }
  66 
  67     /**
  68      * Create an abstract iterator object with the given prototype and property map.
  69      *
  70      * @param prototype the prototype
  71      * @param map the property map
  72      */
  73     protected AbstractIterator(final ScriptObject prototype, final PropertyMap map) {
  74         super(prototype, map);
  75     }
  76 
  77     /**
  78      * 25.1.2.1 %IteratorPrototype% [ @@iterator ] ( )
  79      *
  80      * @param self the self object
  81      * @return this iterator
  82      */
  83     @Function(attributes = Attribute.NOT_ENUMERABLE, name = "@@iterator")
  84     public static Object getIterator(final Object self) {
  85         return self;
  86     }
  87 
  88     @Override
  89     public String getClassName() {
  90         return "Iterator";
  91     }
  92 
  93     /**
  94      * ES6 25.1.1.2 The Iterator Interface
  95      *
  96      * @param arg argument
  97      * @return next iterator result
  98      */
  99     protected abstract IteratorResult next(final Object arg);
 100 
 101     /**
 102      * ES6 25.1.1.3 The IteratorResult Interface
 103      *
 104      * @param value result value
 105      * @param done result status
 106      * @param global the global object
 107      * @return result object
 108      */
 109     protected IteratorResult makeResult(final Object value, final Boolean done, final Global global) {
 110         return new IteratorResult(value, done, global);
 111     }
 112 
 113     static MethodHandle getIteratorInvoker(final Global global) {
 114         return global.getDynamicInvoker(ITERATOR_INVOKER_KEY,
 115                 () -> Bootstrap.createDynamicCallInvoker(Object.class, Object.class, Object.class));
 116     }
 117 
 118     /**
 119      * Get the invoker for the ES6 iterator {@code next} method.
 120      * @param global the global object
 121      * @return the next invoker
 122      */
 123     public static InvokeByName getNextInvoker(final Global global) {
 124         return global.getInvokeByName(AbstractIterator.NEXT_INVOKER_KEY,
 125                 () -> new InvokeByName("next", Object.class, Object.class, Object.class));
 126     }
 127 
 128     /**
 129      * Get the invoker for the ES6 iterator result {@code done} property.
 130      * @param global the global object
 131      * @return the done invoker
 132      */
 133     public static MethodHandle getDoneInvoker(final Global global) {
 134         return global.getDynamicInvoker(AbstractIterator.DONE_INVOKER_KEY,
 135                 () -> Bootstrap.createDynamicInvoker("done", NashornCallSiteDescriptor.GET_PROPERTY, Object.class, Object.class));
 136     }
 137 
 138     /**
 139      * Get the invoker for the ES6 iterator result {@code value} property.
 140      * @param global the global object
 141      * @return the value invoker
 142      */
 143     public static MethodHandle getValueInvoker(final Global global) {
 144         return global.getDynamicInvoker(AbstractIterator.VALUE_INVOKER_KEY,
 145                 () -> Bootstrap.createDynamicInvoker("value", NashornCallSiteDescriptor.GET_PROPERTY, Object.class, Object.class));
 146     }
 147 
 148     /**
 149      * ES6 7.4.1 GetIterator abstract operation
 150      *
 151      * @param iterable an object
 152      * @param global the global object
 153      * @return the iterator
 154      */
 155     public static Object getIterator(final Object iterable, final Global global) {
 156         final Object object = Global.toObject(iterable);
 157 
 158         if (object instanceof ScriptObject) {
 159             // TODO we need to implement fast property access for Symbol keys in order to use InvokeByName here.
 160             final Object getter = ((ScriptObject) object).get(NativeSymbol.iterator);
 161 
 162             if (Bootstrap.isCallable(getter)) {
 163                 try {
 164                     final MethodHandle invoker = getIteratorInvoker(global);
 165 
 166                     final Object value = invoker.invokeExact(getter, iterable);
 167                     if (JSType.isPrimitive(value)) {
 168                         throw typeError("not.an.object", ScriptRuntime.safeToString(value));
 169                     }
 170                     return value;
 171 
 172                 } catch (final Throwable t) {
 173                     throw new RuntimeException(t);
 174                 }
 175             }
 176             throw typeError("not.a.function", ScriptRuntime.safeToString(getter));
 177         }
 178 
 179         throw typeError("cannot.get.iterator", ScriptRuntime.safeToString(iterable));
 180     }
 181 
 182     /**
 183      * Iterate over an iterable object, passing every value to {@code consumer}.
 184      *
 185      * @param iterable an iterable object
 186      * @param global the current global
 187      * @param consumer the value consumer
 188      */
 189     public static void iterate(final Object iterable, final Global global, final Consumer<Object> consumer) {
 190 
 191         final Object iterator = AbstractIterator.getIterator(Global.toObject(iterable), global);
 192 
 193         final InvokeByName nextInvoker = getNextInvoker(global);
 194         final MethodHandle doneInvoker = getDoneInvoker(global);
 195         final MethodHandle valueInvoker = getValueInvoker(global);
 196 
 197         try {
 198             do {
 199                 final Object next = nextInvoker.getGetter().invokeExact(iterator);
 200                 if (!Bootstrap.isCallable(next)) {
 201                     break;
 202                 }
 203 
 204                 final Object result = nextInvoker.getInvoker().invokeExact(next, iterator, (Object) null);
 205                 if (!(result instanceof ScriptObject)) {
 206                     break;
 207                 }
 208 
 209                 final Object done = doneInvoker.invokeExact(result);
 210                 if (JSType.toBoolean(done)) {
 211                     break;
 212                 }
 213 
 214                 consumer.accept(valueInvoker.invokeExact(result));
 215 
 216             } while (true);
 217 
 218         } catch (final RuntimeException r) {
 219             throw r;
 220         } catch (final Throwable t) {
 221             throw new RuntimeException(t);
 222         }
 223 
 224     }
 225 }
 226 
 227