src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyHashMap.java
changeset 47351 fff3970bd14f
parent 47216 71c04702a3d5
equal deleted inserted replaced
47350:d65c3b21081c 47351:fff3970bd14f
    29 import java.util.Collection;
    29 import java.util.Collection;
    30 import java.util.Collections;
    30 import java.util.Collections;
    31 import java.util.HashSet;
    31 import java.util.HashSet;
    32 import java.util.Map;
    32 import java.util.Map;
    33 import java.util.Set;
    33 import java.util.Set;
       
    34 import jdk.nashorn.internal.runtime.options.Options;
    34 
    35 
    35 /**
    36 /**
    36  * Immutable hash map implementation for properties.  Properties are keyed on strings.
    37  * Immutable hash map implementation for properties. Properties are keyed on strings
    37  * Copying and cloning is avoided by relying on immutability.
    38  * or symbols (ES6). Copying and cloning is avoided by relying on immutability.
    38  * <p>
    39  * <p>
    39  * When adding an element to a hash table, only the head of a bin list is updated, thus
    40  * When adding an element to a hash table, only the head of a bin list is updated, thus
    40  * an add only requires the cloning of the bins array and adding an element to the head
    41  * an add only requires the cloning of the bins array and adding an element to the head
    41  * of the bin list.  Similarly for removal, only a portion of a bin list is updated.
    42  * of the bin list.  Similarly for removal, only a portion of a bin list is updated.
    42  * <p>
    43  * <p>
       
    44  * For large tables with hundreds or thousands of elements, even just cloning the bins
       
    45  * array when adding properties is an expensive operation. For this case, we put new
       
    46  * elements in a separate list called {@link ElementQueue}.
       
    47  * The list component is merged into the hash table at regular intervals during element
       
    48  * insertion to keep it from growing too long. Also, when a map with a queue component
       
    49  * is queried repeatedly, the map will replace itself with a pure hash table version
       
    50  * of itself to optimize lookup performance.
       
    51  * <p>
    43  * A separate chronological list is kept for quick generation of keys and values, and,
    52  * A separate chronological list is kept for quick generation of keys and values, and,
    44  * for rehashing.
    53  * for rehashing. For very small maps where the overhead of the hash table would
       
    54  * outweigh its benefits we deliberately avoid creating a hash structure and use the
       
    55  * chronological list alone for element storage.
    45  * <p>
    56  * <p>
    46  * Details:
    57  * Details:
    47  * <p>
    58  * <p>
    48  * The main goal is to be able to retrieve properties from a map quickly, keying on
    59  * The main goal is to be able to retrieve properties from a map quickly, keying on
    49  * the property name (String.)  A secondary, but important goal, is to keep maps
    60  * the property name (String or Symbol). A secondary, but important goal, is to keep
    50  * immutable, so that a map can be shared by multiple objects in a context.
    61  * maps immutable, so that a map can be shared by multiple objects in a context.
    51  * Sharing maps allows objects to be categorized as having similar properties, a
    62  * Sharing maps allows objects to be categorized as having similar properties, a
    52  * fact that call site guards rely on.  In this discussion, immutability allows us
    63  * fact that call site guards rely on.  In this discussion, immutability allows us
    53  * to significantly reduce the amount of duplication we have in our maps.
    64  * to significantly reduce the amount of duplication we have in our maps.
    54  * <p>
    65  * <p>
    55  * The simplest of immutable maps is a basic singly linked list.  New properties
    66  * The simplest of immutable maps is a basic singly linked list.  New properties
   107     private static final int INITIAL_BINS = 32;
   118     private static final int INITIAL_BINS = 32;
   108 
   119 
   109     /** Threshold before using bins. */
   120     /** Threshold before using bins. */
   110     private static final int LIST_THRESHOLD = 8;
   121     private static final int LIST_THRESHOLD = 8;
   111 
   122 
       
   123     /** Threshold before adding new elements to queue instead of directly adding to hash bins. */
       
   124     private static final int QUEUE_THRESHOLD = Options.getIntProperty("nashorn.propmap.queue.threshold", 500);
       
   125 
   112     /** Initial map. */
   126     /** Initial map. */
   113     public static final PropertyHashMap EMPTY_HASHMAP = new PropertyHashMap();
   127     public static final PropertyHashMap EMPTY_HASHMAP = new PropertyHashMap();
   114 
   128 
   115     /** Number of properties in the map. */
   129     /** Number of properties in the map. */
   116     private final int size;
   130     private final int size;
   120 
   134 
   121     /** Reverse list of all properties. */
   135     /** Reverse list of all properties. */
   122     private final Element list;
   136     private final Element list;
   123 
   137 
   124     /** Hash map bins. */
   138     /** Hash map bins. */
   125     private final Element[] bins;
   139     private Element[] bins;
       
   140 
       
   141     /** Queue for adding elements to large maps with delayed hashing. */
       
   142     private ElementQueue queue;
   126 
   143 
   127     /** All properties as an array (lazy). */
   144     /** All properties as an array (lazy). */
   128     private Property[] properties;
   145     private Property[] properties;
   129 
   146 
   130     /**
   147     /**
   132      */
   149      */
   133     private PropertyHashMap() {
   150     private PropertyHashMap() {
   134         this.size      = 0;
   151         this.size      = 0;
   135         this.threshold = 0;
   152         this.threshold = 0;
   136         this.bins      = null;
   153         this.bins      = null;
       
   154         this.queue     = null;
   137         this.list      = null;
   155         this.list      = null;
   138     }
   156     }
   139 
   157 
   140     /**
   158     /**
   141      * Clone Constructor
   159      * Constructor used internally to create new maps
   142      *
   160      *
   143      * @param map Original {@link PropertyHashMap}.
   161      * @param map the new map
   144      */
   162      */
   145     private PropertyHashMap(final PropertyHashMap map) {
   163     private PropertyHashMap(final MapBuilder map) {
   146         this.size      = map.size;
   164         this.size      = map.size;
   147         this.threshold = map.threshold;
   165         if (map.qhead == null) {
   148         this.bins      = map.bins;
   166             this.bins = map.bins;
       
   167             this.queue = null;
       
   168         } else {
       
   169             this.bins = null;
       
   170             this.queue = new ElementQueue(map.qhead, map.bins);
       
   171         }
   149         this.list      = map.list;
   172         this.list      = map.list;
   150     }
   173         this.threshold = map.bins != null ? threeQuarters(map.bins.length) : 0;
   151 
       
   152     /**
       
   153      * Constructor used internally to extend a map
       
   154      *
       
   155      * @param size Size of the new {@link PropertyHashMap}.
       
   156      * @param bins The hash bins.
       
   157      * @param list The {@link Property} list.
       
   158      */
       
   159     private PropertyHashMap(final int size, final Element[] bins, final Element list) {
       
   160         this.size      = size;
       
   161         this.threshold = bins != null ? threeQuarters(bins.length) : 0;
       
   162         this.bins      = bins;
       
   163         this.list      = list;
       
   164     }
   174     }
   165 
   175 
   166     /**
   176     /**
   167      * Clone a property map, replacing a property with a new one in the same place,
   177      * Clone a property map, replacing a property with a new one in the same place,
   168      * which is important for property iterations if a property changes types
   178      * which is important for property iterations if a property changes types
   171      * @return new property map
   181      * @return new property map
   172      */
   182      */
   173     public PropertyHashMap immutableReplace(final Property property, final Property newProperty) {
   183     public PropertyHashMap immutableReplace(final Property property, final Property newProperty) {
   174         assert property.getKey().equals(newProperty.getKey()) : "replacing properties with different keys: '" + property.getKey() + "' != '" + newProperty.getKey() + "'";
   184         assert property.getKey().equals(newProperty.getKey()) : "replacing properties with different keys: '" + property.getKey() + "' != '" + newProperty.getKey() + "'";
   175         assert findElement(property.getKey()) != null         : "replacing property that doesn't exist in map: '" + property.getKey() + "'";
   185         assert findElement(property.getKey()) != null         : "replacing property that doesn't exist in map: '" + property.getKey() + "'";
   176         return cloneMap().replaceNoClone(property.getKey(), newProperty);
   186         final MapBuilder builder = newMapBuilder(size);
       
   187         builder.replaceProperty(property.getKey(), newProperty);
       
   188         return new PropertyHashMap(builder);
   177     }
   189     }
   178 
   190 
   179     /**
   191     /**
   180      * Clone a {@link PropertyHashMap} and add a {@link Property}.
   192      * Clone a {@link PropertyHashMap} and add a {@link Property}.
   181      *
   193      *
   183      *
   195      *
   184      * @return New {@link PropertyHashMap}.
   196      * @return New {@link PropertyHashMap}.
   185      */
   197      */
   186     public PropertyHashMap immutableAdd(final Property property) {
   198     public PropertyHashMap immutableAdd(final Property property) {
   187         final int newSize = size + 1;
   199         final int newSize = size + 1;
   188         PropertyHashMap newMap = cloneMap(newSize);
   200         MapBuilder builder = newMapBuilder(newSize);
   189         newMap = newMap.addNoClone(property);
   201         builder.addProperty(property);
   190         return newMap;
   202         return new PropertyHashMap(builder);
   191     }
   203     }
   192 
   204 
   193     /**
   205     /**
   194      * Clone a {@link PropertyHashMap} and add an array of properties.
   206      * Clone a {@link PropertyHashMap} and add an array of properties.
   195      *
   207      *
   197      *
   209      *
   198      * @return New {@link PropertyHashMap}.
   210      * @return New {@link PropertyHashMap}.
   199      */
   211      */
   200     public PropertyHashMap immutableAdd(final Property... newProperties) {
   212     public PropertyHashMap immutableAdd(final Property... newProperties) {
   201         final int newSize = size + newProperties.length;
   213         final int newSize = size + newProperties.length;
   202         PropertyHashMap newMap = cloneMap(newSize);
   214         MapBuilder builder = newMapBuilder(newSize);
   203         for (final Property property : newProperties) {
   215         for (final Property property : newProperties) {
   204             newMap = newMap.addNoClone(property);
   216             builder.addProperty(property);
   205         }
   217         }
   206         return newMap;
   218         return new PropertyHashMap(builder);
   207     }
   219     }
   208 
   220 
   209     /**
   221     /**
   210      * Clone a {@link PropertyHashMap} and add a collection of properties.
   222      * Clone a {@link PropertyHashMap} and add a collection of properties.
   211      *
   223      *
   214      * @return New {@link PropertyHashMap}.
   226      * @return New {@link PropertyHashMap}.
   215      */
   227      */
   216     public PropertyHashMap immutableAdd(final Collection<Property> newProperties) {
   228     public PropertyHashMap immutableAdd(final Collection<Property> newProperties) {
   217         if (newProperties != null) {
   229         if (newProperties != null) {
   218             final int newSize = size + newProperties.size();
   230             final int newSize = size + newProperties.size();
   219             PropertyHashMap newMap = cloneMap(newSize);
   231             MapBuilder builder = newMapBuilder(newSize);
   220             for (final Property property : newProperties) {
   232             for (final Property property : newProperties) {
   221                 newMap = newMap.addNoClone(property);
   233                 builder.addProperty(property);
   222             }
   234             }
   223             return newMap;
   235             return new PropertyHashMap(builder);
   224         }
   236         }
   225         return this;
   237         return this;
   226     }
   238     }
   227 
   239 
   228     /**
   240     /**
   229      * Clone a {@link PropertyHashMap} and remove a {@link Property}.
   241      * Clone a {@link PropertyHashMap} and remove a {@link Property} based on its key.
   230      *
   242      *
   231      * @param property {@link Property} to remove.
   243      * @param key Key of {@link Property} to remove.
   232      *
   244      *
   233      * @return New {@link PropertyHashMap}.
   245      * @return New {@link PropertyHashMap}.
   234      */
   246      */
   235     public PropertyHashMap immutableRemove(final Property property) {
       
   236         return immutableRemove(property.getKey());
       
   237     }
       
   238 
       
   239     /**
       
   240      * Clone a {@link PropertyHashMap} and remove a {@link Property} based on its key.
       
   241      *
       
   242      * @param key Key of {@link Property} to remove.
       
   243      *
       
   244      * @return New {@link PropertyHashMap}.
       
   245      */
       
   246     public PropertyHashMap immutableRemove(final Object key) {
   247     public PropertyHashMap immutableRemove(final Object key) {
   247         if (bins != null) {
   248         MapBuilder builder = newMapBuilder(size);
   248             final int binIndex = binIndex(bins, key);
   249         builder.removeProperty(key);
   249             final Element bin = bins[binIndex];
   250         if (builder.size < size) {
   250             if (findElement(bin, key) != null) {
   251             return builder.size != 0 ? new PropertyHashMap(builder) : EMPTY_HASHMAP;
   251                 final int newSize = size - 1;
       
   252                 Element[] newBins = null;
       
   253                 if (newSize >= LIST_THRESHOLD) {
       
   254                     newBins = bins.clone();
       
   255                     newBins[binIndex] = removeFromList(bin, key);
       
   256                 }
       
   257                 final Element newList = removeFromList(list, key);
       
   258                 return new PropertyHashMap(newSize, newBins, newList);
       
   259             }
       
   260         } else if (findElement(list, key) != null) {
       
   261             final int newSize = size - 1;
       
   262             return newSize != 0 ? new PropertyHashMap(newSize, null, removeFromList(list, key)) : EMPTY_HASHMAP;
       
   263         }
   252         }
   264         return this;
   253         return this;
   265     }
   254     }
   266 
   255 
   267     /**
   256     /**
   354      * @param key {@link Element} key.
   343      * @param key {@link Element} key.
   355      *
   344      *
   356      * @return {@link Element} matching key or {@code null} if not found.
   345      * @return {@link Element} matching key or {@code null} if not found.
   357      */
   346      */
   358     private Element findElement(final Object key) {
   347     private Element findElement(final Object key) {
   359         if (bins != null) {
   348         if (queue != null) {
       
   349             return queue.find(key);
       
   350         } else if (bins != null) {
   360             final int binIndex = binIndex(bins, key);
   351             final int binIndex = binIndex(bins, key);
   361             return findElement(bins[binIndex], key);
   352             return findElement(bins[binIndex], key);
   362         }
   353         }
   363         return findElement(list, key);
   354         return findElement(list, key);
   364     }
   355     }
   378             }
   369             }
   379         }
   370         }
   380         return null;
   371         return null;
   381     }
   372     }
   382 
   373 
   383 
   374     /**
   384     private PropertyHashMap cloneMap() {
   375      * Create a {@code MapBuilder} to add new elements to.
   385         return new PropertyHashMap(size, bins == null ? null : bins.clone(), list);
       
   386     }
       
   387 
       
   388     /**
       
   389      * Clone {@link PropertyHashMap} to accommodate new size.
       
   390      *
   376      *
   391      * @param newSize New size of {@link PropertyHashMap}.
   377      * @param newSize New size of {@link PropertyHashMap}.
   392      *
   378      *
   393      * @return Cloned {@link PropertyHashMap} with new size.
   379      * @return {@link MapBuilder} for the new size.
   394      */
   380      */
   395     private PropertyHashMap cloneMap(final int newSize) {
   381     private MapBuilder newMapBuilder(final int newSize) {
   396         Element[] newBins;
   382         if (bins == null && newSize < LIST_THRESHOLD) {
   397         if (bins == null && newSize <= LIST_THRESHOLD) {
   383             return new MapBuilder(bins, list, size, false);
   398             newBins = null;
       
   399         } else if (newSize > threshold) {
   384         } else if (newSize > threshold) {
   400             newBins = rehash(list, binsNeeded(newSize));
   385             return new MapBuilder(rehash(list, binsNeeded(newSize)), list, size, true);
       
   386         } else if (shouldCloneBins(size, newSize)) {
       
   387             return new MapBuilder(cloneBins(), list, size, true);
       
   388         } else if (queue == null) {
       
   389             return new MapBuilder(bins, list, size, false);
   401         } else {
   390         } else {
   402             newBins = bins.clone();
   391             return new MapBuilder(queue, list, size, false);
   403         }
   392         }
   404         return new PropertyHashMap(newSize, newBins, list);
   393     }
   405     }
   394 
   406 
   395     /**
   407 
   396      * Create a cloned or new bins array and merge the elements in the queue into it if there are any.
   408 
   397      *
   409     /**
   398      * @return the cloned bins array
   410      * Add a {@link Property} to a temporary {@link PropertyHashMap}, that has
   399      */
   411      * been already cloned.  Removes duplicates if necessary.
   400     private Element[] cloneBins() {
   412      *
   401         if (queue != null) {
   413      * @param property {@link Property} to add.
   402             return queue.cloneAndMergeBins();
   414      *
   403         }
   415      * @return New {@link PropertyHashMap}.
   404 
   416      */
   405         return bins.clone();
   417     private PropertyHashMap addNoClone(final Property property) {
   406     }
   418         int newSize = size;
   407 
   419         final Object key = property.getKey();
   408     /**
   420         Element newList = list;
   409      * Used on insertion to determine whether the bins array should be cloned, or we should keep
   421         if (bins != null) {
   410      * using the ancestor's bins array and put new elements into the queue.
   422             final int binIndex = binIndex(bins, key);
   411      *
   423             Element bin = bins[binIndex];
   412      * @param oldSize the old map size
   424             if (findElement(bin, key) != null) {
   413      * @param newSize the new map size
   425                 newSize--;
   414      * @return whether to clone the bins array
   426                 bin = removeFromList(bin, key);
   415      */
   427                 newList = removeFromList(list, key);
   416     private boolean shouldCloneBins(final int oldSize, final int newSize) {
   428             }
   417         // For maps with less than QUEUE_THRESHOLD elements we clone the bins array on every insertion.
   429             bins[binIndex] = new Element(bin, property);
   418         // Above that threshold we put new elements into the queue and only merge every 512  elements.
   430         } else {
   419         return newSize < QUEUE_THRESHOLD || (newSize >>> 9) > (oldSize >>> 9);
   431             if (findElement(list, key) != null) {
       
   432                 newSize--;
       
   433                 newList = removeFromList(list, key);
       
   434             }
       
   435         }
       
   436         newList = new Element(newList, property);
       
   437         return new PropertyHashMap(newSize, bins, newList);
       
   438     }
       
   439 
       
   440     private PropertyHashMap replaceNoClone(final Object key, final Property property) {
       
   441         if (bins != null) {
       
   442             final int binIndex = binIndex(bins, key);
       
   443             Element bin = bins[binIndex];
       
   444             bin = replaceInList(bin, key, property);
       
   445             bins[binIndex] = bin;
       
   446         }
       
   447         Element newList = list;
       
   448         newList = replaceInList(newList, key, property);
       
   449         return new PropertyHashMap(size, bins, newList);
       
   450     }
   420     }
   451 
   421 
   452     /**
   422     /**
   453      * Removes an {@link Element} from a specific list, avoiding duplication.
   423      * Removes an {@link Element} from a specific list, avoiding duplication.
   454      *
   424      *
   679         Property getProperty() {
   649         Property getProperty() {
   680             return property;
   650             return property;
   681         }
   651         }
   682     }
   652     }
   683 
   653 
       
   654     /**
       
   655      * A hybrid map/list structure to add elements to the map without the need to clone and rehash the main table.
       
   656      * This is used for large maps to reduce the overhead of adding elements. Instances of this class can replace
       
   657      * themselves with a pure hash map version of themselves to optimize query performance.
       
   658      */
       
   659     private class ElementQueue {
       
   660 
       
   661         /** List of elements not merged into bins */
       
   662         private final Element qhead;
       
   663 
       
   664         /** Our own bins array. Differs from original PropertyHashMap bins when queue is merged. */
       
   665         private final Element[] qbins;
       
   666 
       
   667         /** Count searches to trigger merging of queue into bins. */
       
   668         int searchCount = 0;
       
   669 
       
   670         ElementQueue(final Element qhead, final Element[] qbins) {
       
   671             this.qhead = qhead;
       
   672             this.qbins = qbins;
       
   673         }
       
   674 
       
   675         Element find(final Object key) {
       
   676             final int binIndex = binIndex(qbins, key);
       
   677             final Element element = findElement(qbins[binIndex], key);
       
   678             if (element != null) {
       
   679                 return element;
       
   680             }
       
   681             if (qhead != null) {
       
   682                 if (++searchCount > 2) {
       
   683                     // Merge the queue into the hash bins if this map is queried more than a few times
       
   684                     final Element[] newBins = cloneAndMergeBins();
       
   685                     assert newBins != qbins;
       
   686                     PropertyHashMap.this.queue = new ElementQueue(null, newBins);
       
   687                     return PropertyHashMap.this.queue.find(key);
       
   688                 }
       
   689                 return findElement(qhead, key);
       
   690             }
       
   691             return null;
       
   692         }
       
   693 
       
   694         /**
       
   695          * Create a cloned or new bins array and merge the elements in the queue into it if there are any.
       
   696          *
       
   697          * @return the cloned bins array
       
   698          */
       
   699         private Element[] cloneAndMergeBins() {
       
   700             if (qhead == null) {
       
   701                 return qbins;
       
   702             }
       
   703 
       
   704             final Element[] newBins = qbins.clone();
       
   705 
       
   706             for (Element element = qhead; element != null; element = element.getLink()) {
       
   707                 final Property property = element.getProperty();
       
   708                 final Object key = property.getKey();
       
   709                 final int binIndex = binIndex(newBins, key);
       
   710 
       
   711                 newBins[binIndex] = new Element(newBins[binIndex], property);
       
   712             }
       
   713 
       
   714             return newBins;
       
   715         }
       
   716 
       
   717     }
       
   718 
       
   719     /**
       
   720      * A builder class used for adding, replacing, or removing elements.
       
   721      */
       
   722     private static class MapBuilder {
       
   723 
       
   724         /** Bins array - may be shared with map that created us. */
       
   725         private Element[] bins;
       
   726         /** Whether our bins are shared */
       
   727         private boolean hasOwnBins;
       
   728 
       
   729         /** Queue of unmerged elements */
       
   730         private Element qhead;
       
   731 
       
   732         /** Full property list. */
       
   733         private Element list;
       
   734 
       
   735         /** Number of properties. */
       
   736         private int size;
       
   737 
       
   738         MapBuilder(final Element[] bins, final Element list, final int size, final boolean hasOwnBins) {
       
   739             this.bins  = bins;
       
   740             this.hasOwnBins = hasOwnBins;
       
   741             this.list  = list;
       
   742             this.qhead = null;
       
   743             this.size  = size;
       
   744         }
       
   745 
       
   746         MapBuilder(final ElementQueue queue, final Element list, final int size, final boolean hasOwnBins) {
       
   747             this.bins  = queue.qbins;
       
   748             this.hasOwnBins = hasOwnBins;
       
   749             this.list  = list;
       
   750             this.qhead = queue.qhead;
       
   751             this.size  = size;
       
   752         }
       
   753 
       
   754         /**
       
   755          * Add a {@link Property}. Removes duplicates if necessary.
       
   756          *
       
   757          * @param property {@link Property} to add.
       
   758          */
       
   759         private void addProperty(final Property property) {
       
   760             final Object key = property.getKey();
       
   761 
       
   762             if (bins != null) {
       
   763                 final int binIndex = binIndex(bins, key);
       
   764                 if (findElement(bins[binIndex], key) != null) {
       
   765                     ensureOwnBins();
       
   766                     bins[binIndex] = removeExistingElement(bins[binIndex], key);
       
   767                 } else if (findElement(qhead, key) != null) {
       
   768                     qhead = removeExistingElement(qhead, key);
       
   769                 }
       
   770                 if (hasOwnBins) {
       
   771                     bins[binIndex] = new Element(bins[binIndex], property);
       
   772                 } else {
       
   773                     qhead = new Element(qhead, property);
       
   774                 }
       
   775             } else {
       
   776                 if (findElement(list, key) != null) {
       
   777                     list = removeFromList(list, key);
       
   778                     size--;
       
   779                 }
       
   780             }
       
   781 
       
   782             list = new Element(list, property);
       
   783             size++;
       
   784         }
       
   785 
       
   786         /**
       
   787          * Replace an existing {@link Property} with a new one with the same key.
       
   788          *
       
   789          * @param key the property key
       
   790          * @param property the property to replace the old one with
       
   791          */
       
   792         private void replaceProperty(final Object key, final Property property) {
       
   793 
       
   794             if (bins != null) {
       
   795                 final int binIndex = binIndex(bins, key);
       
   796                 Element bin = bins[binIndex];
       
   797                 if (findElement(bin, key) != null) {
       
   798                     ensureOwnBins();
       
   799                     bins[binIndex] = replaceInList(bin, key, property);
       
   800                 } else if (qhead != null) {
       
   801                     qhead = replaceInList(qhead, key, property);
       
   802                 }
       
   803             }
       
   804 
       
   805             list = replaceInList(list, key, property);
       
   806         }
       
   807 
       
   808         /**
       
   809          * Remove a {@link Property} based on its key.
       
   810          *
       
   811          * @param key Key of {@link Property} to remove.
       
   812          */
       
   813         void removeProperty(final Object key) {
       
   814 
       
   815             if (bins != null) {
       
   816                 final int binIndex = binIndex(bins, key);
       
   817                 final Element bin = bins[binIndex];
       
   818                 if (findElement(bin, key) != null) { ;
       
   819                     if (size >= LIST_THRESHOLD) {
       
   820                         ensureOwnBins();
       
   821                         bins[binIndex] = removeFromList(bin, key);
       
   822                     } else {
       
   823                         // Go back to list-only representation for small maps
       
   824                         bins = null;
       
   825                         qhead = null;
       
   826                     }
       
   827                 } else if (findElement(qhead, key) != null) {
       
   828                     qhead = removeFromList(qhead, key);
       
   829                 }
       
   830             }
       
   831 
       
   832             list = removeFromList(list, key);
       
   833             size--;
       
   834         }
       
   835 
       
   836         /**
       
   837          * Removes an element known to exist from an element list and the main {@code list} and decreases {@code size}.
       
   838          *
       
   839          * @param element the element list
       
   840          * @param key the key to remove
       
   841          * @return the new element list
       
   842          */
       
   843         private Element removeExistingElement(Element element, Object key) {
       
   844             size--;
       
   845             list = removeFromList(list, key);
       
   846             return removeFromList(element, key);
       
   847         }
       
   848 
       
   849         /**
       
   850          * Make sure we own the bins we have, cloning them if necessary.
       
   851          */
       
   852         private void ensureOwnBins() {
       
   853             if (!hasOwnBins) {
       
   854                 bins = bins.clone();
       
   855             }
       
   856             hasOwnBins = true;
       
   857         }
       
   858     }
       
   859 
   684 }
   860 }