summaryrefslogtreecommitdiffstats
path: root/document/src/main/java/com/yahoo/document/idstring/IdString.java
blob: c58f2070e31e066a8210f21f2bddb65d3183fd79 (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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.document.idstring;

import com.yahoo.text.Text;
import com.yahoo.text.Utf8String;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.OptionalInt;

/**
 * To be used with DocumentId constructor.
 *
 * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
 */
public abstract class IdString {

    public boolean hasDocType() {
        return false;
    }

    public String getDocType() {
        return "";
    }

    public boolean hasGroup() {
        return false;
    }

    public boolean hasNumber() {
        return false;
    }

    public long getNumber() {
        return 0;
    }

    public String getGroup() {
        return "";
    }

    public class GidModifier {
        public int usedBits;
        public long value;
    }

    public enum Scheme { doc, userdoc, groupdoc, orderdoc, id }
    final Scheme scheme;
    final String namespace;
    final String namespaceSpecific;
    Utf8String cache;

    public static int[] generateOrderDocParams(String scheme) {
        int parenPos = scheme.indexOf("(");
        int endParenPos = scheme.indexOf(")");

        if (parenPos == -1 || endParenPos == -1) {
            throw new IllegalArgumentException("Unparseable scheme " + scheme + ": Must be on the form orderdoc(width, division)");
        }

        String params = scheme.substring(parenPos + 1, endParenPos);
        String[] vals = params.split(",");

        if (vals.length != 2) {
            throw new IllegalArgumentException("Unparseable scheme " + scheme + ": Must be on the form orderdoc(width, division)");
        }

        int[] retVal = new int[2];

        try {
            retVal[0] = Integer.parseInt(vals[0]);
            retVal[1] = Integer.parseInt(vals[1]);
            return retVal;
        } catch (Exception e) {
            throw new IllegalArgumentException("Unparseable scheme " + scheme + ": Must be on the form orderdoc(width, division)");
        }
    }

    /**
     * Creates a IdString based on the given document id string.
     *
     * The document id string can only contain text characters.
     */
    public static IdString createIdString(String id) {
        validateTextString(id);
        return parseAndCreate(id);
    }

    /**
     * Creates a IdString based on the given serialized document id string.
     *
     * The document id string can not contain 0x0 byte characters.
     */
    public static IdString createFromSerialized(String id) {
        validateNoZeroBytes(id);
        return parseAndCreate(id);
    }

    private static void validateTextString(String id) {
        OptionalInt illegalCodePoint = Text.validateTextString(id);
        if (illegalCodePoint.isPresent()) {
            throw new IllegalArgumentException("Unparseable id '" + id + "': Contains illegal code point 0x" +
                    Integer.toHexString(illegalCodePoint.getAsInt()).toUpperCase());
        }
    }

    private static void validateNoZeroBytes(String id) {
        for (int i = 0; i < id.length(); i++) {
            if (id.codePointAt(i) == 0) {
                throw new IllegalArgumentException("Unparseable id '" + id + "': Contains illegal zero byte code point");
            }
        }
    }

    private static IdString parseAndCreate(String id) {
        String namespace;
        long userId;
        String group;
        long ordering;

        int schemePos = id.indexOf(":");
        if (schemePos < 0) {
            throw new IllegalArgumentException("Unparseable id '" + id + "': Scheme missing");
        }

        //Find scheme
        String schemeStr = id.substring(0, schemePos);
        int currPos = schemePos + 1;

        //Find namespace
        int colonPos = id.indexOf(":", currPos);
        if (colonPos < 0) {
            throw new IllegalArgumentException("Unparseable id '" + id + "': Namespace missing");
        } else {
            namespace = id.substring(currPos, colonPos);

            if (namespace.length() == 0) {
                throw new IllegalArgumentException("Unparseable id '" + id + "': Namespace must be non-empty");
            }

            currPos = colonPos + 1;
        }

        if (schemeStr.equals("id")) {
            colonPos = id.indexOf(":", currPos);
            if (colonPos < 0) {
                throw new IllegalArgumentException("Unparseable id '" + id + "': Document type missing");
            }
            String type = id.substring(currPos, colonPos);
            currPos = colonPos + 1;
            colonPos = id.indexOf(":", currPos);
            if (colonPos < 0) {
                throw new IllegalArgumentException("Unparseable id '" + id + "': Key/value section missing");
            }
            String keyValues = id.substring(currPos, colonPos);

            currPos = colonPos + 1;
            return new IdIdString(namespace, type, keyValues, id.substring(currPos));

        } if (schemeStr.equals("doc")) {
            return new DocIdString(namespace, id.substring(currPos));
        } else if (schemeStr.equals("userdoc")) {
            colonPos = id.indexOf(":", currPos);
            if (colonPos < 0) {
                throw new IllegalArgumentException("Unparseable id '" + id + "': User id missing");
            }

            try {
                userId = new BigInteger(id.substring(currPos, colonPos)).longValue();
            } catch (IllegalArgumentException iae) {
                throw new IllegalArgumentException("Unparseable id '" + id + "': " + iae.getMessage(), iae.getCause());
            }

            currPos = colonPos + 1;
            return new UserDocIdString(namespace, userId, id.substring(currPos));
        } else if (schemeStr.equals("groupdoc")) {
            colonPos = id.indexOf(":", currPos);

            if (colonPos < 0) {
                throw new IllegalArgumentException("Unparseable id '" + id + "': Group id missing");
            }

            group = id.substring(currPos, colonPos);
            currPos = colonPos + 1;
            return new GroupDocIdString(namespace, group, id.substring(currPos));
        } else if (schemeStr.indexOf("orderdoc") == 0) {
            int[] params = generateOrderDocParams(schemeStr);

            colonPos = id.indexOf(":", currPos);

            if (colonPos < 0) {
                throw new IllegalArgumentException("Unparseable id '" + id + "': Group id missing");
            }

            group = id.substring(currPos, colonPos);

            currPos = colonPos + 1;

            colonPos = id.indexOf(":", currPos);
            if (colonPos < 0) {
                throw new IllegalArgumentException("Unparseable id '" + id + "': Ordering missing");
            }

            try {
                ordering = Long.parseLong(id.substring(currPos, colonPos));
            } catch (IllegalArgumentException e) {
                throw new IllegalArgumentException("Unparseable id '" + id + "': " + e.getMessage(), e.getCause());
            }

            currPos = colonPos + 1;
            return new OrderDocIdString(namespace, group, params[0], params[1], ordering, id.substring(currPos));
        } else {
            throw new IllegalArgumentException("Unknown id scheme '" + schemeStr + "'");
        }
    }

    protected IdString(Scheme scheme, String namespace, String namespaceSpecific) {
        this.scheme = scheme;
        this.namespace = namespace;
        this.namespaceSpecific = namespaceSpecific;
    }

    public Scheme getType() { return scheme; }

    public String getNamespace() { return namespace; }
    public String getNamespaceSpecific() { return namespaceSpecific; }
    public abstract long getLocation();
    public String getSchemeParameters() { return ""; }
    public abstract String getSchemeSpecific();
    public GidModifier getGidModifier() { return null; }

    public boolean equals(Object o) {
        return (o instanceof IdString && o.toString().equals(toString()));
    }

    public int hashCode() {
        return toString().hashCode();
    }

    private Utf8String createToString() {
        return new Utf8String(scheme.toString() + getSchemeParameters() + ':' + namespace + ':' + getSchemeSpecific() + namespaceSpecific);
    }
    public String toString() {
        if (cache == null) {
            cache = createToString();
        }
        return cache.toString();
    }
    public Utf8String toUtf8() {
        if (cache == null) {
            cache = createToString();
        }
        return cache;
    }

}