-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathFormat.java
More file actions
245 lines (230 loc) · 8.25 KB
/
Format.java
File metadata and controls
245 lines (230 loc) · 8.25 KB
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
// Copyright 2023-2024 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package build.buf.protovalidate;
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
import com.google.protobuf.Duration;
import com.google.protobuf.Timestamp;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.time.Instant;
import java.util.List;
import org.projectnessie.cel.common.types.Err.ErrException;
import org.projectnessie.cel.common.types.IntT;
import org.projectnessie.cel.common.types.ListT;
import org.projectnessie.cel.common.types.pb.Db;
import org.projectnessie.cel.common.types.pb.DefaultTypeAdapter;
import org.projectnessie.cel.common.types.ref.TypeEnum;
import org.projectnessie.cel.common.types.ref.Val;
/** String formatter for CEL evaluation. */
final class Format {
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
private static final char[] LOWER_HEX_ARRAY = "0123456789abcdef".toCharArray();
/**
* Format the string with a {@link ListT}.
*
* @param fmtString the string to format.
* @param list the arguments.
* @return the formatted string.
* @throws ErrException If an error occurs formatting the string.
*/
static String format(String fmtString, ListT list) {
// StringBuilder to accumulate the formatted string
StringBuilder builder = new StringBuilder();
int index = 0;
int argIndex = 0;
while (index < fmtString.length()) {
char c = fmtString.charAt(index++);
if (c != '%') {
// Append non-format characters directly
builder.append(c);
// Add the entire character if it's not a UTF-8 character.
if ((c & 0x80) != 0) {
// Add the rest of the UTF-8 character.
while (index < fmtString.length() && (fmtString.charAt(index) & 0xc0) == 0x80) {
builder.append(fmtString.charAt(index++));
}
}
continue;
}
if (index >= fmtString.length()) {
throw new ErrException("format: expected format specifier");
}
if (fmtString.charAt(index) == '%') {
// Escaped '%', append '%' and move to the next character
builder.append('%');
index++;
continue;
}
if (argIndex >= list.size().intValue()) {
throw new ErrException("format: not enough arguments");
}
Val arg = list.get(IntT.intOf(argIndex++));
c = fmtString.charAt(index++);
int precision = 6;
if (c == '.') {
// parse the precision
precision = 0;
while (index < fmtString.length()
&& '0' <= fmtString.charAt(index)
&& fmtString.charAt(index) <= '9') {
precision = precision * 10 + (fmtString.charAt(index++) - '0');
}
if (index >= fmtString.length()) {
throw new ErrException("format: expected format specifier");
}
c = fmtString.charAt(index++);
}
switch (c) {
case 'd':
formatDecimal(builder, arg);
break;
case 'x':
formatHex(builder, arg, LOWER_HEX_ARRAY);
break;
case 'X':
formatHex(builder, arg, HEX_ARRAY);
break;
case 's':
formatString(builder, arg);
break;
case 'e':
case 'f':
case 'b':
case 'o':
default:
throw new ErrException("format: unparsable format specifier %s", c);
}
}
return builder.toString();
}
/**
* Converts a byte array to a hexadecimal string representation.
*
* @param bytes the byte array to convert.
* @param digits the array of hexadecimal digits.
* @return the hexadecimal string representation.
*/
private static String bytesToHex(byte[] bytes, char[] digits) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = digits[v >>> 4];
hexChars[j * 2 + 1] = digits[v & 0x0F];
}
return new String(hexChars);
}
/**
* Formats a string value.
*
* @param builder the StringBuilder to append the formatted string to.
* @param val the value to format.
*/
private static void formatString(StringBuilder builder, Val val) {
TypeEnum type = val.type().typeEnum();
if (type == TypeEnum.Bool) {
builder.append(val.booleanValue());
} else if (type == TypeEnum.String || type == TypeEnum.Int || type == TypeEnum.Uint) {
builder.append(val.value());
} else if (type == TypeEnum.Bytes) {
builder.append(new String((byte[]) val.value(), StandardCharsets.UTF_8));
} else if (type == TypeEnum.Double) {
formatDecimal(builder, val);
} else if (type == TypeEnum.Duration) {
formatDuration(builder, val);
} else if (type == TypeEnum.Timestamp) {
formatTimestamp(builder, val);
} else if (type == TypeEnum.List) {
formatList(builder, val);
} else if (type == TypeEnum.Map) {
throw new ErrException("unimplemented string map type");
} else if (type == TypeEnum.Null) {
throw new ErrException("unimplemented string null type");
}
}
/**
* Formats a list value.
*
* @param builder the StringBuilder to append the formatted list value to.
* @param val the value to format.
*/
@SuppressWarnings("rawtypes")
private static void formatList(StringBuilder builder, Val val) {
builder.append('[');
List list = val.convertToNative(List.class);
for (int i = 0; i < list.size(); i++) {
Object obj = list.get(i);
formatString(builder, DefaultTypeAdapter.nativeToValue(Db.newDb(), null, obj));
if (i != list.size() - 1) {
builder.append(", ");
}
}
builder.append(']');
}
/**
* Formats a timestamp value.
*
* @param builder the StringBuilder to append the formatted timestamp value to.
* @param val the value to format.
*/
private static void formatTimestamp(StringBuilder builder, Val val) {
Timestamp timestamp = val.convertToNative(Timestamp.class);
Instant instant = Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos());
builder.append(ISO_INSTANT.format(instant));
}
/**
* Formats a duration value.
*
* @param builder the StringBuilder to append the formatted duration value to.
* @param val the value to format.
*/
private static void formatDuration(StringBuilder builder, Val val) {
Duration duration = val.convertToNative(Duration.class);
double totalSeconds = duration.getSeconds() + (duration.getNanos() / 1_000_000_000.0);
DecimalFormat formatter = new DecimalFormat("0.#########");
builder.append(formatter.format(totalSeconds));
builder.append("s");
}
/**
* Formats a hexadecimal value.
*
* @param builder the StringBuilder to append the formatted hexadecimal value to.
* @param val the value to format.
* @param digits the array of hexadecimal digits.
*/
private static void formatHex(StringBuilder builder, Val val, char[] digits) {
String hexString;
TypeEnum type = val.type().typeEnum();
if (type == TypeEnum.Int || type == TypeEnum.Uint) {
hexString = Long.toHexString(val.intValue());
} else if (type == TypeEnum.Bytes) {
byte[] bytes = (byte[]) val.value();
hexString = bytesToHex(bytes, digits);
} else if (type == TypeEnum.String) {
hexString = val.value().toString();
} else {
throw new ErrException("formatHex: expected int or string");
}
builder.append(hexString);
}
/**
* Formats a decimal value.
*
* @param builder the StringBuilder to append the formatted decimal value to.
* @param val the value to format.
*/
private static void formatDecimal(StringBuilder builder, Val val) {
DecimalFormat formatter = new DecimalFormat("0.#########");
builder.append(formatter.format(val.value()));
}
}