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
26package jdk.nashorn.internal.objects;
27
28import java.lang.invoke.MethodHandle;
29import jdk.nashorn.internal.objects.annotations.Attribute;
30import jdk.nashorn.internal.objects.annotations.Constructor;
31import jdk.nashorn.internal.objects.annotations.Function;
32import jdk.nashorn.internal.objects.annotations.Getter;
33import jdk.nashorn.internal.objects.annotations.ScriptClass;
34import jdk.nashorn.internal.objects.annotations.Where;
35import jdk.nashorn.internal.runtime.PropertyMap;
36import jdk.nashorn.internal.runtime.ScriptObject;
37import jdk.nashorn.internal.runtime.ScriptRuntime;
38import jdk.nashorn.internal.runtime.Undefined;
39import jdk.nashorn.internal.runtime.linker.Bootstrap;
40
41import static jdk.nashorn.internal.objects.NativeMap.convertKey;
42import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
43
44/**
45 * This implements the ECMA6 Set object.
46 */
47@ScriptClass("Set")
48public class NativeSet extends ScriptObject {
49
50    // our set/map implementation
51    private final LinkedMap map = new LinkedMap();
52
53    // Invoker for the forEach callback
54    private final static Object FOREACH_INVOKER_KEY = new Object();
55
56    // initialized by nasgen
57    private static PropertyMap $nasgenmap$;
58
59    private NativeSet(final ScriptObject proto, final PropertyMap map) {
60        super(proto, map);
61    }
62
63    /**
64     * ECMA6 23.1 Set constructor
65     *
66     * @param isNew  whether the new operator used
67     * @param self self reference
68     * @param arg optional iterable argument
69     * @return a new Set object
70     */
71    @Constructor(arity = 0)
72    public static Object construct(final boolean isNew, final Object self, final Object arg){
73        if (!isNew) {
74            throw typeError("constructor.requires.new", "Set");
75        }
76        final Global global = Global.instance();
77        final NativeSet set = new NativeSet(global.getSetPrototype(), $nasgenmap$);
78        populateSet(set.getJavaMap(), arg, global);
79        return set;
80    }
81
82    /**
83     * ECMA6 23.2.3.1 Set.prototype.add ( value )
84     *
85     * @param self the self reference
86     * @param value the value to add
87     * @return this Set object
88     */
89    @Function(attributes = Attribute.NOT_ENUMERABLE)
90    public static Object add(final Object self, final Object value) {
91        getNativeSet(self).map.set(convertKey(value), null);
92        return self;
93    }
94
95    /**
96     * ECMA6 23.2.3.7 Set.prototype.has ( value )
97     *
98     * @param self the self reference
99     * @param value the value
100     * @return true if value is contained
101     */
102    @Function(attributes = Attribute.NOT_ENUMERABLE)
103    public static boolean has(final Object self, final Object value) {
104        return getNativeSet(self).map.has(convertKey(value));
105    }
106
107    /**
108     * ECMA6 23.2.3.2 Set.prototype.clear ( )
109     *
110     * @param self the self reference
111     */
112    @Function(attributes = Attribute.NOT_ENUMERABLE)
113    public static void clear(final Object self) {
114        getNativeSet(self).map.clear();
115    }
116
117    /**
118     * ECMA6 23.2.3.4 Set.prototype.delete ( value )
119     *
120     * @param self the self reference
121     * @param value the value
122     * @return true if value was deleted
123     */
124    @Function(attributes = Attribute.NOT_ENUMERABLE)
125    public static boolean delete(final Object self, final Object value) {
126        return getNativeSet(self).map.delete(convertKey(value));
127    }
128
129    /**
130     * ECMA6 23.2.3.9 get Set.prototype.size
131     *
132     * @param self the self reference
133     * @return the number of contained values
134     */
135    @Getter(attributes = Attribute.NOT_ENUMERABLE | Attribute.IS_ACCESSOR, where = Where.PROTOTYPE)
136    public static int size(final Object self) {
137        return getNativeSet(self).map.size();
138    }
139
140    /**
141     * ECMA6 23.2.3.5 Set.prototype.entries ( )
142     *
143     * @param self the self reference
144     * @return an iterator over the Set object's entries
145     */
146    @Function(attributes = Attribute.NOT_ENUMERABLE)
147    public static Object entries(final Object self) {
148        return new SetIterator(getNativeSet(self), AbstractIterator.IterationKind.KEY_VALUE, Global.instance());
149    }
150
151    /**
152     * ECMA6 23.2.3.8 Set.prototype.keys ( )
153     *
154     * @param self the self reference
155     * @return an iterator over the Set object's values
156     */
157    @Function(attributes = Attribute.NOT_ENUMERABLE)
158    public static Object keys(final Object self) {
159        return new SetIterator(getNativeSet(self), AbstractIterator.IterationKind.KEY, Global.instance());
160    }
161
162    /**
163     * ECMA6 23.2.3.10 Set.prototype.values ( )
164     *
165     * @param self the self reference
166     * @return an iterator over the Set object's values
167     */
168    @Function(attributes = Attribute.NOT_ENUMERABLE)
169    public static Object values(final Object self) {
170        return new SetIterator(getNativeSet(self), AbstractIterator.IterationKind.VALUE, Global.instance());
171    }
172
173    /**
174     * ECMA6 23.2.3.11 Set.prototype [ @@iterator ] ( )
175     *
176     * @param self the self reference
177     * @return an iterator over the Set object's values
178     */
179    @Function(attributes = Attribute.NOT_ENUMERABLE, name = "@@iterator")
180    public static Object getIterator(final Object self) {
181        return new SetIterator(getNativeSet(self), AbstractIterator.IterationKind.VALUE, Global.instance());
182    }
183
184    /**
185     * ECMA6 23.2.3.6 Set.prototype.forEach ( callbackfn [ , thisArg ] )
186     *
187     * @param self the self reference
188     * @param callbackFn the callback function
189     * @param thisArg optional this object
190     */
191    @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
192    public static void forEach(final Object self, final Object callbackFn, final Object thisArg) {
193        final NativeSet set = getNativeSet(self);
194        if (!Bootstrap.isCallable(callbackFn)) {
195            throw typeError("not.a.function", ScriptRuntime.safeToString(callbackFn));
196        }
197        final MethodHandle invoker = Global.instance().getDynamicInvoker(FOREACH_INVOKER_KEY,
198                () -> Bootstrap.createDynamicCallInvoker(Object.class, Object.class, Object.class, Object.class, Object.class, Object.class));
199
200        final LinkedMap.LinkedMapIterator iterator = set.getJavaMap().getIterator();
201        for (;;) {
202            final LinkedMap.Node node = iterator.next();
203            if (node == null) {
204                break;
205            }
206
207            try {
208                final Object result = invoker.invokeExact(callbackFn, thisArg, node.getKey(), node.getKey(), self);
209            } catch (final RuntimeException | Error e) {
210                throw e;
211            } catch (final Throwable t) {
212                throw new RuntimeException(t);
213            }
214        }
215    }
216
217    @Override
218    public String getClassName() {
219        return "Set";
220    }
221
222    static void populateSet(final LinkedMap map, final Object arg, final Global global) {
223        if (arg != null && arg != Undefined.getUndefined()) {
224            AbstractIterator.iterate(arg, global, value -> map.set(convertKey(value), null));
225        }
226    }
227
228    LinkedMap getJavaMap() {
229        return map;
230    }
231
232    private static NativeSet getNativeSet(final Object self) {
233        if (self instanceof NativeSet) {
234            return (NativeSet) self;
235        } else {
236            throw typeError("not.a.set", ScriptRuntime.safeToString(self));
237        }
238    }
239}
240