aboutsummaryrefslogtreecommitdiffstats
path: root/container-search/src/main/java/com/yahoo/search/query/properties/PropertyMap.java
blob: 820c4fc8ea34477879a9975fe9db5a2f6b2d5d49 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// 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.properties;

import com.yahoo.processing.request.CompoundName;
import com.yahoo.search.query.Properties;
import com.yahoo.search.result.Hit;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.logging.Logger;

/**
 * A Map backing of Properties.
 * <p>
 * When this is cloned it will deep copy not only the model object map, but also each
 * clonable member inside the map.
 * <p>
 * Subclassing is supported, a hook can be implemented to provide conditional inclusion in the map.
 * By default - all properties are accepted, so set is never propagated.
 * <p>
 * This class is not multithread safe.
 *
 * @author bratseth
 */
public class PropertyMap extends Properties {

    private static Logger log=Logger.getLogger(PropertyMap.class.getName());

    /** The properties of this */
    private Map<CompoundName, Object> properties = new LinkedHashMap<>();

    public void set(CompoundName name, Object value, Map<String,String> context) {
        if (shouldSet(name, value))
            properties.put(name, value);
        else
            super.set(name, value, context);
    }

    /**
     * Return true if this value should be set in this map, false if the set should be propagated instead
     * This default implementation always returns true.
     */
    protected boolean shouldSet(CompoundName name,Object value) { return true; }

    public @Override Object get(CompoundName name, Map<String,String> context,
                                com.yahoo.processing.request.Properties substitution) {
        if ( ! properties.containsKey(name)) return super.get(name,context,substitution);
        return properties.get(name);
    }

    /**
     * Returns a direct reference to the map containing the properties set in this instance.
     */
    public Map<CompoundName, Object> propertyMap() {
        return properties;
    }

    public @Override PropertyMap clone() {
        PropertyMap clone = (PropertyMap)super.clone();
        clone.properties = new HashMap<>();
        for (Map.Entry<CompoundName, Object> entry : this.properties.entrySet()) {
            Object cloneValue = clone(entry.getValue());
            if (cloneValue == null)
                cloneValue = entry.getValue(); // Shallow copy objects which does not support cloning
            clone.properties.put(entry.getKey(), cloneValue);
        }
        return clone;
    }

    /** Clones this object if it is clonable, and the clone is public. Returns null if not */
    public static Object clone(Object object) {
        if (object==null) return null;
        if (! ( object instanceof Cloneable) ) return null;
        if (object  instanceof Object[])
            return arrayClone((Object[])object);
        else
            return objectClone(object);
    }

    private static Object arrayClone(Object[] object) {
        Object[] arrayClone= Arrays.copyOf(object, object.length);
        // deep clone
        for (int i=0; i<arrayClone.length; i++) {
            Object elementClone=clone(arrayClone[i]);
            if (elementClone!=null)
                arrayClone[i]=elementClone;
        }
        return arrayClone;
    }

    private static Object objectClone(Object object) {
        if (object instanceof Hit) {
            return ((Hit) object).clone();
        } else if (object instanceof LinkedList) {
            return ((LinkedList) object).clone();
        }
        try {
            Method cloneMethod=object.getClass().getMethod("clone");
            return cloneMethod.invoke(object);
        }
        catch (NoSuchMethodException e) {
            log.warning("'" + object + "' is Cloneable, but has no clone method - will use the same instance in all requests");
            return null;
        }
        catch (IllegalAccessException e) {
            log.warning("'" + object + "' is Cloneable, but clone method cannot be accessed - will use the same instance in all requests");
            return null;
        }
        catch (InvocationTargetException e) {
            throw new RuntimeException("Exception cloning '" + object + "'",e);
        }
    }

    @Override
    public Map<String, Object> listProperties(CompoundName path, Map<String, String> context, com.yahoo.processing.request.Properties substitution) {
        Map<String, Object> map = super.listProperties(path, context, substitution);

        for (Map.Entry<CompoundName, Object> entry : properties.entrySet()) {
            if ( ! entry.getKey().hasPrefix(path)) continue;
            CompoundName propertyName = entry.getKey().rest(path.size());
            if (propertyName.isEmpty()) continue;
            map.put(propertyName.toString(), entry.getValue());
        }
        return map;
    }

}