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.util.Map;
  29 import java.util.WeakHashMap;
  30 import jdk.nashorn.internal.objects.annotations.Attribute;
  31 import jdk.nashorn.internal.objects.annotations.Constructor;
  32 import jdk.nashorn.internal.objects.annotations.Function;
  33 import jdk.nashorn.internal.objects.annotations.ScriptClass;
  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.Undefined;
  38 
  39 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  40 import static jdk.nashorn.internal.runtime.JSType.isPrimitive;
  41 
  42 /**
  43  * This implements the ECMA6 WeakMap object.
  44  */
  45 @ScriptClass("WeakMap")
  46 public class NativeWeakMap extends ScriptObject {
  47 
  48     private final Map<Object, Object> jmap = new WeakHashMap<>();
  49 
  50     // initialized by nasgen
  51     private static PropertyMap $nasgenmap$;
  52 
  53     private NativeWeakMap(final ScriptObject proto, final PropertyMap map) {
  54         super(proto, map);
  55     }
  56 
  57     /**
  58      * ECMA6 23.3.1 The WeakMap Constructor
  59      *
  60      * @param isNew  whether the new operator used
  61      * @param self self reference
  62      * @param arg optional iterable argument
  63      * @return a new WeakMap object
  64      */
  65     @Constructor(arity = 0)
  66     public static Object construct(final boolean isNew, final Object self, final Object arg) {
  67         if (!isNew) {
  68             throw typeError("constructor.requires.new", "WeakMap");
  69         }
  70         final Global global = Global.instance();
  71         final NativeWeakMap weakMap = new NativeWeakMap(global.getWeakMapPrototype(), $nasgenmap$);
  72         populateMap(weakMap.jmap, arg, global);
  73         return weakMap;
  74     }
  75 
  76     /**
  77      * ECMA6 23.3.3.5 WeakMap.prototype.set ( key , value )
  78      *
  79      * @param self the self reference
  80      * @param key the key
  81      * @param value the value
  82      * @return this WeakMap object
  83      */
  84     @Function(attributes = Attribute.NOT_ENUMERABLE)
  85     public static Object set(final Object self, final Object key, final Object value) {
  86         final NativeWeakMap map = getMap(self);
  87         map.jmap.put(checkKey(key), value);
  88         return self;
  89     }
  90 
  91     /**
  92      * ECMA6 23.3.3.3 WeakMap.prototype.get ( key )
  93      *
  94      * @param self the self reference
  95      * @param key the key
  96      * @return the associated value or undefined
  97      */
  98     @Function(attributes = Attribute.NOT_ENUMERABLE)
  99     public static Object get(final Object self, final Object key) {
 100         final NativeWeakMap map = getMap(self);
 101         if (isPrimitive(key)) {
 102             return Undefined.getUndefined();
 103         }
 104         return map.jmap.get(key);
 105     }
 106 
 107     /**
 108      * ECMA6 23.3.3.2 WeakMap.prototype.delete ( key )
 109      *
 110      * @param self the self reference
 111      * @param key the key to delete
 112      * @return true if the key was deleted
 113      */
 114     @Function(attributes = Attribute.NOT_ENUMERABLE)
 115     public static boolean delete(final Object self, final Object key) {
 116         final Map<Object, Object> map = getMap(self).jmap;
 117         if (isPrimitive(key)) {
 118             return false;
 119         }
 120         final boolean returnValue = map.containsKey(key);
 121         map.remove(key);
 122         return returnValue;
 123     }
 124 
 125     /**
 126      * ECMA6 23.3.3.4 WeakMap.prototype.has ( key )
 127      *
 128      * @param self the self reference
 129      * @param key the key
 130      * @return true if key is contained
 131      */
 132     @Function(attributes = Attribute.NOT_ENUMERABLE)
 133     public static boolean has(final Object self, final Object key) {
 134         final NativeWeakMap map = getMap(self);
 135         return !isPrimitive(key) && map.jmap.containsKey(key);
 136     }
 137 
 138     @Override
 139     public String getClassName() {
 140         return "WeakMap";
 141     }
 142 
 143     /**
 144      * Make sure {@code key} is not a JavaScript primitive value.
 145      *
 146      * @param key a key object
 147      * @return the valid key
 148      */
 149     static Object checkKey(final Object key) {
 150         if (isPrimitive(key)) {
 151             throw typeError("invalid.weak.key", ScriptRuntime.safeToString(key));
 152         }
 153         return key;
 154     }
 155 
 156     static void populateMap(final Map<Object, Object> map, final Object arg, final Global global) {
 157         // This method is similar to NativeMap.populateMap, but it uses a different
 158         // map implementation and the checking/conversion of keys differs as well.
 159         if (arg != null && arg != Undefined.getUndefined()) {
 160             AbstractIterator.iterate(arg, global, value -> {
 161                 if (isPrimitive(value)) {
 162                     throw typeError(global, "not.an.object", ScriptRuntime.safeToString(value));
 163                 }
 164                 if (value instanceof ScriptObject) {
 165                     final ScriptObject sobj = (ScriptObject) value;
 166                     map.put(checkKey(sobj.get(0)), sobj.get(1));
 167                 }
 168             });
 169         }
 170     }
 171 
 172     private static NativeWeakMap getMap(final Object self) {
 173         if (self instanceof NativeWeakMap) {
 174             return (NativeWeakMap)self;
 175         } else {
 176             throw typeError("not.a.weak.map", ScriptRuntime.safeToString(self));
 177         }
 178     }
 179 
 180 }
 181 
 182