blob: f59b267c01c606e849b08479d89346fc304193ca [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
Matthias Andreas Benkard91dbd742022-10-17 19:38:56 +02007import static java.nio.charset.StandardCharsets.US_ASCII;
8import static java.nio.charset.StandardCharsets.UTF_8;
9
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +010010import java.nio.ByteBuffer;
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +010011import java.text.ParseException;
12import java.util.ArrayList;
13import java.util.List;
14import java.util.Objects;
Matthias Andreas Benkard25b7f902021-12-17 06:02:11 +010015import org.apiguardian.api.API;
16import org.apiguardian.api.API.Status;
Matthias Andreas Benkard9006e702022-03-01 13:43:50 +010017import org.jetbrains.annotations.Nullable;
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +010018
19/**
20 * A GVariant signature string.
21 *
22 * <p>Describes a type in the GVariant type system. The type can be arbitrarily complex.
23 *
24 * <p><strong>Examples</strong>
25 *
26 * <dl>
27 * <dt>{@code "i"}
28 * <dd>a single 32-bit integer
29 * <dt>{@code "ai"}
30 * <dd>an array of 32-bit integers
31 * <dt>{@code "(bbb(sai))"}
32 * <dd>a record consisting of three booleans and a nested record, which consists of a string and
33 * an array of 32-bit integers
34 * </dl>
35 */
Matthias Andreas Benkard25b7f902021-12-17 06:02:11 +010036@API(status = Status.STABLE)
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +010037public final class Signature {
38
39 private final String signatureString;
40 private final Decoder<?> decoder;
41
42 Signature(ByteBuffer signatureBytes) throws ParseException {
43 this.decoder = parseSignature(signatureBytes);
44
45 signatureBytes.rewind();
Matthias Andreas Benkard91dbd742022-10-17 19:38:56 +020046 this.signatureString = US_ASCII.decode(signatureBytes).toString();
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +010047 }
48
49 static Signature parse(ByteBuffer signatureBytes) throws ParseException {
50 return new Signature(signatureBytes);
51 }
52
Matthias Andreas Benkard4e8423d2021-12-19 22:56:09 +010053 public static Signature parse(String signatureString) throws ParseException {
Matthias Andreas Benkard91dbd742022-10-17 19:38:56 +020054 var signatureBytes = ByteBuffer.wrap(signatureString.getBytes(US_ASCII));
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +010055 return parse(signatureBytes);
56 }
57
58 /**
59 * Returns a {@link Decoder} that can decode values conforming to this signature.
60 *
61 * @return a {@link Decoder} for this signature
62 */
63 @SuppressWarnings("unchecked")
64 Decoder<Object> decoder() {
65 return (Decoder<Object>) decoder;
66 }
67
68 /**
69 * Returns the signature formatted as a GVariant signature string.
70 *
71 * @return a GVariant signature string.
72 */
73 @Override
74 public String toString() {
75 return signatureString;
76 }
77
78 @Override
Matthias Andreas Benkard9006e702022-03-01 13:43:50 +010079 public boolean equals(@Nullable Object o) {
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +010080 return (o instanceof Signature signature)
81 && Objects.equals(signatureString, signature.signatureString);
82 }
83
84 @Override
85 public int hashCode() {
86 return Objects.hash(signatureString);
87 }
88
89 private static Decoder<?> parseSignature(ByteBuffer signature) throws ParseException {
90 char c = (char) signature.get();
91 return switch (c) {
92 case 'b' -> Decoder.ofBoolean();
93 case 'y' -> Decoder.ofByte();
94 case 'n', 'q' -> Decoder.ofShort();
95 case 'i', 'u' -> Decoder.ofInt();
96 case 'x', 't' -> Decoder.ofLong();
97 case 'd' -> Decoder.ofDouble();
Matthias Andreas Benkard91dbd742022-10-17 19:38:56 +020098 case 's', 'o', 'g' -> Decoder.ofString(UTF_8);
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +010099 case 'v' -> Decoder.ofVariant();
100 case 'm' -> Decoder.ofMaybe(parseSignature(signature));
Matthias Andreas Benkardcd924f62021-12-28 00:46:06 +0100101 case '(' -> Decoder.ofStructure(parseTupleTypes(signature).toArray(new Decoder<?>[0]));
Matthias Andreas Benkardd6a25d12021-12-28 01:13:58 +0100102 case 'a' -> {
103 char elementC = (char) signature.get(signature.position());
104 if (elementC == '{') {
105 signature.get();
106 List<Decoder<?>> entryTypes = parseDictionaryEntryTypes(signature);
107 yield Decoder.ofDictionary(entryTypes.get(0), entryTypes.get(1));
108 } else {
109 yield Decoder.ofArray(parseSignature(signature));
Matthias Andreas Benkardcd924f62021-12-28 00:46:06 +0100110 }
Matthias Andreas Benkardd6a25d12021-12-28 01:13:58 +0100111 }
112 case '{' -> {
113 List<Decoder<?>> entryTypes = parseDictionaryEntryTypes(signature);
114 yield Decoder.ofDictionaryEntry(entryTypes.get(0), entryTypes.get(1));
Matthias Andreas Benkardcd924f62021-12-28 00:46:06 +0100115 }
Matthias Andreas Benkard61464282024-03-02 13:50:21 +0100116 default ->
117 throw new ParseException(
118 String.format("encountered unknown signature byte '%c'", c), signature.position());
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +0100119 };
120 }
121
Matthias Andreas Benkardd6a25d12021-12-28 01:13:58 +0100122 private static List<Decoder<?>> parseDictionaryEntryTypes(ByteBuffer signature)
123 throws ParseException {
124 var tupleTypes = parseTupleTypes(signature);
125 if (tupleTypes.size() != 2) {
126 throw new ParseException(
127 String.format("dictionary entry type with %d components, expected 2", tupleTypes.size()),
128 signature.position());
129 }
130 return tupleTypes;
131 }
132
Matthias Andreas Benkard31c61e72021-12-16 20:06:39 +0100133 private static List<Decoder<?>> parseTupleTypes(ByteBuffer signature) throws ParseException {
134 List<Decoder<?>> decoders = new ArrayList<>();
135
136 while (true) {
137 char c = (char) signature.get(signature.position());
138 if (c == ')' || c == '}') {
139 signature.get();
140 break;
141 }
142
143 decoders.add(parseSignature(signature));
144 }
145
146 return decoders;
147 }
148}