• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

burningwave / json / #7

25 Oct 2023 06:36AM UTC coverage: 45.756% (-3.2%) from 48.947%
#7

push

Roberto-Gentili
Releasing new version

372 of 813 relevant lines covered (45.76%)

0.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

30.82
/src/main/java/org/burningwave/json/Path.java
1
/*
2
 * This file is part of Burningwave JSON.
3
 *
4
 * Author: Roberto Gentili
5
 *
6
 * Hosted at: https://github.com/burningwave/json
7
 *
8
 * --
9
 *
10
 * The MIT License (MIT)
11
 *
12
 * Copyright (c) 2023 Roberto Gentili
13
 *
14
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
15
 * documentation files (the "Software"), to deal in the Software without restriction, including without
16
 * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
17
 * the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
18
 * conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all copies or substantial
21
 * portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
24
 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
25
 * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
26
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
27
 * OR OTHER DEALINGS IN THE SOFTWARE.
28
 */
29
package org.burningwave.json;
30

31
import java.util.AbstractMap;
32
import java.util.ArrayList;
33
import java.util.Arrays;
34
import java.util.Collection;
35
import java.util.Collections;
36
import java.util.LinkedHashMap;
37
import java.util.List;
38
import java.util.Map;
39
import java.util.Optional;
40
import java.util.regex.Matcher;
41
import java.util.regex.Pattern;
42
import java.util.stream.Collectors;
43

44
import org.burningwave.Classes;
45
import org.burningwave.Strings;
46

47
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
48

49
@SuppressWarnings("unchecked")
50
public class Path {
51
        public static final Path INSTANCE;
52

53
        public static class Segment {
54

55
                protected Segment() {}
×
56

57
                public static final String root = "";
58
                public static final String parent = "../";
59
                public static final String current = "./";
60

61
                public static final String toIndexed(String pathSegment, int... indexes) {
62
                        StringBuilder indexedNameBuilder = new StringBuilder(pathSegment);
×
63
                        indexedNameBuilder.append("[");
×
64
                        for (int i = 0; i < indexes.length; i++) {
×
65
                                indexedNameBuilder.append(indexes[i]);
×
66
                                indexedNameBuilder.append(",");
×
67
                        }
68
                        String indexedName = indexedNameBuilder.toString();
×
69
                        if (indexedName.endsWith(",")) {
×
70
                                indexedName = indexedName.substring(0, indexedName.length()-1);
×
71
                        }
72
                        return indexedName + "]";
×
73
                }
74
        }
75

76
        private final Pattern multipleIndexesSearcher;
77
        private final Pattern singleIndexSearcher;
78
        private final Pattern pathSeparatorSearcher;
79
        private final Pattern unvalidCurrentOrParentDirectoryPlaceHolderSearcher;
80

81
        static {
82
                INSTANCE = new Path();
1✔
83
        }
1✔
84

85
        private Path(){
1✔
86
                multipleIndexesSearcher = Pattern.compile("\\[([\\d*\\s*,*]+)\\]");
1✔
87
                singleIndexSearcher = Pattern.compile("\\[([\\d*]+)\\]");
1✔
88
                pathSeparatorSearcher = Pattern.compile("\\.(?![\\/\\.])");
1✔
89
                unvalidCurrentOrParentDirectoryPlaceHolderSearcher = Pattern.compile("(?<![\\.\\/])\\.\\/|(?<!\\/)\\.\\.\\/");
1✔
90
        }
1✔
91

92
        public static final String of(String... pathSegments) {
93
                return String.join(
1✔
94
                        ".",
95
                        Arrays.asList(pathSegments).stream().filter(value -> !value.isEmpty()).collect(Collectors.toList())
1✔
96
                ).replace(Segment.parent + ".", Segment.parent)
1✔
97
                .replace(Segment.current + ".", Segment.current);
1✔
98
        }
99

100
        public String getName(String path) {
101
                String[] splittedPath = path.split("\\.");
1✔
102
                return splittedPath[splittedPath.length -1];
1✔
103
        }
104

105
        public String normalize(String basePath, String pathOrRelativePath) {
106
                String joinCharacter = "";
×
107
                if (basePath == null) {
×
108
                        basePath = "";
×
109
                }
110
                if (pathOrRelativePath == null) {
×
111
                        pathOrRelativePath = "";
×
112
                }
113
                if (basePath.isEmpty()) {
×
114
                        if (pathOrRelativePath.startsWith(".")) {
×
115
                                throw new IllegalArgumentException("Base path cannot be null if the path or relative parameter is a relative path");
×
116
                        }
117
                } else {
118
                        if (pathOrRelativePath.startsWith(".") || basePath.endsWith(".")) {
×
119
                                joinCharacter = "/";
×
120
                        } else if (!pathOrRelativePath.isEmpty()) {
×
121
                                joinCharacter = ".";
×
122
                        }
123
                }
124
                return normalize(String.join(joinCharacter, Arrays.asList(basePath, pathOrRelativePath)));
×
125
        }
126

127
        public String normalize(String path) {
128
                if (path == null) {
×
129
                        throw new IllegalArgumentException("path is null");
×
130
                }
131
                String originalPath = path;
×
132
                if (unvalidCurrentOrParentDirectoryPlaceHolderSearcher.matcher(path).find()) {
×
133
                        throw new IllegalArgumentException(path + " contains not valid " + Segment.current + " or " + Segment.parent + " references");
×
134
                }
135
                Matcher matcher = singleIndexSearcher.matcher(path);
×
136
                int placeHolderIndex = 1;
×
137
                Map<String, String> indexesHolder = new LinkedHashMap<>();
×
138
                while (matcher.find()) {
×
139
                        String placeHolder = "${" + placeHolderIndex++ + "}";
×
140
                        indexesHolder.put("/" + placeHolder, matcher.group(1));
×
141
                        path = path.replaceFirst(singleIndexSearcher.pattern(), "/\\" + placeHolder);
×
142
                }
×
143
                path = Strings.INSTANCE.normalizePath(
×
144
                        pathSeparatorSearcher.matcher(path).replaceAll("/")// Sostituiamo }. con /
×
145
                );
146
                if (path == null) {
×
147
                        throw new IllegalArgumentException(originalPath + " is not a valid path");
×
148
                }
149
                while (path.startsWith("/")) {
×
150
                        path = path.substring(1);
×
151
                }
152
                while(path.endsWith("/")) {
×
153
                        path = path.substring(0, path.lastIndexOf("/"));
×
154
                }
155
                for (Map.Entry<String, String> replacement : indexesHolder.entrySet()) {
×
156
                        path = path.replace(replacement.getKey(), "[" + replacement.getValue() + "]") ;
×
157
                }
×
158
                return path.replace("/", ".");
×
159
        }
160

161
        public List<Integer> getIndexes(String path) {
162
                Matcher matcher = singleIndexSearcher.matcher(path);
1✔
163
                List<Integer> indexes = new ArrayList<>();
1✔
164
                while (matcher.find()) {
1✔
165
                        indexes.add(Integer.parseInt(matcher.group(1)));
1✔
166
                }
167
                return indexes;
1✔
168
        }
169

170
        public String toEndsWithRegEx(String value) {
171
                return ".*?" + toRegEx(value);
1✔
172
        }
173

174
        public String toStartsWithRegEx(String value) {
175
                return toRegEx(value) + ".*?";
×
176
        }
177

178
        public String toContainsRegEx(String value) {
179
                return ".*?" + toRegEx(value) + ".*?";
×
180
        }
181

182
        public String toRegEx(String value) {
183
                Matcher matcher = multipleIndexesSearcher.matcher(value);
1✔
184
                return multipleIndexesSearcher.splitAsStream(value)
1✔
185
                        .map(piece ->
1✔
186
                                piece + (matcher.find() ?
1✔
187
                                        "\\[(" +  String.join("|",  matcher.group(1).split(",")) + ")\\]":
1✔
188
                                        "")
189
                        ).collect(Collectors.joining())
1✔
190
                        .replace(".", "\\.").replace("[]", "\\[.+?\\]");
1✔
191
        }
192

193
        public boolean isRoot(String path) {
194
                return Path.Segment.root.equals(path);
1✔
195
        }
196
        public interface Validation {
197
                public static class Context<S extends JsonSchema, T> {
198

199
                        final org.burningwave.json.Validation.Context validationContext;
200
                        final String path;
201
                        final String name;
202
                        final List<Integer> indexes;
203
                        final S jsonSchema;
204
                        final T rawValue;
205

206
                        Context(org.burningwave.json.Validation.Context validationContext, String path, S jsonSchema, Object value) {
1✔
207
                                this.validationContext = validationContext;
1✔
208
                                this.path = path;
1✔
209
                                this.jsonSchema = jsonSchema;
1✔
210
                                this.name = Path.INSTANCE.getName(path);
1✔
211
                                List<Integer> indexes = Path.INSTANCE.getIndexes(name);
1✔
212
                                if (!indexes.isEmpty()) {
1✔
213
                                        this.indexes = Collections.unmodifiableList(indexes);
1✔
214
                                } else {
215
                                        this.indexes = null;
1✔
216
                                }
217
                                String schemaDescription = jsonSchema.getDescription();
1✔
218
                                if (org.burningwave.json.Validation.Context.MOCK_SCHEMA_LABEL.equals(schemaDescription) &&
1✔
219
                                        !validationContext.checkValue(jsonSchema, value)
×
220
                                ) {
221
                                        rejectValue("UNEXPECTED_TYPE", "unexpected type");
×
222
                                }
223
                                this.rawValue = (T)value;
1✔
224
                        }
1✔
225

226
                        public ObjectHandler getRootHandler() {
227
                                return validationContext.getInputHandler();
×
228
                        }
229

230
                        public ObjectHandler getObjectHandler() {
231
                                return getRootHandler().newFinder().findForPathEquals(this.path);
×
232
                        }
233

234
                        public void rejectValue(
235
                                String checkType,
236
                                String message,
237
                                Object... messageArgs
238
                        ) {
239
                                validationContext.rejectValue(this, checkType, message, messageArgs);
×
240
                        }
×
241

242
                        public static <S extends JsonSchema, T> java.util.function.Predicate<Path.Validation.Context<S, T>> predicateFor(
243
                                Class<T> valueType,
244
                                java.util.function.Predicate<Path.Validation.Context<S, T>> predicate
245
                        ) {
246
                                return pathValidationContext ->
×
247
                                        (valueType == null || valueType.isInstance(pathValidationContext.rawValue)) &&
×
248
                                        predicate.test(pathValidationContext);
×
249
                        }
250

251
                        public boolean isFieldRequired() {
252
                                return Optional.ofNullable(jsonSchema.getRequired()).orElseGet(() -> false);
1✔
253
                        }
254

255
                        public org.burningwave.json.Validation.Context getValidationContext() {
256
                                return validationContext;
×
257
                        }
258

259
                        public S getJsonSchema() {
260
                                return jsonSchema;
×
261
                        }
262

263
                        public String getPath() {
264
                                return path;
×
265
                        }
266

267
                        public String getName() {
268
                                return this.name;
×
269
                        }
270

271
                        public T getRawValue() {
272
                                return rawValue;
1✔
273
                        }
274

275
                        public T getValue() {
276
                                if (rawValue != null && !Classes.INSTANCE.isPrimitive(rawValue)) {
1✔
277
                                        getRootHandler().newValueFinder().findForPathEquals(this.path);
×
278
                                }
279
                                return rawValue;
1✔
280
                        }
281

282
                        public Integer getIndex() {
283
                                return indexes != null ?
×
284
                                        indexes.get(indexes.size() - 1) : null;
×
285
                        }
286

287
                        public <V> V getParent() {
288
                                return findValue(Path.Segment.parent);
×
289
                        }
290

291
                        public ObjectHandler getParentObjectHandler() {
292
                                return findObjectHandler(Path.Segment.parent);
×
293
                        }
294

295
                        public <V> V findValue(String... pathSegmentsOrRelativePathSegments) {
296
                                return getRootHandler().newValueFinder().findForPathEquals(
×
297
                                        resolvePath(pathSegmentsOrRelativePathSegments)
×
298
                                );
299
                        }
300

301
                        public ObjectHandler findObjectHandler(String... pathSegmentsOrRelativePathSegments) {
302
                                return getRootHandler().newFinder().findForPathEquals(
×
303
                                        resolvePath(pathSegmentsOrRelativePathSegments)
×
304
                                );
305
                        }
306

307
                        public <V> V findValueAndConvert(Class<V> targetClass, String... pathSegmentsOrRelativePathSegments) {
308
                                return getRootHandler().newValueFinderAndConverter(targetClass).findForPathEquals(
×
309
                                        resolvePath(pathSegmentsOrRelativePathSegments)
×
310
                                );
311
                        }
312

313
                        protected String resolvePath(String... pathSegmentsOrRelativePathSegments) {
314
                                String pathToFind = Path.of(pathSegmentsOrRelativePathSegments);
×
315
                                if (!pathToFind.startsWith("/")) {
×
316
                                        return Path.INSTANCE.normalize(this.path, pathToFind);
×
317
                                } else {
318
                                        return Path.INSTANCE.normalize(pathToFind);
×
319
                                }
320
                        }
321

322
                        public boolean isRoot() {
323
                                return Path.INSTANCE.isRoot(path);
×
324
                        }
325

326
                        @Override
327
                        public String toString() {
328
                                return
×
329
                                        path + " - " +
330
                                        jsonSchema.getClass().getSimpleName().replace("Schema", "") + ": " +
×
331
                                        Optional.ofNullable(rawValue).map(Object::toString).orElseGet(() -> "null");
×
332
                        }
333

334
                }
335

336
        }
337

338
        abstract static class Predicate<P> implements java.util.function.Predicate<P> {
339
                Collection<Map.Entry<String, String>> pathForRegEx;
340

341
                private Predicate() {
×
342
                        pathForRegEx = new ArrayList<>();
×
343
                }
×
344

345
                Predicate(String path, String pathRegEx) {
346
                        this();
×
347
                        pathForRegEx.add(new AbstractMap.SimpleEntry<>(path, pathRegEx));
×
348
                }
×
349

350
                @Override
351
                public Path.Predicate<P> and(java.util.function.Predicate<? super P> other) {
352
                        return concat(other,
×
353
                                input->
354
                                        test(input) && other.test(input)
×
355
                        );
356
            }
357

358
                @Override
359
                public Path.Predicate<P> or(java.util.function.Predicate<? super P> other) {
360
                        return concat(other,
×
361
                                input->
362
                                        test(input) || other.test(input)
×
363
                        );
364
            }
365

366
                @Override
367
                public Path.Predicate<P> negate() {
368
                        return concat(null, input->
×
369
                                !test(input)
×
370
                        );
371
                }
372

373
                Path.Predicate<P> concat(java.util.function.Predicate<? super P> other, java.util.function.Predicate<? super P> finalPredicate) {
374
                        Collection<Map.Entry<String, String>> otherPathForRegEx =
×
375
                                (other instanceof  Path.Predicate)?
376
                                        ((Path.Predicate<P>)other).pathForRegEx : null;
377

378
                        Path.Predicate<P> pathPredicate = new Path.Predicate<P>() {
×
379
                                @Override
380
                                public boolean test(P input) {
381
                                        return finalPredicate.test(input);
×
382
                                }
383
                        };
384
                        pathPredicate.pathForRegEx.addAll(this.pathForRegEx);
×
385
                        if (otherPathForRegEx != null) {
×
386
                                pathPredicate.pathForRegEx.addAll(otherPathForRegEx);
×
387
                        }
388
                        return pathPredicate;
×
389
                }
390
        }
391

392
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc