Wed, 26 Jun 2013 15:40:52 +0200
8019157: Avoid calling ScriptObject.setProto() if possible
Reviewed-by: jlaskey, sundar
jlaskey@3 | 1 | /* |
jlaskey@7 | 2 | * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
jlaskey@3 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
jlaskey@3 | 4 | * |
jlaskey@3 | 5 | * This code is free software; you can redistribute it and/or modify it |
jlaskey@3 | 6 | * under the terms of the GNU General Public License version 2 only, as |
jlaskey@3 | 7 | * published by the Free Software Foundation. Oracle designates this |
jlaskey@3 | 8 | * particular file as subject to the "Classpath" exception as provided |
jlaskey@3 | 9 | * by Oracle in the LICENSE file that accompanied this code. |
jlaskey@3 | 10 | * |
jlaskey@3 | 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
jlaskey@3 | 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
jlaskey@3 | 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
jlaskey@3 | 14 | * version 2 for more details (a copy is included in the LICENSE file that |
jlaskey@3 | 15 | * accompanied this code). |
jlaskey@3 | 16 | * |
jlaskey@3 | 17 | * You should have received a copy of the GNU General Public License version |
jlaskey@3 | 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
jlaskey@3 | 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
jlaskey@3 | 20 | * |
jlaskey@3 | 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
jlaskey@3 | 22 | * or visit www.oracle.com if you need additional information or have any |
jlaskey@3 | 23 | * questions. |
jlaskey@3 | 24 | */ |
jlaskey@3 | 25 | |
jlaskey@3 | 26 | package jdk.nashorn.internal.runtime; |
jlaskey@3 | 27 | |
hannesw@380 | 28 | import jdk.nashorn.internal.scripts.JO; |
hannesw@380 | 29 | |
jlaskey@242 | 30 | import static jdk.nashorn.internal.runtime.PropertyHashMap.EMPTY_HASHMAP; |
jlaskey@3 | 31 | |
jlaskey@3 | 32 | import java.lang.invoke.MethodHandle; |
jlaskey@3 | 33 | import java.lang.invoke.SwitchPoint; |
jlaskey@3 | 34 | import java.lang.ref.WeakReference; |
jlaskey@3 | 35 | import java.util.Arrays; |
jlaskey@3 | 36 | import java.util.Collection; |
jlaskey@3 | 37 | import java.util.HashMap; |
jlaskey@3 | 38 | import java.util.Iterator; |
jlaskey@3 | 39 | import java.util.LinkedHashMap; |
jlaskey@3 | 40 | import java.util.Map; |
jlaskey@3 | 41 | import java.util.NoSuchElementException; |
jlaskey@3 | 42 | import java.util.WeakHashMap; |
jlaskey@3 | 43 | |
jlaskey@3 | 44 | /** |
jlaskey@3 | 45 | * Map of object properties. The PropertyMap is the "template" for JavaScript object |
jlaskey@3 | 46 | * layouts. It contains a map with prototype names as keys and {@link Property} instances |
jlaskey@3 | 47 | * as values. A PropertyMap is typically passed to the {@link ScriptObject} constructor |
jlaskey@3 | 48 | * to form the seed map for the ScriptObject. |
jlaskey@3 | 49 | * <p> |
jlaskey@3 | 50 | * All property maps are immutable. If a property is added, modified or removed, the mutator |
jlaskey@3 | 51 | * will return a new map. |
jlaskey@3 | 52 | */ |
jlaskey@3 | 53 | public final class PropertyMap implements Iterable<Object>, PropertyListener { |
jlaskey@3 | 54 | /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */ |
jlaskey@242 | 55 | public static final int NOT_EXTENSIBLE = 0b0000_0001; |
jlaskey@3 | 56 | /** This mask is used to preserve certain flags when cloning the PropertyMap. Others should not be copied */ |
jlaskey@3 | 57 | private static final int CLONEABLE_FLAGS_MASK = 0b0000_1111; |
jlaskey@3 | 58 | /** Has a listener been added to this property map. This flag is not copied when cloning a map. See {@link PropertyListener} */ |
jlaskey@3 | 59 | public static final int IS_LISTENER_ADDED = 0b0001_0000; |
jlaskey@3 | 60 | |
jlaskey@242 | 61 | /** Empty map used for seed map for JO$ objects */ |
jlaskey@242 | 62 | private static final PropertyMap EMPTY_MAP = new PropertyMap(EMPTY_HASHMAP); |
jlaskey@242 | 63 | |
jlaskey@3 | 64 | /** Map status flags. */ |
jlaskey@3 | 65 | private int flags; |
jlaskey@3 | 66 | |
jlaskey@3 | 67 | /** Map of properties. */ |
jlaskey@3 | 68 | private final PropertyHashMap properties; |
jlaskey@3 | 69 | |
jlaskey@242 | 70 | /** Number of fields in use. */ |
jlaskey@242 | 71 | private int fieldCount; |
jlaskey@242 | 72 | |
jlaskey@242 | 73 | /** Number of fields available. */ |
jlaskey@242 | 74 | private int fieldMaximum; |
jlaskey@3 | 75 | |
jlaskey@3 | 76 | /** Length of spill in use. */ |
jlaskey@3 | 77 | private int spillLength; |
jlaskey@3 | 78 | |
jlaskey@3 | 79 | /** {@link SwitchPoint}s for gets on inherited properties. */ |
jlaskey@3 | 80 | private Map<String, SwitchPoint> protoGetSwitches; |
jlaskey@3 | 81 | |
jlaskey@3 | 82 | /** History of maps, used to limit map duplication. */ |
jlaskey@3 | 83 | private HashMap<Property, PropertyMap> history; |
jlaskey@3 | 84 | |
jlaskey@3 | 85 | /** History of prototypes, used to limit map duplication. */ |
jlaskey@3 | 86 | private WeakHashMap<ScriptObject, WeakReference<PropertyMap>> protoHistory; |
jlaskey@3 | 87 | |
jlaskey@3 | 88 | /** Cache for hashCode */ |
jlaskey@3 | 89 | private int hashCode; |
jlaskey@3 | 90 | |
jlaskey@3 | 91 | /** |
jlaskey@3 | 92 | * Constructor. |
jlaskey@3 | 93 | * |
jlaskey@242 | 94 | * @param properties A {@link PropertyHashMap} with initial contents. |
jlaskey@242 | 95 | * @param fieldCount Number of fields in use. |
jlaskey@242 | 96 | * @param fieldMaximum Number of fields available. |
jlaskey@3 | 97 | */ |
jlaskey@242 | 98 | private PropertyMap(final PropertyHashMap properties, final int fieldCount, final int fieldMaximum) { |
jlaskey@242 | 99 | this.properties = properties; |
jlaskey@242 | 100 | this.fieldCount = fieldCount; |
jlaskey@242 | 101 | this.fieldMaximum = fieldMaximum; |
jlaskey@3 | 102 | |
jlaskey@3 | 103 | if (Context.DEBUG) { |
jlaskey@3 | 104 | count++; |
jlaskey@3 | 105 | } |
jlaskey@3 | 106 | } |
jlaskey@3 | 107 | |
jlaskey@3 | 108 | /** |
jlaskey@242 | 109 | * Constructor. |
jlaskey@242 | 110 | * |
jlaskey@242 | 111 | * @param properties A {@link PropertyHashMap} with initial contents. |
jlaskey@242 | 112 | */ |
jlaskey@242 | 113 | private PropertyMap(final PropertyHashMap properties) { |
jlaskey@242 | 114 | this(properties, 0, 0); |
jlaskey@242 | 115 | } |
jlaskey@242 | 116 | |
jlaskey@242 | 117 | /** |
jlaskey@3 | 118 | * Cloning constructor. |
jlaskey@3 | 119 | * |
jlaskey@3 | 120 | * @param propertyMap Existing property map. |
jlaskey@3 | 121 | * @param properties A {@link PropertyHashMap} with a new set of properties. |
jlaskey@3 | 122 | */ |
jlaskey@3 | 123 | private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties) { |
jlaskey@242 | 124 | this.properties = properties; |
jlaskey@242 | 125 | this.flags = propertyMap.getClonedFlags(); |
jlaskey@242 | 126 | this.spillLength = propertyMap.spillLength; |
jlaskey@242 | 127 | this.fieldCount = propertyMap.fieldCount; |
jlaskey@242 | 128 | this.fieldMaximum = propertyMap.fieldMaximum; |
jlaskey@3 | 129 | |
jlaskey@3 | 130 | if (Context.DEBUG) { |
jlaskey@3 | 131 | count++; |
jlaskey@3 | 132 | clonedCount++; |
jlaskey@3 | 133 | } |
jlaskey@3 | 134 | } |
jlaskey@3 | 135 | |
jlaskey@3 | 136 | /** |
jlaskey@242 | 137 | * Cloning constructor. |
jlaskey@242 | 138 | * |
jlaskey@242 | 139 | * @param propertyMap Existing property map. |
jlaskey@242 | 140 | */ |
jlaskey@242 | 141 | private PropertyMap(final PropertyMap propertyMap) { |
jlaskey@242 | 142 | this(propertyMap, propertyMap.properties); |
jlaskey@242 | 143 | } |
jlaskey@242 | 144 | |
jlaskey@242 | 145 | /** |
jlaskey@3 | 146 | * Duplicates this PropertyMap instance. This is used by nasgen generated |
jlaskey@3 | 147 | * prototype and constructor classes. {@link PropertyMap} used for singletons |
jlaskey@3 | 148 | * like these (and global instance) are duplicated using this method and used. |
jlaskey@3 | 149 | * The original filled map referenced by static fields of prototype and |
jlaskey@3 | 150 | * constructor classes are not touched. This allows multiple independent global |
jlaskey@3 | 151 | * instances to be used within a single context instance. |
jlaskey@3 | 152 | * |
jlaskey@3 | 153 | * @return Duplicated {@link PropertyMap}. |
jlaskey@3 | 154 | */ |
jlaskey@3 | 155 | public PropertyMap duplicate() { |
jlaskey@242 | 156 | return new PropertyMap(this.properties); |
jlaskey@3 | 157 | } |
jlaskey@3 | 158 | |
jlaskey@3 | 159 | /** |
jlaskey@3 | 160 | * Public property map allocator. |
jlaskey@3 | 161 | * |
jlaskey@3 | 162 | * @param structure Class the map's {@link AccessorProperty}s apply to. |
jlaskey@3 | 163 | * @param properties Collection of initial properties. |
jlaskey@242 | 164 | * @param fieldCount Number of fields in use. |
jlaskey@242 | 165 | * @param fieldMaximum Number of fields available. |
jlaskey@3 | 166 | * |
jlaskey@3 | 167 | * @return New {@link PropertyMap}. |
jlaskey@3 | 168 | */ |
jlaskey@242 | 169 | public static PropertyMap newMap(final Class<?> structure, final Collection<Property> properties, final int fieldCount, final int fieldMaximum) { |
jlaskey@3 | 170 | // Reduce the number of empty maps in the context. |
hannesw@380 | 171 | if (structure == JO.class) { |
jlaskey@242 | 172 | return EMPTY_MAP; |
jlaskey@3 | 173 | } |
jlaskey@3 | 174 | |
jlaskey@242 | 175 | PropertyHashMap newProperties = EMPTY_HASHMAP.immutableAdd(properties); |
jlaskey@3 | 176 | |
jlaskey@242 | 177 | return new PropertyMap(newProperties, fieldCount, fieldMaximum); |
jlaskey@3 | 178 | } |
jlaskey@3 | 179 | |
jlaskey@3 | 180 | /** |
jlaskey@3 | 181 | * Public property map factory allocator |
jlaskey@3 | 182 | * |
jlaskey@3 | 183 | * @param structure Class the map's {@link AccessorProperty}s apply to. |
jlaskey@3 | 184 | * |
jlaskey@3 | 185 | * @return New {@link PropertyMap}. |
jlaskey@3 | 186 | */ |
jlaskey@3 | 187 | public static PropertyMap newMap(final Class<?> structure) { |
jlaskey@242 | 188 | return newMap(structure, null, 0, 0); |
jlaskey@3 | 189 | } |
jlaskey@3 | 190 | |
jlaskey@3 | 191 | /** |
jlaskey@3 | 192 | * Return a sharable empty map. |
jlaskey@3 | 193 | * |
lagergren@8 | 194 | * @param context the context |
jlaskey@3 | 195 | * @return New empty {@link PropertyMap}. |
jlaskey@3 | 196 | */ |
lagergren@8 | 197 | public static PropertyMap newEmptyMap(final Context context) { |
jlaskey@242 | 198 | return new PropertyMap(EMPTY_HASHMAP); |
jlaskey@3 | 199 | } |
jlaskey@3 | 200 | |
jlaskey@3 | 201 | /** |
jlaskey@3 | 202 | * Return number of properties in the map. |
jlaskey@3 | 203 | * |
jlaskey@3 | 204 | * @return Number of properties. |
jlaskey@3 | 205 | */ |
jlaskey@3 | 206 | public int size() { |
jlaskey@3 | 207 | return properties.size(); |
jlaskey@3 | 208 | } |
jlaskey@3 | 209 | |
jlaskey@3 | 210 | /** |
jlaskey@3 | 211 | * Return a SwitchPoint used to track changes of a property in a prototype. |
jlaskey@3 | 212 | * |
jlaskey@242 | 213 | * @param proto Object prototype. |
jlaskey@242 | 214 | * @param key {@link Property} key. |
jlaskey@3 | 215 | * |
jlaskey@3 | 216 | * @return A shared {@link SwitchPoint} for the property. |
jlaskey@3 | 217 | */ |
jlaskey@242 | 218 | public SwitchPoint getProtoGetSwitchPoint(final ScriptObject proto, final String key) { |
jlaskey@3 | 219 | if (proto == null) { |
jlaskey@3 | 220 | return null; |
jlaskey@3 | 221 | } |
jlaskey@3 | 222 | |
jlaskey@3 | 223 | if (protoGetSwitches == null) { |
jlaskey@3 | 224 | protoGetSwitches = new HashMap<>(); |
jlaskey@3 | 225 | if (! isListenerAdded()) { |
jlaskey@3 | 226 | proto.addPropertyListener(this); |
jlaskey@3 | 227 | setIsListenerAdded(); |
jlaskey@3 | 228 | } |
jlaskey@3 | 229 | } |
jlaskey@3 | 230 | |
jlaskey@3 | 231 | if (protoGetSwitches.containsKey(key)) { |
jlaskey@3 | 232 | return protoGetSwitches.get(key); |
jlaskey@3 | 233 | } |
jlaskey@3 | 234 | |
jlaskey@3 | 235 | final SwitchPoint switchPoint = new SwitchPoint(); |
jlaskey@3 | 236 | protoGetSwitches.put(key, switchPoint); |
jlaskey@3 | 237 | |
jlaskey@3 | 238 | return switchPoint; |
jlaskey@3 | 239 | } |
jlaskey@3 | 240 | |
jlaskey@3 | 241 | /** |
jlaskey@3 | 242 | * Indicate that a prototype property hash changed. |
jlaskey@3 | 243 | * |
jlaskey@3 | 244 | * @param property {@link Property} to invalidate. |
jlaskey@3 | 245 | */ |
jlaskey@3 | 246 | private void invalidateProtoGetSwitchPoint(final Property property) { |
jlaskey@3 | 247 | if (protoGetSwitches != null) { |
jlaskey@3 | 248 | final String key = property.getKey(); |
jlaskey@3 | 249 | final SwitchPoint sp = protoGetSwitches.get(key); |
jlaskey@3 | 250 | if (sp != null) { |
jlaskey@3 | 251 | protoGetSwitches.put(key, new SwitchPoint()); |
jlaskey@3 | 252 | if (Context.DEBUG) { |
jlaskey@3 | 253 | protoInvalidations++; |
jlaskey@3 | 254 | } |
jlaskey@3 | 255 | SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); |
jlaskey@3 | 256 | } |
jlaskey@3 | 257 | } |
jlaskey@3 | 258 | } |
jlaskey@3 | 259 | |
jlaskey@3 | 260 | /** |
jlaskey@3 | 261 | * Add a property to the map. |
jlaskey@3 | 262 | * |
jlaskey@3 | 263 | * @param property {@link Property} being added. |
jlaskey@3 | 264 | * |
jlaskey@3 | 265 | * @return New {@link PropertyMap} with {@link Property} added. |
jlaskey@3 | 266 | */ |
jlaskey@3 | 267 | public PropertyMap newProperty(final Property property) { |
jlaskey@3 | 268 | return addProperty(property); |
jlaskey@3 | 269 | } |
jlaskey@3 | 270 | |
jlaskey@3 | 271 | /** |
jlaskey@3 | 272 | * Add a property to the map, re-binding its getters and setters, |
jlaskey@3 | 273 | * if available, to a given receiver. This is typically the global scope. See |
jlaskey@3 | 274 | * {@link ScriptObject#addBoundProperties(ScriptObject)} |
jlaskey@3 | 275 | * |
jlaskey@3 | 276 | * @param property {@link Property} being added. |
jlaskey@3 | 277 | * @param bindTo Object to bind to. |
jlaskey@3 | 278 | * |
jlaskey@3 | 279 | * @return New {@link PropertyMap} with {@link Property} added. |
jlaskey@3 | 280 | */ |
jlaskey@3 | 281 | PropertyMap newPropertyBind(final AccessorProperty property, final ScriptObject bindTo) { |
jlaskey@3 | 282 | return newProperty(new AccessorProperty(property, bindTo)); |
jlaskey@3 | 283 | } |
jlaskey@3 | 284 | |
jlaskey@3 | 285 | /** |
jlaskey@3 | 286 | * Add a new accessor property to the map. |
jlaskey@3 | 287 | * |
jlaskey@3 | 288 | * @param key {@link Property} key. |
jlaskey@3 | 289 | * @param propertyFlags {@link Property} flags. |
jlaskey@80 | 290 | * @param slot {@link Property} slot. |
jlaskey@3 | 291 | * @param getter {@link Property} get accessor method. |
jlaskey@3 | 292 | * @param setter {@link Property} set accessor method. |
jlaskey@3 | 293 | * |
jlaskey@3 | 294 | * @return New {@link PropertyMap} with {@link AccessorProperty} added. |
jlaskey@3 | 295 | */ |
jlaskey@80 | 296 | public PropertyMap newProperty(final String key, final int propertyFlags, final int slot, final MethodHandle getter, final MethodHandle setter) { |
jlaskey@80 | 297 | return newProperty(new AccessorProperty(key, propertyFlags, slot, getter, setter)); |
jlaskey@3 | 298 | } |
jlaskey@3 | 299 | |
jlaskey@3 | 300 | /** |
jlaskey@3 | 301 | * Add a property to the map. Cloning or using an existing map if available. |
jlaskey@3 | 302 | * |
jlaskey@3 | 303 | * @param property {@link Property} being added. |
jlaskey@3 | 304 | * |
jlaskey@3 | 305 | * @return New {@link PropertyMap} with {@link Property} added. |
jlaskey@3 | 306 | */ |
jlaskey@379 | 307 | public PropertyMap addProperty(final Property property) { |
jlaskey@3 | 308 | PropertyMap newMap = checkHistory(property); |
jlaskey@3 | 309 | |
jlaskey@3 | 310 | if (newMap == null) { |
jlaskey@3 | 311 | final PropertyHashMap newProperties = properties.immutableAdd(property); |
jlaskey@3 | 312 | newMap = new PropertyMap(this, newProperties); |
jlaskey@3 | 313 | addToHistory(property, newMap); |
jlaskey@242 | 314 | |
jlaskey@242 | 315 | if(!property.isSpill()) { |
jlaskey@242 | 316 | newMap.fieldCount = Math.max(newMap.fieldCount, property.getSlot() + 1); |
jlaskey@242 | 317 | } |
jlaskey@242 | 318 | |
jlaskey@3 | 319 | newMap.spillLength += property.getSpillCount(); |
jlaskey@3 | 320 | } |
jlaskey@3 | 321 | |
jlaskey@3 | 322 | return newMap; |
jlaskey@3 | 323 | } |
jlaskey@3 | 324 | |
jlaskey@3 | 325 | /** |
jlaskey@3 | 326 | * Remove a property from a map. Cloning or using an existing map if available. |
jlaskey@3 | 327 | * |
jlaskey@3 | 328 | * @param property {@link Property} being removed. |
jlaskey@3 | 329 | * |
jlaskey@3 | 330 | * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found. |
jlaskey@3 | 331 | */ |
attila@84 | 332 | public PropertyMap deleteProperty(final Property property) { |
jlaskey@3 | 333 | PropertyMap newMap = checkHistory(property); |
jlaskey@3 | 334 | final String key = property.getKey(); |
jlaskey@3 | 335 | |
jlaskey@3 | 336 | if (newMap == null && properties.containsKey(key)) { |
jlaskey@3 | 337 | final PropertyHashMap newProperties = properties.immutableRemove(key); |
jlaskey@3 | 338 | newMap = new PropertyMap(this, newProperties); |
jlaskey@3 | 339 | addToHistory(property, newMap); |
jlaskey@3 | 340 | } |
jlaskey@3 | 341 | |
jlaskey@3 | 342 | return newMap; |
jlaskey@3 | 343 | } |
jlaskey@3 | 344 | |
jlaskey@3 | 345 | /** |
jlaskey@3 | 346 | * Replace an existing property with a new one. |
jlaskey@3 | 347 | * |
jlaskey@3 | 348 | * @param oldProperty Property to replace. |
jlaskey@3 | 349 | * @param newProperty New {@link Property}. |
jlaskey@3 | 350 | * |
jlaskey@3 | 351 | * @return New {@link PropertyMap} with {@link Property} replaced. |
jlaskey@3 | 352 | */ |
jlaskey@3 | 353 | PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) { |
jlaskey@3 | 354 | // Add replaces existing property. |
jlaskey@3 | 355 | final PropertyHashMap newProperties = properties.immutableAdd(newProperty); |
jlaskey@3 | 356 | final PropertyMap newMap = new PropertyMap(this, newProperties); |
jlaskey@3 | 357 | |
jlaskey@3 | 358 | /* |
jlaskey@3 | 359 | * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods. |
jlaskey@3 | 360 | * |
jlaskey@3 | 361 | * This replaceProperty method is called only for the following three cases: |
jlaskey@3 | 362 | * |
jlaskey@3 | 363 | * 1. To change flags OR TYPE of an old (cloned) property. We use the same spill slots. |
jlaskey@3 | 364 | * 2. To change one UserAccessor property with another - user getter or setter changed via |
jlaskey@3 | 365 | * Object.defineProperty function. Again, same spill slots are re-used. |
jlaskey@3 | 366 | * 3. Via ScriptObject.setUserAccessors method to set user getter and setter functions |
jlaskey@3 | 367 | * replacing the dummy AccessorProperty with null method handles (added during map init). |
jlaskey@3 | 368 | * |
jlaskey@3 | 369 | * In case (1) and case(2), the property type of old and new property is same. For case (3), |
jlaskey@3 | 370 | * the old property is an AccessorProperty and the new one is a UserAccessorProperty property. |
jlaskey@3 | 371 | */ |
jlaskey@3 | 372 | |
jlaskey@3 | 373 | final boolean sameType = (oldProperty.getClass() == newProperty.getClass()); |
jlaskey@3 | 374 | assert sameType || |
jlaskey@3 | 375 | (oldProperty instanceof AccessorProperty && |
jlaskey@3 | 376 | newProperty instanceof UserAccessorProperty) : "arbitrary replaceProperty attempted"; |
jlaskey@3 | 377 | |
jlaskey@3 | 378 | newMap.flags = getClonedFlags(); |
jlaskey@3 | 379 | |
jlaskey@3 | 380 | /* |
jlaskey@3 | 381 | * spillLength remains same in case (1) and (2) because of slot reuse. Only for case (3), we need |
jlaskey@3 | 382 | * to add spill count of the newly added UserAccessorProperty property. |
jlaskey@3 | 383 | */ |
jlaskey@3 | 384 | newMap.spillLength = spillLength + (sameType? 0 : newProperty.getSpillCount()); |
jlaskey@3 | 385 | return newMap; |
jlaskey@3 | 386 | } |
jlaskey@3 | 387 | |
jlaskey@379 | 388 | /* |
jlaskey@379 | 389 | * Make a new UserAccessorProperty property. getter and setter functions are stored in |
jlaskey@379 | 390 | * this ScriptObject and slot values are used in property object. Note that slots |
jlaskey@379 | 391 | * are assigned speculatively and should be added to map before adding other |
jlaskey@379 | 392 | * properties. |
jlaskey@379 | 393 | */ |
jlaskey@379 | 394 | public UserAccessorProperty newUserAccessors(final String key, final int propertyFlags) { |
jlaskey@379 | 395 | int oldSpillLength = spillLength; |
jlaskey@379 | 396 | |
jlaskey@379 | 397 | final int getterSlot = oldSpillLength++; |
jlaskey@379 | 398 | final int setterSlot = oldSpillLength++; |
jlaskey@379 | 399 | |
jlaskey@379 | 400 | return new UserAccessorProperty(key, propertyFlags, getterSlot, setterSlot); |
jlaskey@379 | 401 | } |
jlaskey@379 | 402 | |
jlaskey@3 | 403 | /** |
jlaskey@3 | 404 | * Find a property in the map. |
jlaskey@3 | 405 | * |
jlaskey@3 | 406 | * @param key Key to search for. |
jlaskey@3 | 407 | * |
jlaskey@3 | 408 | * @return {@link Property} matching key. |
jlaskey@3 | 409 | */ |
jlaskey@3 | 410 | public Property findProperty(final String key) { |
jlaskey@3 | 411 | return properties.find(key); |
jlaskey@3 | 412 | } |
jlaskey@3 | 413 | |
jlaskey@3 | 414 | /** |
jlaskey@3 | 415 | * Adds all map properties from another map. |
jlaskey@3 | 416 | * |
jlaskey@3 | 417 | * @param other The source of properties. |
jlaskey@3 | 418 | * |
jlaskey@3 | 419 | * @return New {@link PropertyMap} with added properties. |
jlaskey@3 | 420 | */ |
jlaskey@3 | 421 | public PropertyMap addAll(final PropertyMap other) { |
hannesw@72 | 422 | assert this != other : "adding property map to itself"; |
jlaskey@3 | 423 | final Property[] otherProperties = other.properties.getProperties(); |
jlaskey@3 | 424 | final PropertyHashMap newProperties = properties.immutableAdd(otherProperties); |
jlaskey@3 | 425 | |
jlaskey@3 | 426 | final PropertyMap newMap = new PropertyMap(this, newProperties); |
jlaskey@3 | 427 | for (final Property property : otherProperties) { |
jlaskey@3 | 428 | newMap.spillLength += property.getSpillCount(); |
jlaskey@3 | 429 | } |
jlaskey@3 | 430 | |
jlaskey@3 | 431 | return newMap; |
jlaskey@3 | 432 | } |
jlaskey@3 | 433 | |
jlaskey@3 | 434 | /** |
jlaskey@3 | 435 | * Return an array of all properties. |
jlaskey@3 | 436 | * |
jlaskey@3 | 437 | * @return Properties as an array. |
jlaskey@3 | 438 | */ |
jlaskey@3 | 439 | public Property[] getProperties() { |
jlaskey@3 | 440 | return properties.getProperties(); |
jlaskey@3 | 441 | } |
jlaskey@3 | 442 | |
jlaskey@3 | 443 | /** |
jlaskey@3 | 444 | * Prevents the map from having additional properties. |
jlaskey@3 | 445 | * |
jlaskey@3 | 446 | * @return New map with {@link #NOT_EXTENSIBLE} flag set. |
jlaskey@3 | 447 | */ |
jlaskey@3 | 448 | PropertyMap preventExtensions() { |
jlaskey@242 | 449 | final PropertyMap newMap = new PropertyMap(this); |
jlaskey@3 | 450 | newMap.flags |= NOT_EXTENSIBLE; |
jlaskey@3 | 451 | return newMap; |
jlaskey@3 | 452 | } |
jlaskey@3 | 453 | |
jlaskey@3 | 454 | /** |
jlaskey@3 | 455 | * Prevents properties in map from being modified. |
jlaskey@3 | 456 | * |
sundar@10 | 457 | * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with |
sundar@10 | 458 | * {@link Property#NOT_CONFIGURABLE} set. |
jlaskey@3 | 459 | */ |
jlaskey@3 | 460 | PropertyMap seal() { |
jlaskey@242 | 461 | PropertyHashMap newProperties = EMPTY_HASHMAP; |
jlaskey@3 | 462 | |
jlaskey@3 | 463 | for (final Property oldProperty : properties.getProperties()) { |
jlaskey@3 | 464 | newProperties = newProperties.immutableAdd(oldProperty.addFlags(Property.NOT_CONFIGURABLE)); |
jlaskey@3 | 465 | } |
jlaskey@3 | 466 | |
jlaskey@3 | 467 | final PropertyMap newMap = new PropertyMap(this, newProperties); |
jlaskey@3 | 468 | newMap.flags |= NOT_EXTENSIBLE; |
jlaskey@3 | 469 | |
jlaskey@3 | 470 | return newMap; |
jlaskey@3 | 471 | } |
jlaskey@3 | 472 | |
jlaskey@3 | 473 | /** |
jlaskey@3 | 474 | * Prevents properties in map from being modified or written to. |
jlaskey@3 | 475 | * |
jlaskey@3 | 476 | * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with |
sundar@10 | 477 | * {@link Property#NOT_CONFIGURABLE} and {@link Property#NOT_WRITABLE} set. |
jlaskey@3 | 478 | */ |
jlaskey@3 | 479 | PropertyMap freeze() { |
jlaskey@242 | 480 | PropertyHashMap newProperties = EMPTY_HASHMAP; |
jlaskey@3 | 481 | |
jlaskey@3 | 482 | for (Property oldProperty : properties.getProperties()) { |
jlaskey@3 | 483 | int propertyFlags = Property.NOT_CONFIGURABLE; |
jlaskey@3 | 484 | |
jlaskey@3 | 485 | if (!(oldProperty instanceof UserAccessorProperty)) { |
jlaskey@3 | 486 | propertyFlags |= Property.NOT_WRITABLE; |
jlaskey@3 | 487 | } |
jlaskey@3 | 488 | |
jlaskey@3 | 489 | newProperties = newProperties.immutableAdd(oldProperty.addFlags(propertyFlags)); |
jlaskey@3 | 490 | } |
jlaskey@3 | 491 | |
jlaskey@3 | 492 | final PropertyMap newMap = new PropertyMap(this, newProperties); |
jlaskey@3 | 493 | newMap.flags |= NOT_EXTENSIBLE; |
jlaskey@3 | 494 | |
jlaskey@3 | 495 | return newMap; |
jlaskey@3 | 496 | } |
jlaskey@3 | 497 | |
jlaskey@3 | 498 | /** |
jlaskey@3 | 499 | * Check for any configurable properties. |
jlaskey@3 | 500 | * |
jlaskey@3 | 501 | * @return {@code true} if any configurable. |
jlaskey@3 | 502 | */ |
jlaskey@3 | 503 | private boolean anyConfigurable() { |
jlaskey@3 | 504 | for (final Property property : properties.getProperties()) { |
jlaskey@3 | 505 | if (property.isConfigurable()) { |
jlaskey@3 | 506 | return true; |
jlaskey@3 | 507 | } |
jlaskey@3 | 508 | } |
jlaskey@3 | 509 | |
jlaskey@3 | 510 | return false; |
jlaskey@3 | 511 | } |
jlaskey@3 | 512 | |
jlaskey@3 | 513 | /** |
jlaskey@3 | 514 | * Check if all properties are frozen. |
jlaskey@3 | 515 | * |
jlaskey@3 | 516 | * @return {@code true} if all are frozen. |
jlaskey@3 | 517 | */ |
jlaskey@3 | 518 | private boolean allFrozen() { |
jlaskey@3 | 519 | for (final Property property : properties.getProperties()) { |
jlaskey@3 | 520 | // check if it is a data descriptor |
jlaskey@3 | 521 | if (!(property instanceof UserAccessorProperty)) { |
jlaskey@3 | 522 | if (property.isWritable()) { |
jlaskey@3 | 523 | return false; |
jlaskey@3 | 524 | } |
jlaskey@3 | 525 | } |
jlaskey@3 | 526 | if (property.isConfigurable()) { |
jlaskey@3 | 527 | return false; |
jlaskey@3 | 528 | } |
jlaskey@3 | 529 | } |
jlaskey@3 | 530 | |
jlaskey@3 | 531 | return true; |
jlaskey@3 | 532 | } |
jlaskey@3 | 533 | |
jlaskey@3 | 534 | /** |
jlaskey@3 | 535 | * Check prototype history for an existing property map with specified prototype. |
jlaskey@3 | 536 | * |
jlaskey@3 | 537 | * @param newProto New prototype object. |
jlaskey@3 | 538 | * |
jlaskey@3 | 539 | * @return Existing {@link PropertyMap} or {@code null} if not found. |
jlaskey@3 | 540 | */ |
jlaskey@3 | 541 | private PropertyMap checkProtoHistory(final ScriptObject newProto) { |
jlaskey@3 | 542 | final PropertyMap cachedMap; |
jlaskey@3 | 543 | if (protoHistory != null) { |
jlaskey@3 | 544 | final WeakReference<PropertyMap> weakMap = protoHistory.get(newProto); |
jlaskey@3 | 545 | cachedMap = (weakMap != null ? weakMap.get() : null); |
jlaskey@3 | 546 | } else { |
jlaskey@3 | 547 | cachedMap = null; |
jlaskey@3 | 548 | } |
jlaskey@3 | 549 | |
jlaskey@3 | 550 | if (Context.DEBUG && cachedMap != null) { |
jlaskey@3 | 551 | protoHistoryHit++; |
jlaskey@3 | 552 | } |
jlaskey@3 | 553 | |
jlaskey@3 | 554 | return cachedMap; |
jlaskey@3 | 555 | } |
jlaskey@3 | 556 | |
jlaskey@3 | 557 | /** |
jlaskey@3 | 558 | * Add a map to the prototype history. |
jlaskey@3 | 559 | * |
jlaskey@3 | 560 | * @param newProto Prototype to add (key.) |
jlaskey@3 | 561 | * @param newMap {@link PropertyMap} associated with prototype. |
jlaskey@3 | 562 | */ |
jlaskey@3 | 563 | private void addToProtoHistory(final ScriptObject newProto, final PropertyMap newMap) { |
hannesw@157 | 564 | if (protoHistory == null) { |
hannesw@157 | 565 | protoHistory = new WeakHashMap<>(); |
hannesw@157 | 566 | } |
jlaskey@156 | 567 | |
hannesw@157 | 568 | protoHistory.put(newProto, new WeakReference<>(newMap)); |
jlaskey@3 | 569 | } |
jlaskey@3 | 570 | |
jlaskey@3 | 571 | /** |
jlaskey@3 | 572 | * Track the modification of the map. |
jlaskey@3 | 573 | * |
jlaskey@3 | 574 | * @param property Mapping property. |
jlaskey@3 | 575 | * @param newMap Modified {@link PropertyMap}. |
jlaskey@3 | 576 | */ |
jlaskey@3 | 577 | private void addToHistory(final Property property, final PropertyMap newMap) { |
jlaskey@172 | 578 | if (!properties.isEmpty()) { |
jlaskey@172 | 579 | if (history == null) { |
jlaskey@172 | 580 | history = new LinkedHashMap<>(); |
jlaskey@172 | 581 | } |
jlaskey@172 | 582 | |
jlaskey@172 | 583 | history.put(property, newMap); |
hannesw@157 | 584 | } |
jlaskey@3 | 585 | } |
jlaskey@3 | 586 | |
jlaskey@3 | 587 | /** |
jlaskey@3 | 588 | * Check the history for a map that already has the given property added. |
jlaskey@3 | 589 | * |
jlaskey@3 | 590 | * @param property {@link Property} to add. |
jlaskey@3 | 591 | * |
jlaskey@3 | 592 | * @return Existing map or {@code null} if not found. |
jlaskey@3 | 593 | */ |
jlaskey@3 | 594 | private PropertyMap checkHistory(final Property property) { |
jlaskey@3 | 595 | if (history != null) { |
jlaskey@3 | 596 | PropertyMap historicMap = history.get(property); |
jlaskey@3 | 597 | |
jlaskey@3 | 598 | if (historicMap != null) { |
jlaskey@3 | 599 | if (Context.DEBUG) { |
jlaskey@3 | 600 | historyHit++; |
jlaskey@3 | 601 | } |
jlaskey@3 | 602 | |
jlaskey@3 | 603 | return historicMap; |
jlaskey@3 | 604 | } |
jlaskey@3 | 605 | } |
jlaskey@3 | 606 | |
jlaskey@3 | 607 | return null; |
jlaskey@3 | 608 | } |
jlaskey@3 | 609 | |
jlaskey@3 | 610 | /** |
jlaskey@3 | 611 | * Calculate the hash code for the map. |
jlaskey@3 | 612 | * |
jlaskey@3 | 613 | * @return Computed hash code. |
jlaskey@3 | 614 | */ |
jlaskey@3 | 615 | private int computeHashCode() { |
jlaskey@242 | 616 | int hash = 0; |
jlaskey@3 | 617 | |
jlaskey@3 | 618 | for (final Property property : getProperties()) { |
jlaskey@3 | 619 | hash = hash << 7 ^ hash >> 7; |
jlaskey@3 | 620 | hash ^= property.hashCode(); |
jlaskey@3 | 621 | } |
jlaskey@3 | 622 | |
jlaskey@3 | 623 | return hash; |
jlaskey@3 | 624 | } |
jlaskey@3 | 625 | |
jlaskey@3 | 626 | @Override |
jlaskey@3 | 627 | public int hashCode() { |
hannesw@341 | 628 | if (hashCode == 0 && !properties.isEmpty()) { |
hannesw@341 | 629 | hashCode = computeHashCode(); |
hannesw@341 | 630 | } |
jlaskey@3 | 631 | return hashCode; |
jlaskey@3 | 632 | } |
jlaskey@3 | 633 | |
jlaskey@3 | 634 | @Override |
jlaskey@3 | 635 | public boolean equals(final Object other) { |
jlaskey@3 | 636 | if (!(other instanceof PropertyMap)) { |
jlaskey@3 | 637 | return false; |
jlaskey@3 | 638 | } |
jlaskey@3 | 639 | |
jlaskey@3 | 640 | final PropertyMap otherMap = (PropertyMap)other; |
jlaskey@3 | 641 | |
jlaskey@242 | 642 | if (properties.size() != otherMap.properties.size()) { |
jlaskey@3 | 643 | return false; |
jlaskey@3 | 644 | } |
jlaskey@3 | 645 | |
jlaskey@3 | 646 | final Iterator<Property> iter = properties.values().iterator(); |
jlaskey@3 | 647 | final Iterator<Property> otherIter = otherMap.properties.values().iterator(); |
jlaskey@3 | 648 | |
jlaskey@3 | 649 | while (iter.hasNext() && otherIter.hasNext()) { |
jlaskey@3 | 650 | if (!iter.next().equals(otherIter.next())) { |
jlaskey@3 | 651 | return false; |
jlaskey@3 | 652 | } |
jlaskey@3 | 653 | } |
jlaskey@3 | 654 | |
jlaskey@3 | 655 | return true; |
jlaskey@3 | 656 | } |
jlaskey@3 | 657 | |
jlaskey@3 | 658 | @Override |
jlaskey@3 | 659 | public String toString() { |
jlaskey@3 | 660 | final StringBuilder sb = new StringBuilder(); |
jlaskey@3 | 661 | |
jlaskey@3 | 662 | sb.append(" ["); |
jlaskey@3 | 663 | boolean isFirst = true; |
jlaskey@3 | 664 | |
jlaskey@3 | 665 | for (final Property property : properties.values()) { |
jlaskey@3 | 666 | if (!isFirst) { |
jlaskey@3 | 667 | sb.append(", "); |
jlaskey@3 | 668 | } |
jlaskey@3 | 669 | |
jlaskey@3 | 670 | isFirst = false; |
jlaskey@3 | 671 | |
jlaskey@3 | 672 | sb.append(ScriptRuntime.safeToString(property.getKey())); |
jlaskey@3 | 673 | final Class<?> ctype = property.getCurrentType(); |
lagergren@57 | 674 | sb.append(" <"). |
lagergren@57 | 675 | append(property.getClass().getSimpleName()). |
lagergren@57 | 676 | append(':'). |
lagergren@57 | 677 | append(ctype == null ? |
lagergren@57 | 678 | "undefined" : |
lagergren@57 | 679 | ctype.getSimpleName()). |
lagergren@57 | 680 | append('>'); |
jlaskey@3 | 681 | } |
jlaskey@3 | 682 | |
jlaskey@3 | 683 | sb.append(']'); |
jlaskey@3 | 684 | |
jlaskey@3 | 685 | return sb.toString(); |
jlaskey@3 | 686 | } |
jlaskey@3 | 687 | |
jlaskey@3 | 688 | @Override |
jlaskey@3 | 689 | public Iterator<Object> iterator() { |
jlaskey@3 | 690 | return new PropertyMapIterator(this); |
jlaskey@3 | 691 | } |
jlaskey@3 | 692 | |
jlaskey@3 | 693 | /** |
jlaskey@3 | 694 | * Check whether a {@link PropertyListener} has been added to this map. |
jlaskey@3 | 695 | * |
jlaskey@3 | 696 | * @return {@code true} if {@link PropertyListener} exists |
jlaskey@3 | 697 | */ |
jlaskey@3 | 698 | public boolean isListenerAdded() { |
jlaskey@3 | 699 | return (flags & IS_LISTENER_ADDED) != 0; |
jlaskey@3 | 700 | } |
jlaskey@3 | 701 | |
jlaskey@3 | 702 | /** |
jlaskey@3 | 703 | * Test to see if {@link PropertyMap} is extensible. |
jlaskey@3 | 704 | * |
jlaskey@3 | 705 | * @return {@code true} if {@link PropertyMap} can be added to. |
jlaskey@3 | 706 | */ |
jlaskey@3 | 707 | boolean isExtensible() { |
jlaskey@3 | 708 | return (flags & NOT_EXTENSIBLE) == 0; |
jlaskey@3 | 709 | } |
jlaskey@3 | 710 | |
jlaskey@3 | 711 | /** |
jlaskey@3 | 712 | * Test to see if {@link PropertyMap} is not extensible or any properties |
jlaskey@3 | 713 | * can not be modified. |
jlaskey@3 | 714 | * |
jlaskey@3 | 715 | * @return {@code true} if {@link PropertyMap} is sealed. |
jlaskey@3 | 716 | */ |
jlaskey@3 | 717 | boolean isSealed() { |
jlaskey@3 | 718 | return !isExtensible() && !anyConfigurable(); |
jlaskey@3 | 719 | } |
jlaskey@3 | 720 | |
jlaskey@3 | 721 | /** |
jlaskey@3 | 722 | * Test to see if {@link PropertyMap} is not extensible or all properties |
jlaskey@3 | 723 | * can not be modified. |
jlaskey@3 | 724 | * |
jlaskey@3 | 725 | * @return {@code true} if {@link PropertyMap} is frozen. |
jlaskey@3 | 726 | */ |
jlaskey@3 | 727 | boolean isFrozen() { |
jlaskey@3 | 728 | return !isExtensible() && allFrozen(); |
jlaskey@3 | 729 | } |
jlaskey@242 | 730 | /** |
jlaskey@242 | 731 | * Get the number of fields allocated for this {@link PropertyMap}. |
jlaskey@242 | 732 | * |
jlaskey@242 | 733 | * @return Number of fields allocated. |
jlaskey@242 | 734 | */ |
jlaskey@242 | 735 | int getFieldCount() { |
jlaskey@242 | 736 | return fieldCount; |
jlaskey@242 | 737 | } |
jlaskey@242 | 738 | /** |
jlaskey@242 | 739 | * Get maximum number of fields available for this {@link PropertyMap}. |
jlaskey@242 | 740 | * |
jlaskey@242 | 741 | * @return Number of fields available. |
jlaskey@242 | 742 | */ |
jlaskey@242 | 743 | int getFieldMaximum() { |
jlaskey@242 | 744 | return fieldMaximum; |
jlaskey@242 | 745 | } |
jlaskey@3 | 746 | |
jlaskey@3 | 747 | /** |
jlaskey@3 | 748 | * Get length of spill area associated with this {@link PropertyMap}. |
jlaskey@3 | 749 | * |
jlaskey@3 | 750 | * @return Length of spill area. |
jlaskey@3 | 751 | */ |
jlaskey@3 | 752 | int getSpillLength() { |
jlaskey@3 | 753 | return spillLength; |
jlaskey@3 | 754 | } |
jlaskey@3 | 755 | |
jlaskey@3 | 756 | /** |
jlaskey@242 | 757 | * Change the prototype of objects associated with this {@link PropertyMap}. |
jlaskey@3 | 758 | * |
jlaskey@242 | 759 | * @param oldProto Current prototype object. |
jlaskey@242 | 760 | * @param newProto New prototype object to replace oldProto. |
jlaskey@3 | 761 | * |
jlaskey@3 | 762 | * @return New {@link PropertyMap} with prototype changed. |
jlaskey@3 | 763 | */ |
jlaskey@242 | 764 | PropertyMap changeProto(final ScriptObject oldProto, final ScriptObject newProto) { |
hannesw@251 | 765 | if (oldProto == newProto) { |
jlaskey@3 | 766 | return this; |
jlaskey@3 | 767 | } |
jlaskey@3 | 768 | |
jlaskey@3 | 769 | final PropertyMap nextMap = checkProtoHistory(newProto); |
jlaskey@3 | 770 | if (nextMap != null) { |
jlaskey@3 | 771 | return nextMap; |
jlaskey@3 | 772 | } |
jlaskey@3 | 773 | |
jlaskey@3 | 774 | if (Context.DEBUG) { |
jlaskey@3 | 775 | incrementSetProtoNewMapCount(); |
jlaskey@3 | 776 | } |
jlaskey@242 | 777 | |
jlaskey@242 | 778 | final PropertyMap newMap = new PropertyMap(this); |
jlaskey@3 | 779 | addToProtoHistory(newProto, newMap); |
jlaskey@3 | 780 | |
jlaskey@3 | 781 | return newMap; |
jlaskey@3 | 782 | } |
jlaskey@3 | 783 | |
jlaskey@3 | 784 | /** |
jlaskey@3 | 785 | * Indicate that the map has listeners. |
jlaskey@3 | 786 | */ |
jlaskey@3 | 787 | private void setIsListenerAdded() { |
jlaskey@3 | 788 | flags |= IS_LISTENER_ADDED; |
jlaskey@3 | 789 | } |
jlaskey@3 | 790 | |
jlaskey@3 | 791 | /** |
jlaskey@3 | 792 | * Return only the flags that should be copied during cloning. |
jlaskey@3 | 793 | * |
jlaskey@3 | 794 | * @return Subset of flags that should be copied. |
jlaskey@3 | 795 | */ |
jlaskey@3 | 796 | private int getClonedFlags() { |
jlaskey@3 | 797 | return flags & CLONEABLE_FLAGS_MASK; |
jlaskey@3 | 798 | } |
jlaskey@3 | 799 | |
jlaskey@3 | 800 | /** |
jlaskey@3 | 801 | * {@link PropertyMap} iterator. |
jlaskey@3 | 802 | */ |
jlaskey@3 | 803 | private static class PropertyMapIterator implements Iterator<Object> { |
jlaskey@3 | 804 | /** Property iterator. */ |
jlaskey@3 | 805 | final Iterator<Property> iter; |
jlaskey@3 | 806 | |
jlaskey@3 | 807 | /** Current Property. */ |
jlaskey@3 | 808 | Property property; |
jlaskey@3 | 809 | |
jlaskey@3 | 810 | /** |
jlaskey@3 | 811 | * Constructor. |
jlaskey@3 | 812 | * |
jlaskey@3 | 813 | * @param propertyMap {@link PropertyMap} to iterate over. |
jlaskey@3 | 814 | */ |
jlaskey@3 | 815 | PropertyMapIterator(final PropertyMap propertyMap) { |
jlaskey@3 | 816 | iter = Arrays.asList(propertyMap.properties.getProperties()).iterator(); |
jlaskey@3 | 817 | property = iter.hasNext() ? iter.next() : null; |
jlaskey@3 | 818 | skipNotEnumerable(); |
jlaskey@3 | 819 | } |
jlaskey@3 | 820 | |
jlaskey@3 | 821 | /** |
jlaskey@3 | 822 | * Ignore properties that are not enumerable. |
jlaskey@3 | 823 | */ |
jlaskey@3 | 824 | private void skipNotEnumerable() { |
jlaskey@3 | 825 | while (property != null && !property.isEnumerable()) { |
jlaskey@3 | 826 | property = iter.hasNext() ? iter.next() : null; |
jlaskey@3 | 827 | } |
jlaskey@3 | 828 | } |
jlaskey@3 | 829 | |
jlaskey@3 | 830 | @Override |
jlaskey@3 | 831 | public boolean hasNext() { |
jlaskey@3 | 832 | return property != null; |
jlaskey@3 | 833 | } |
jlaskey@3 | 834 | |
jlaskey@3 | 835 | @Override |
jlaskey@3 | 836 | public Object next() { |
jlaskey@3 | 837 | if (property == null) { |
jlaskey@3 | 838 | throw new NoSuchElementException(); |
jlaskey@3 | 839 | } |
jlaskey@3 | 840 | |
jlaskey@3 | 841 | final Object key = property.getKey(); |
jlaskey@3 | 842 | property = iter.next(); |
jlaskey@3 | 843 | skipNotEnumerable(); |
jlaskey@3 | 844 | |
jlaskey@3 | 845 | return key; |
jlaskey@3 | 846 | } |
jlaskey@3 | 847 | |
jlaskey@3 | 848 | @Override |
jlaskey@3 | 849 | public void remove() { |
jlaskey@3 | 850 | throw new UnsupportedOperationException(); |
jlaskey@3 | 851 | } |
jlaskey@3 | 852 | } |
jlaskey@3 | 853 | |
jlaskey@3 | 854 | /* |
jlaskey@3 | 855 | * PropertyListener implementation. |
jlaskey@3 | 856 | */ |
jlaskey@3 | 857 | |
jlaskey@3 | 858 | @Override |
jlaskey@3 | 859 | public void propertyAdded(final ScriptObject object, final Property prop) { |
jlaskey@3 | 860 | invalidateProtoGetSwitchPoint(prop); |
jlaskey@3 | 861 | } |
jlaskey@3 | 862 | |
jlaskey@3 | 863 | @Override |
jlaskey@3 | 864 | public void propertyDeleted(final ScriptObject object, final Property prop) { |
jlaskey@3 | 865 | invalidateProtoGetSwitchPoint(prop); |
jlaskey@3 | 866 | } |
jlaskey@3 | 867 | |
jlaskey@3 | 868 | @Override |
jlaskey@3 | 869 | public void propertyModified(final ScriptObject object, final Property oldProp, final Property newProp) { |
jlaskey@3 | 870 | invalidateProtoGetSwitchPoint(oldProp); |
jlaskey@3 | 871 | } |
jlaskey@3 | 872 | |
jlaskey@3 | 873 | /* |
jlaskey@3 | 874 | * Debugging and statistics. |
jlaskey@3 | 875 | */ |
jlaskey@3 | 876 | |
jlaskey@3 | 877 | // counters updated only in debug mode |
jlaskey@3 | 878 | private static int count; |
jlaskey@3 | 879 | private static int clonedCount; |
jlaskey@3 | 880 | private static int historyHit; |
jlaskey@3 | 881 | private static int protoInvalidations; |
jlaskey@3 | 882 | private static int protoHistoryHit; |
jlaskey@3 | 883 | private static int setProtoNewMapCount; |
jlaskey@3 | 884 | |
jlaskey@3 | 885 | /** |
jlaskey@3 | 886 | * @return Total number of maps. |
jlaskey@3 | 887 | */ |
jlaskey@3 | 888 | public static int getCount() { |
jlaskey@3 | 889 | return count; |
jlaskey@3 | 890 | } |
jlaskey@3 | 891 | |
jlaskey@3 | 892 | /** |
jlaskey@3 | 893 | * @return The number of maps that were cloned. |
jlaskey@3 | 894 | */ |
jlaskey@3 | 895 | public static int getClonedCount() { |
jlaskey@3 | 896 | return clonedCount; |
jlaskey@3 | 897 | } |
jlaskey@3 | 898 | |
jlaskey@3 | 899 | /** |
jlaskey@3 | 900 | * @return The number of times history was successfully used. |
jlaskey@3 | 901 | */ |
jlaskey@3 | 902 | public static int getHistoryHit() { |
jlaskey@3 | 903 | return historyHit; |
jlaskey@3 | 904 | } |
jlaskey@3 | 905 | |
jlaskey@3 | 906 | /** |
jlaskey@3 | 907 | * @return The number of times prototype changes caused invalidation. |
jlaskey@3 | 908 | */ |
jlaskey@3 | 909 | public static int getProtoInvalidations() { |
jlaskey@3 | 910 | return protoInvalidations; |
jlaskey@3 | 911 | } |
jlaskey@3 | 912 | |
jlaskey@3 | 913 | /** |
jlaskey@3 | 914 | * @return The number of times proto history was successfully used. |
jlaskey@3 | 915 | */ |
jlaskey@3 | 916 | public static int getProtoHistoryHit() { |
jlaskey@3 | 917 | return protoHistoryHit; |
jlaskey@3 | 918 | } |
jlaskey@3 | 919 | |
jlaskey@3 | 920 | /** |
jlaskey@3 | 921 | * @return The number of times prototypes were modified. |
jlaskey@3 | 922 | */ |
jlaskey@3 | 923 | public static int getSetProtoNewMapCount() { |
jlaskey@3 | 924 | return setProtoNewMapCount; |
jlaskey@3 | 925 | } |
jlaskey@3 | 926 | |
jlaskey@3 | 927 | /** |
jlaskey@3 | 928 | * Increment the prototype set count. |
jlaskey@3 | 929 | */ |
jlaskey@3 | 930 | private static void incrementSetProtoNewMapCount() { |
jlaskey@3 | 931 | setProtoNewMapCount++; |
jlaskey@3 | 932 | } |
jlaskey@3 | 933 | } |