aboutsummaryrefslogtreecommitdiffstats
path: root/vespajlib/src/main/java/com/yahoo/slime/SlimeUtils.java
blob: b08346f7cecddaed7b5b0a4a74713d210e4a2d5c (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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.slime;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import static com.yahoo.yolean.Exceptions.uncheck;

/**
 * Extra utilities/operations on slime trees.
 *
 * @author Ulf Lilleengen
 */
public class SlimeUtils {

    public static void copyObject(Inspector from, Cursor to) {
        if (from.type() != Type.OBJECT) {
            throw new IllegalArgumentException("Cannot copy object: " + from);
        }
        from.traverse((ObjectTraverser) (name, inspector) -> setObjectEntry(inspector, name, to));

    }

    public static void setObjectEntry(Inspector from, String name, Cursor to) {
        switch (from.type()) {
            case NIX -> to.setNix(name);
            case BOOL -> to.setBool(name, from.asBool());
            case LONG -> to.setLong(name, from.asLong());
            case DOUBLE -> to.setDouble(name, from.asDouble());
            case STRING -> to.setString(name, from.asString());
            case DATA -> to.setData(name, from.asData());
            case ARRAY -> copyArray(from, to.setArray(name));
            case OBJECT -> copyObject(from, to.setObject(name));
        }
    }

    public static void copyArray(Inspector from, Cursor to) {
        if (from.type() != Type.ARRAY) {
            throw new IllegalArgumentException("Cannot copy array: " + from);
        }
        from.traverse((ArrayTraverser) (i, inspector) -> addValue(inspector, to));
    }

    private static void addValue(Inspector from, Cursor to) {
        switch (from.type()) {
            case NIX -> to.addNix();
            case BOOL -> to.addBool(from.asBool());
            case LONG -> to.addLong(from.asLong());
            case DOUBLE -> to.addDouble(from.asDouble());
            case STRING -> to.addString(from.asString());
            case DATA -> to.addData(from.asData());
            case ARRAY -> copyArray(from, to.addArray());
            case OBJECT -> copyObject(from, to.addObject());
        }

    }

    public static byte[] toJsonBytes(Slime slime) throws IOException {
        return toJsonBytes(slime.get());
    }

    public static byte[] toJsonBytes(Inspector inspector) throws IOException {
        return toJsonBytes(inspector, true);
    }

    public static byte[] toJsonBytes(Inspector inspector, boolean compact) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        new JsonFormat(compact ? 0 : 2).encode(baos, inspector);
        return baos.toByteArray();
    }

    public static String toJson(Slime slime) {
        return toJson(slime.get());
    }

    public static String toJson(Inspector inspector) {
        return toJson(inspector, true);
    }

    public static String toJson(Inspector inspector, boolean compact) {
        var outputStream = new ByteArrayOutputStream();
        var jsonFormat = new JsonFormat(compact ? 0 : 2);
        uncheck(() -> jsonFormat.encode(outputStream, inspector));
        return outputStream.toString(StandardCharsets.UTF_8);
    }

    public static Slime jsonToSlime(byte[] json) {
        Slime slime = new Slime();
        new JsonDecoder().decode(slime, json);
        return slime;
    }

    public static Slime jsonToSlime(String json) {
        return jsonToSlime(json.getBytes(StandardCharsets.UTF_8));
    }

    /** Throws {@link JsonParseException} on invalid JSON. */
    public static Slime jsonToSlimeOrThrow(String json) {
        return jsonToSlimeOrThrow(json.getBytes(StandardCharsets.UTF_8));
    }

    public static Slime jsonToSlimeOrThrow(byte[] json) {
        Slime slime = new Slime();
        new JsonDecoder().decodeOrThrow(slime, json);
        return slime;
    }

    public static Instant instant(Inspector field) {
        return Instant.ofEpochMilli(field.asLong());
    }

    public static Duration duration(Inspector field) {
        return Duration.ofMillis(field.asLong());
    }

    public static boolean isPresent(Inspector field) {
        return field.valid() && field.type() != Type.NIX;
    }

    public static Optional<String> optionalString(Inspector inspector) {
        return Optional.of(inspector).filter(SlimeUtils::isPresent).map(Inspector::asString);
    }

    public static OptionalLong optionalLong(Inspector field) {
        return isPresent(field) ? OptionalLong.of(field.asLong()) : OptionalLong.empty();
    }

    public static OptionalInt optionalInteger(Inspector field) {
        return isPresent(field) ? OptionalInt.of((int) field.asLong()) : OptionalInt.empty();
    }

    public static OptionalDouble optionalDouble(Inspector field) {
        return isPresent(field) ? OptionalDouble.of(field.asDouble()) : OptionalDouble.empty();
    }

    public static Optional<Instant> optionalInstant(Inspector field) {
        return optionalLong(field).stream().mapToObj(Instant::ofEpochMilli).findFirst();
    }

    public static Optional<Duration> optionalDuration(Inspector field) {
        return optionalLong(field).stream().mapToObj(Duration::ofMillis).findFirst();
    }

    public static Iterator<Inspector> entriesIterator(Inspector inspector) {
        return new Iterator<>() {
            private int current = 0;
            @Override public boolean hasNext() { return current < inspector.entries(); }
            @Override public Inspector next() { return inspector.entry(current++); }
        };
    }

    /** Returns stream of entries for given inspector. If the inspector is not an array, empty stream is returned */
    public static Stream<Inspector> entriesStream(Inspector inspector) {
        int characteristics = Spliterator.NONNULL | Spliterator.SIZED | Spliterator.ORDERED;
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(entriesIterator(inspector),
                                                                        characteristics),
                                    false);
    }

    public static boolean equalTo(Inspector a, Inspector b) {
        if (a.type() != b.type()) return false;

        switch (a.type()) {
            case NIX: return a.valid() == b.valid();
            case BOOL: return a.asBool() == b.asBool();
            case LONG: return a.asLong() == b.asLong();
            case DOUBLE: return Double.compare(a.asDouble(), b.asDouble()) == 0;
            case STRING: return a.asString().equals(b.asString());
            case DATA: return Arrays.equals(a.asData(), b.asData());
            case ARRAY: {
                if (a.entries() != b.entries()) return false;
                for (int i = 0; i < a.entries(); i++) {
                    if (!equalTo(a.entry(i), b.entry(i))) return false;
                }
                return true;
            }
            case OBJECT: {
                if (a.fields() != b.fields()) return false;
                boolean[] equal = new boolean[]{ true };
                a.traverse((String key, Inspector value) -> {
                    if (equal[0] && !equalTo(value, b.field(key))) equal[0] = false;
                });
                return equal[0];
            }
            default: throw new IllegalStateException("Unexpected type: " + a.type());
        }
    }
}