aboutsummaryrefslogtreecommitdiffstats
path: root/container-core/src/main/java/com/yahoo/processing/request/CloneHelper.java
blob: d758aff050141fa793be04aef42aeb8cf3a9a4bf (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
133
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.processing.request;

import com.yahoo.collections.MethodCache;
import com.yahoo.component.provider.FreezableClass;
import com.yahoo.lang.PublicCloneable;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Objects;
import java.util.logging.Logger;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.HashMap;

/**
 * Helps to deep clone complex objects
 * The following classes and their subclasses does have a fastpath
 * - com.yahoo.component.provider.FreezableClass
 *  - com.yahoo.processing.request.properties.PublicCloneable BTW, this is the one you should implement too
 *    if you want the fastpath.
 *  - java.util.LinkedList
 *  - java.util.ArrayList
 * The rest has the slow path with reflection,
 * though using a fast thread safe method cache for speedup.
 *
 * @author bratseth
 * @author baldersheim
 */
public class CloneHelper {

    private static final Logger log = Logger.getLogger(CloneHelper.class.getName());
    private static final MethodCache cloneMethodCache = new MethodCache("clone");

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

    private Object arrayClone(Object array) {
        if (array instanceof Object[])
            return objectArrayClone((Object[]) array);
        else if (array instanceof byte[])
            return Arrays.copyOf((byte[])array, ((byte[])array).length);
        else if (array instanceof char[])
            return Arrays.copyOf((char[])array, ((char[])array).length);
        else if (array instanceof short[])
            return Arrays.copyOf((short[])array, ((short[])array).length);
        else if (array instanceof int[])
            return Arrays.copyOf((int[])array, ((int[])array).length);
        else if (array instanceof long[])
            return Arrays.copyOf((long[])array, ((long[])array).length);
        else if (array instanceof float[])
            return Arrays.copyOf((float[])array, ((float[])array).length);
        else if (array instanceof double[])
            return Arrays.copyOf((double[])array, ((double[])array).length);
        else if (array instanceof boolean[])
            return Arrays.copyOf((boolean[])array, ((boolean[])array).length);
        else
            return new IllegalArgumentException("Unexpected primitive array type " + array.getClass());
    }
    
    private Object objectArrayClone(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;
    }

    @SuppressWarnings({"removal"})
    protected Object objectClone(Object object) {
        // Fastpath for commonly used classes
        if (object instanceof FreezableClass)
            return ((FreezableClass)object).clone();
        else if (object instanceof PublicCloneable)
            return ((PublicCloneable<?>)object).clone();
        else if (object instanceof com.yahoo.processing.request.properties.PublicCloneable)
            return ((com.yahoo.processing.request.properties.PublicCloneable<?>)object).clone();
        else if (object instanceof LinkedList)
            return ((LinkedList<?>) object).clone();
        else if (object instanceof ArrayList)
            return ((ArrayList<?>) object).clone();
        else if (object instanceof HashMap)
            return ((HashMap<?, ?>) object).clone();
        else if (object instanceof HashSet)
            return ((HashSet<?>) object).clone();
        
        return cloneByReflection(object);
    }

    private Object cloneByReflection(Object object) {
        try {
            Method cloneMethod = cloneMethodCache.get(object, name -> log.warning("Caching the clone method of '" + name + "'. Let it implement com.yahoo.lang.PublicCloneable instead"));
            if (cloneMethod == null) {
                log.warning("'" + object + "' of class " + object.getClass() +
                            " is Cloneable, but has no clone method - will use the same instance in all requests");
                return null;
            }
            return cloneMethod.invoke(object);
        } catch (IllegalAccessException e) {
            log.warning("'" + object + "' of class " + object.getClass() +
                        " 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);
        }
    }

    /** Clones a map by deep cloning each value which is cloneable and shallow copying all other values. */
    public Map<CompoundName, Object> cloneMap(Map<CompoundName, Object> map) {
        Map<CompoundName, Object> cloneMap = new HashMap<>((int)(map.size()/0.75) + 1);
        for (Map.Entry<CompoundName, Object> entry : map.entrySet()) {
            Object cloneValue = clone(entry.getValue());
            if (cloneValue == null)
                cloneValue = entry.getValue(); // Shallow copy objects which does not support cloning
            cloneMap.put(entry.getKey(), cloneValue);
        }
        return cloneMap;
    }

}