summaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/search/query/profile/CopyOnWriteContent.java
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-search/src/main/java/com/yahoo/search/query/profile/CopyOnWriteContent.java
Publish
Diffstat (limited to 'container-search/src/main/java/com/yahoo/search/query/profile/CopyOnWriteContent.java')
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/CopyOnWriteContent.java159
1 files changed, 159 insertions, 0 deletions
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/CopyOnWriteContent.java b/container-search/src/main/java/com/yahoo/search/query/profile/CopyOnWriteContent.java
new file mode 100644
index 00000000000..3c02677b676
--- /dev/null
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/CopyOnWriteContent.java
@@ -0,0 +1,159 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.query.profile;
+
+import com.yahoo.component.provider.FreezableClass;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A HashMap wrapper which can be cloned without copying the wrapped map.
+ * Copying of the map is deferred until there is a write access to the wrapped map.
+ * This may be frozen, at which point no further modifications are allowed.
+ * Note that <b>until</b> this is cloned, the internal map may be both read and written.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class CopyOnWriteContent extends FreezableClass implements Cloneable {
+
+ // TODO: Now that we used CompiledQueryProfiles at runtime we can remove this
+
+ // Possible states:
+ // WRITABLE: The map can be freely modified - it is only used by this
+ // -> !isFrozen() && (map!=null || unmodifiableMap==null)
+ // COPYONWRITE: The map is referred by at least one clone - further modification must cause a copy
+ // -> !isFrozen() && (map==null && unmodifiableMap!=null)
+ // FROZEN: No further changes are allowed to the state of this, ever
+ // -> isFrozen()
+
+ // Possible start states:
+ // WRITABLE: When created using the public constructor
+ // COPYONWRITE: When created by cloning
+
+ // Possible state transitions:
+ // WRITABLE->COPYONWRITE: When this is cloned
+ // COPYONWRITE->WRITABLE: When a clone is written to
+ // (COPYONWRITE,WRITABLE)->FROZEN: When a profile is frozen
+
+ /** The modifiable content of this. Null if this is empty or if this is not in the WRITABLE state */
+ private Map<String,Object> map=null;
+ /**
+ * If map is non-null this is either null (not instantiated yet) or an unmodifiable wrapper of map,
+ * if map is null this is either null (this is empty) or a reference to the map of the content this was cloned from
+ */
+ private Map<String,Object> unmodifiableMap =null;
+
+ /** Create a WRITABLE, empty instance */
+ public CopyOnWriteContent() {
+ }
+
+ /** Create a COPYONWRITE instance with some initial state */
+ private static CopyOnWriteContent createInCopyOnWriteState(Map<String,Object> unmodifiableMap) {
+ CopyOnWriteContent content=new CopyOnWriteContent();
+ content.unmodifiableMap = unmodifiableMap;
+ return content;
+ }
+
+ /** Create a WRITABLE instance with some initial state */
+ private static CopyOnWriteContent createInWritableState(Map<String,Object> map) {
+ CopyOnWriteContent content=new CopyOnWriteContent();
+ content.map = map;
+ return content;
+ }
+
+ @Override
+ public void freeze() {
+ // Freeze this
+ if (unmodifiableMap==null)
+ unmodifiableMap= map!=null ? Collections.unmodifiableMap(map) : Collections.<String, Object>emptyMap();
+ map=null; // just to keep the states simpler
+
+ // Freeze content
+ for (Map.Entry<String,Object> entry : unmodifiableMap.entrySet()) {
+ if (entry.getValue() instanceof QueryProfile)
+ ((QueryProfile)entry.getValue()).freeze();
+ }
+ super.freeze();
+ }
+
+ private boolean isEmpty() {
+ return (map==null || map.isEmpty()) && (unmodifiableMap ==null || unmodifiableMap.isEmpty());
+ }
+
+ private boolean isWritable() {
+ return !isFrozen() && (map!=null || unmodifiableMap==null);
+ }
+
+ @Override
+ public CopyOnWriteContent clone() {
+ if (isEmpty()) return new CopyOnWriteContent(); // No referencing is necessary in this case
+ if (isDeepUnmodifiable(unmodifiableMap())) {
+ // Create an instance pointing to this and put both in the COPYONWRITE state
+ unmodifiableMap(); // Make sure we have an unmodifiable reference to the map below
+ map=null; // Put this into the COPYONWRITE state (unless it is already frozen, in which case this is a noop)
+ return createInCopyOnWriteState(unmodifiableMap());
+ }
+ else {
+ // This contains query profiles, don't try to defer copying
+ return createInWritableState(deepClone(map));
+ }
+ }
+
+ private boolean isDeepUnmodifiable(Map<String,Object> map) {
+ for (Object value : map.values())
+ if (value instanceof QueryProfile && !((QueryProfile)value).isFrozen()) return false;
+ return true; // all other values are primitives
+ }
+
+ /** Deep clones a map - this handles all value types which can be found in a query profile */
+ static Map<String,Object> deepClone(Map<String,Object> map) {
+ if (map==null) return null;
+ Map<String,Object> mapClone=new HashMap<>(map.size());
+ for (Map.Entry<String,Object> entry : map.entrySet())
+ mapClone.put(entry.getKey(),QueryProfile.cloneIfNecessary(entry.getValue()));
+ return mapClone;
+ }
+
+
+ //------- Content access -------------------------------------------------------
+
+ public Map<String,Object> unmodifiableMap() {
+ if (isEmpty()) return Collections.emptyMap();
+ if (map==null) // in COPYONWRITE or FROZEN state
+ return unmodifiableMap;
+ // In WRITABLE state: Create unmodifiable wrapper if necessary and return it
+ if (unmodifiableMap==null)
+ unmodifiableMap=Collections.unmodifiableMap(map);
+ return unmodifiableMap;
+ }
+
+ public Object get(String key) {
+ if (map!=null) return map.get(key);
+ if (unmodifiableMap!=null) return unmodifiableMap.get(key);
+ return null;
+ }
+
+ public void put(String key,Object value) {
+ ensureNotFrozen();
+ copyIfNotWritable();
+ if (map==null)
+ map=new HashMap<>();
+ map.put(key,value);
+ }
+
+ public void remove(String key) {
+ ensureNotFrozen();
+ copyIfNotWritable();
+ if (map!=null)
+ map.remove(key);
+ }
+
+ private void copyIfNotWritable() {
+ if (isWritable()) return;
+ // move from COPYONWRITE to WRITABLE state
+ map=new HashMap<>(unmodifiableMap); // deep clone is not necessary as this map is shallowly modifiable
+ unmodifiableMap=null; // will be created as needed
+ }
+
+}