aboutsummaryrefslogtreecommitdiffstats
path: root/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/FieldValueConverter.java
blob: d2cf97273adf4fd5cad58553879592f5b410371f (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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.indexinglanguage;

import com.yahoo.document.DataType;
import com.yahoo.document.Field;
import com.yahoo.document.datatypes.*;

import java.util.*;

/**
 * @author Simon Thoresen Hult
 */
public abstract class FieldValueConverter {

    @SuppressWarnings({ "unchecked" })
    public final FieldValue convert(FieldValue value) {
        if (value == null) {
            return null;
        }
        if (shouldConvert(value)) {
            return doConvert(value);
        }
        if (value instanceof Array) {
            return convertArray((Array)value);
        }
        if (value instanceof MapFieldValue) {
            return convertMap((MapFieldValue)value);
        }
        if (value instanceof WeightedSet) {
            return convertWset((WeightedSet)value);
        }
        if (value instanceof StructuredFieldValue) {
            return convertStructured((StructuredFieldValue)value);
        }
        return value;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private FieldValue convertArray(Array val) {
        List<FieldValue> next = new LinkedList<FieldValue>();
        DataType nextType = null;
        for (Iterator<FieldValue> it = val.fieldValueIterator(); it.hasNext();) {
            FieldValue prevVal = it.next();
            FieldValue nextVal = convert(prevVal);
            if (nextVal == null) {
                continue;
            }
            if (nextType == null) {
                nextType = nextVal.getDataType();
            } else if (!nextType.isValueCompatible(nextVal)) {
                throw new IllegalArgumentException("Expected " + nextType.getName() + ", got " +
                                                   nextVal.getDataType().getName());
            }
            next.add(nextVal);
        }
        if (nextType == null) {
            return null;
        }
        Array ret = DataType.getArray(nextType).createFieldValue();
        ret.addAll(next);
        return ret;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private FieldValue convertMap(MapFieldValue<FieldValue, FieldValue> val) {
        Map<FieldValue, FieldValue> next = new LinkedHashMap<>();
        DataType nextKeyType = null, nextValType = null;
        for (Map.Entry<FieldValue, FieldValue> entry : val.entrySet()) {
            FieldValue prevKey = entry.getKey();
            FieldValue nextKey = convert(prevKey);
            if (nextKey == null) {
                continue;
            }
            if (nextKeyType == null) {
                nextKeyType = nextKey.getDataType();
            } else if (!nextKeyType.isValueCompatible(nextKey)) {
                throw new IllegalArgumentException("Expected " + nextKeyType.getName() + ", got " +
                                                   nextKey.getDataType().getName());
            }
            FieldValue prevVal = entry.getValue();
            FieldValue nextVal = convert(prevVal);
            if (nextVal == null) {
                continue;
            }
            if (nextValType == null) {
                nextValType = nextVal.getDataType();
            } else if (!nextValType.isValueCompatible(nextVal)) {
                throw new IllegalArgumentException("Expected " + nextValType.getName() + ", got " +
                                                   nextVal.getDataType().getName());
            }
            next.put(nextKey, nextVal);
        }
        if (nextKeyType == null || nextValType == null) {
            return null;
        }
        MapFieldValue ret = DataType.getMap(nextKeyType, nextValType).createFieldValue();
        ret.putAll(next);
        return ret;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private FieldValue convertWset(WeightedSet val) {
        Map<FieldValue, Integer> next = new LinkedHashMap<>();
        DataType nextType = null;
        for (Iterator<FieldValue> it = val.fieldValueIterator(); it.hasNext();) {
            FieldValue prevKey = it.next();
            Integer prevVal = val.get(prevKey);

            FieldValue nextKey = convert(prevKey);
            if (nextKey == null) {
                continue;
            }
            if (nextType == null) {
                nextType = nextKey.getDataType();
            } else if (!nextType.isValueCompatible(nextKey)) {
                throw new IllegalArgumentException("Expected " + nextType.getName() + ", got " +
                                                   nextKey.getDataType().getName());
            }
            next.put(nextKey, prevVal);
        }
        if (nextType == null) {
            return null;
        }
        WeightedSet ret = DataType.getWeightedSet(nextType, val.getDataType().createIfNonExistent(),
                                                  val.getDataType().removeIfZero()).createFieldValue();

        ret.putAll(next);
        return ret;
    }

    private FieldValue convertStructured(StructuredFieldValue val) {
        StructuredFieldValue ret = val.getDataType().createFieldValue();
        for (Iterator<Map.Entry<Field, FieldValue>> it = val.iterator(); it.hasNext();) {
            Map.Entry<Field, FieldValue> entry = it.next();
            FieldValue prev = entry.getValue();
            FieldValue next = convert(prev);
            if (next == null) {
                continue;
            }
            ret.setFieldValue(entry.getKey(), next);
        }
        return ret;
    }

    /**
     * Returns whether the given {@link FieldValue} should be converted. If this method returns <em>false</em>,
     * the converter will proceed to traverse the value itself to see if its internal can be converted.
     *
     * @param value the value to check
     * @return true to convert, false to traverse
     */
    protected abstract boolean shouldConvert(FieldValue value);

    /**
     * Converts the given value. It is IMPERATIVE that the implementation of this method DOES NOT mutate the given
     * {@link FieldValue} in place, as that can cause SERIOUS inconsistencies in the parent structures.
     *
     * @param value the value to convert
     * @return the value to replace the old
     */
    protected abstract FieldValue doConvert(FieldValue value);

}