src/jdk/nashorn/internal/runtime/PropertyMap.java

Wed, 26 Jun 2013 15:40:52 +0200

author
hannesw
date
Wed, 26 Jun 2013 15:40:52 +0200
changeset 380
80c66d3fd872
parent 379
682889823712
child 415
edca88d3a03e
permissions
-rw-r--r--

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 }

mercurial