blob: cc9674d75b60d458306033bfa8f661afc054081c [file] [log] [blame]
Matthias Andreas Benkardb5d657a2022-02-03 21:14:30 +01001// SPDX-FileCopyrightText: © 2021 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
2//
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +01005package eu.mulk.jgvariant.core;
6
7import java.nio.ByteBuffer;
8import java.nio.charset.StandardCharsets;
9import java.text.ParseException;
10import java.util.ArrayList;
11import java.util.List;
12import java.util.Objects;
Matthias Andreas Benkard25b7f902021-12-17 06:02:11 +010013import org.apiguardian.api.API;
14import org.apiguardian.api.API.Status;
Matthias Andreas Benkard9006e702022-03-01 13:43:50 +010015import org.jetbrains.annotations.Nullable;
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +010016
17/**
18 * A GVariant signature string.
19 *
20 * <p>Describes a type in the GVariant type system. The type can be arbitrarily complex.
21 *
22 * <p><strong>Examples</strong>
23 *
24 * <dl>
25 * <dt>{@code "i"}
26 * <dd>a single 32-bit integer
27 * <dt>{@code "ai"}
28 * <dd>an array of 32-bit integers
29 * <dt>{@code "(bbb(sai))"}
30 * <dd>a record consisting of three booleans and a nested record, which consists of a string and
31 * an array of 32-bit integers
32 * </dl>
33 */
Matthias Andreas Benkard25b7f902021-12-17 06:02:11 +010034@API(status = Status.STABLE)
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +010035public final class Signature {
36
37 private final String signatureString;
38 private final Decoder<?> decoder;
39
40 Signature(ByteBuffer signatureBytes) throws ParseException {
41 this.decoder = parseSignature(signatureBytes);
42
43 signatureBytes.rewind();
44 this.signatureString = StandardCharsets.US_ASCII.decode(signatureBytes).toString();
45 }
46
47 static Signature parse(ByteBuffer signatureBytes) throws ParseException {
48 return new Signature(signatureBytes);
49 }
50
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010051 public static Signature parse(String signatureString) throws ParseException {
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +010052 var signatureBytes = ByteBuffer.wrap(signatureString.getBytes(StandardCharsets.US_ASCII));
53 return parse(signatureBytes);
54 }
55
56 /**
57 * Returns a {@link Decoder} that can decode values conforming to this signature.
58 *
59 * @return a {@link Decoder} for this signature
60 */
61 @SuppressWarnings("unchecked")
62 Decoder<Object> decoder() {
63 return (Decoder<Object>) decoder;
64 }
65
66 /**
67 * Returns the signature formatted as a GVariant signature string.
68 *
69 * @return a GVariant signature string.
70 */
71 @Override
72 public String toString() {
73 return signatureString;
74 }
75
76 @Override
Matthias Andreas Benkard9006e702022-03-01 13:43:50 +010077 public boolean equals(@Nullable Object o) {
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +010078 return (o instanceof Signature signature)
79 && Objects.equals(signatureString, signature.signatureString);
80 }
81
82 @Override
83 public int hashCode() {
84 return Objects.hash(signatureString);
85 }
86
87 private static Decoder<?> parseSignature(ByteBuffer signature) throws ParseException {
88 char c = (char) signature.get();
89 return switch (c) {
90 case 'b' -> Decoder.ofBoolean();
91 case 'y' -> Decoder.ofByte();
92 case 'n', 'q' -> Decoder.ofShort();
93 case 'i', 'u' -> Decoder.ofInt();
94 case 'x', 't' -> Decoder.ofLong();
95 case 'd' -> Decoder.ofDouble();
96 case 's', 'o', 'g' -> Decoder.ofString(StandardCharsets.UTF_8);
97 case 'v' -> Decoder.ofVariant();
98 case 'm' -> Decoder.ofMaybe(parseSignature(signature));
Matthias Andreas Benkardcd924f62021-12-28 00:46:06 +010099 case '(' -> Decoder.ofStructure(parseTupleTypes(signature).toArray(new Decoder<?>[0]));
Matthias Andreas Benkardd6a25d12021-12-28 01:13:58 +0100100 case 'a' -> {
101 char elementC = (char) signature.get(signature.position());
102 if (elementC == '{') {
103 signature.get();
104 List<Decoder<?>> entryTypes = parseDictionaryEntryTypes(signature);
105 yield Decoder.ofDictionary(entryTypes.get(0), entryTypes.get(1));
106 } else {
107 yield Decoder.ofArray(parseSignature(signature));
Matthias Andreas Benkardcd924f62021-12-28 00:46:06 +0100108 }
Matthias Andreas Benkardd6a25d12021-12-28 01:13:58 +0100109 }
110 case '{' -> {
111 List<Decoder<?>> entryTypes = parseDictionaryEntryTypes(signature);
112 yield Decoder.ofDictionaryEntry(entryTypes.get(0), entryTypes.get(1));
Matthias Andreas Benkardcd924f62021-12-28 00:46:06 +0100113 }
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +0100114 default -> throw new ParseException(
115 String.format("encountered unknown signature byte '%c'", c), signature.position());
116 };
117 }
118
Matthias Andreas Benkardd6a25d12021-12-28 01:13:58 +0100119 private static List<Decoder<?>> parseDictionaryEntryTypes(ByteBuffer signature)
120 throws ParseException {
121 var tupleTypes = parseTupleTypes(signature);
122 if (tupleTypes.size() != 2) {
123 throw new ParseException(
124 String.format("dictionary entry type with %d components, expected 2", tupleTypes.size()),
125 signature.position());
126 }
127 return tupleTypes;
128 }
129
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +0100130 private static List<Decoder<?>> parseTupleTypes(ByteBuffer signature) throws ParseException {
131 List<Decoder<?>> decoders = new ArrayList<>();
132
133 while (true) {
134 char c = (char) signature.get(signature.position());
135 if (c == ')' || c == '}') {
136 signature.get();
137 break;
138 }
139
140 decoders.add(parseSignature(signature));
141 }
142
143 return decoders;
144 }
145}