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

torand / jsonschema2java / 18399814123

10 Oct 2025 07:35AM UTC coverage: 78.214% (-0.9%) from 79.157%
18399814123

push

github

torand
fix: bean validation annotations on primitive subtypes of compound pojo property types now generated

287 of 413 branches covered (69.49%)

Branch coverage included in aggregate %.

440 of 533 new or added lines in 26 files covered. (82.55%)

1 existing line in 1 file now uncovered.

808 of 987 relevant lines covered (81.86%)

4.75 hits per line

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

59.29
/src/main/java/io/github/torand/jsonschema2java/collectors/SchemaResolver.java
1
/*
2
 * Copyright (c) 2024-2025 Tore Eide Andersen
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package io.github.torand.jsonschema2java.collectors;
17

18
import com.networknt.schema.*;
19
import com.networknt.schema.SpecVersion.VersionFlag;
20
import com.networknt.schema.resource.SchemaMapper;
21
import io.github.torand.jsonschema2java.generators.Options;
22
import io.github.torand.jsonschema2java.utils.JsonSchema2JavaException;
23
import io.github.torand.jsonschema2java.utils.JsonSchemaDef;
24

25
import java.io.FileInputStream;
26
import java.io.IOException;
27
import java.io.InputStream;
28
import java.net.URI;
29
import java.nio.file.*;
30
import java.nio.file.attribute.BasicFileAttributes;
31
import java.util.ArrayList;
32
import java.util.List;
33
import java.util.Optional;
34
import java.util.Set;
35

36
import static io.github.torand.javacommons.lang.Exceptions.illegalStateException;
37
import static io.github.torand.jsonschema2java.collectors.Extensions.EXT_MODEL_SUBDIR;
38
import static io.github.torand.jsonschema2java.utils.StringUtils.toPascalCase;
39
import static java.util.Objects.isNull;
40

41
/**
42
 * Resolves (loads) external JSON Schemas referenced in a JSON Schema.
43
 */
44
public class SchemaResolver {
45
    private final Options opts;
46

47
    private JsonSchemaFactory schemaFactory;
48

49
    public SchemaResolver(Options opts) {
2✔
50
        this.opts = opts;
3✔
51
    }
1✔
52

53
    public static List<String> validate(Path schemaFile) {
54
        JsonSchemaFactory metaSchemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V202012);
×
55
        SchemaValidatorsConfig.Builder builder = SchemaValidatorsConfig.builder();
×
56

57
        // By default, the JDK regular expression implementation which is not ECMA 262 compliant is used.
58
        // Note that setting this requires including optional dependencies
59
        // builder.regularExpressionFactory(GraalJSRegularExpressionFactory.getInstance());
60
        // builder.regularExpressionFactory(JoniRegularExpressionFactory.getInstance());
61
        SchemaValidatorsConfig config = builder.build();
×
62

63
        // Due to the mapping the meta-schema will be retrieved from the classpath at classpath:draft/2020-12/schema.
64
        JsonSchema metaSchema = metaSchemaFactory.getSchema(SchemaLocation.of(SchemaId.V202012), config);
×
65
        String schemaContent;
66

67
        try {
68
            schemaContent = new String(Files.readAllBytes(schemaFile));
×
69
        } catch (IOException e) {
×
NEW
70
            throw new JsonSchema2JavaException(e);
×
71
        }
×
72

73
        Set<ValidationMessage> messages = metaSchema.validate(schemaContent, InputFormat.JSON, executionContext -> {
×
74
            // By default, since Draft 2019-09 the format keyword only generates annotations and not assertions
75
            executionContext.getExecutionConfig().setFormatAssertionsEnabled(true);
×
76
        });
×
77

78
        return messages.stream()
×
79
            .map(msg -> "%s %s".formatted(msg.getEvaluationPath().toString(), msg.getMessage()))
×
80
            .toList();
×
81
    }
82

83
    public JsonSchemaDef load(Path schemaFile) {
84
        JsonSchema schema;
85
        try (InputStream schemaStream = new FileInputStream(schemaFile.toFile())) {
6✔
86
            JsonSchemaFactory factory = getSchemaFactory();
3✔
87
            schema = factory.getSchema(schemaStream);
4✔
88
        } catch (IOException e) {
×
NEW
89
            throw new JsonSchema2JavaException("Failed to open schema file %s".formatted(schemaFile), e);
×
90
        }
1✔
91

92
        String schemaName = getSchemaName(schemaFile);
3✔
93

94
        return new JsonSchemaDef(schemaName, schema);
6✔
95
    }
96

97
    public Optional<JsonSchemaDef> get(URI ref) {
98
        JsonSchema schema = getSchemaFactory().getSchema(ref);
5✔
99

100
        if (isNull(schema)) {
3!
101
            return Optional.empty();
×
102
        }
103

104
        return Optional.of(new JsonSchemaDef(getTypeName(ref), schema));
8✔
105
    }
106

107
    public JsonSchemaDef getOrThrow(URI ref) {
108
        return get(ref).orElseThrow(illegalStateException("Schema %s not found", ref));
14✔
109
    }
110

111
    public static String getTypeName(URI ref) {
112
        String refStr = ref.toString();
3✔
113

114
        int lastSlashIdx = refStr.lastIndexOf('/');
4✔
115
        if (lastSlashIdx != -1) {
3!
116
            refStr = refStr.substring(lastSlashIdx + 1);
6✔
117
        }
118

119
        int extIdx = refStr.lastIndexOf('.');
4✔
120
        if (extIdx != -1) {
3!
NEW
121
            refStr = refStr.substring(0, extIdx);
×
122
        }
123

124
        return toPascalCase(refStr);
3✔
125
    }
126

127
    public Optional<String> getModelSubpackage(URI ref) {
128
        JsonSchemaDef schema = getOrThrow(ref);
4✔
129
        return schema.extensions()
4✔
130
            .getString(EXT_MODEL_SUBDIR)
2✔
131
            .map(subdir -> subdir.replace("/", "."));
6✔
132
    }
133

134
    public boolean isEnumType(URI ref) {
135
        return get(ref).map(SchemaResolver::isEnumType).orElse(false);
11✔
136
    }
137

138
    public boolean isObjectType(URI ref) {
NEW
139
        return get(ref).map(SchemaResolver::isObjectType).orElse(false);
×
140
    }
141

142
    public boolean isArrayType(URI ref) {
NEW
143
        return get(ref).map(SchemaResolver::isArrayType).orElse(false);
×
144
    }
145

146
    public boolean isCompoundType(URI ref) {
NEW
147
        return get(ref).map(SchemaResolver::isCompoundType).orElse(false);
×
148
    }
149

150
    public boolean isPrimitiveType(URI ref) {
151
        return get(ref).map(SchemaResolver::isPrimitiveType).orElse(false);
11✔
152
    }
153

154
    public static boolean isEnumType(JsonSchemaDef schema) {
155
        return schema.isEnum();
3✔
156
    }
157

158
    public static boolean isObjectType(JsonSchemaDef schema) {
159
        return schema.hasType("object");
4✔
160
    }
161

162
    public static boolean isArrayType(JsonSchemaDef schema) {
163
        return schema.hasType("array");
4✔
164
    }
165

166
    public static boolean isCompoundType(JsonSchemaDef schema) {
167
        return schema.hasAllOf();
3✔
168
    }
169

170
    /**
171
     * Indicates if schema represents a non-enumerated primitive JSON type, i.e. string, number, integer or boolean
172
     */
173
    public static boolean isPrimitiveType(JsonSchemaDef schema) {
174
        return !isEnumType(schema) && !isObjectType(schema) && !isArrayType(schema) && !isCompoundType(schema);
16!
175
    }
176

177
    public static List<Path> findSchemaFiles(Path rootDir, String pattern) {
178
        List<Path> schemaFiles = new ArrayList<>();
×
179

180
        FileSystem fs = FileSystems.getDefault();
×
181
        PathMatcher matcher = fs.getPathMatcher(pattern);
×
182

183
        FileVisitor<Path> matcherVisitor = new SimpleFileVisitor<>() {
×
184
            @Override
185
            public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) {
186
                Path name = file.getFileName();
×
187
                if (matcher.matches(name)) {
×
188
                    schemaFiles.add(rootDir.resolve(name.toString()));
×
189
                }
190
                return FileVisitResult.CONTINUE;
×
191
            }
192
        };
193

194
        try {
195
            Files.walkFileTree(rootDir, matcherVisitor);
×
196
        } catch (IOException e) {
×
NEW
197
            throw new JsonSchema2JavaException(e);
×
198
        }
×
199

200
        return schemaFiles;
×
201
    }
202

203
    private static String getSchemaName(Path schemaFile) {
204
        String filenameWithExt = schemaFile.getFileName().toString();
4✔
205
        int dotIdx = filenameWithExt.lastIndexOf('.');
4✔
206
        return toPascalCase(filenameWithExt.substring(0, dotIdx));
6✔
207
    }
208

209
    private JsonSchemaFactory getSchemaFactory() {
210
        if (schemaFactory == null) {
3✔
211
            JsonMetaSchema.Builder metaSchemaBuilder = JsonMetaSchema.builder(JsonMetaSchema.getV202012());
3✔
212
            Extensions.KEYWORDS.forEach(extKeyword ->
4✔
213
                metaSchemaBuilder.keyword(new NonValidationKeyword(extKeyword))
8✔
214
            );
215

216
            schemaFactory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
7✔
217
                builder -> builder
3✔
218
                    .metaSchema(metaSchemaBuilder.build())
4✔
219
                    .schemaMappers(schemaMappers -> schemaMappers.add(createSchemaMapper()))
8✔
220
            );
221
        }
222

223
        return schemaFactory;
3✔
224
    }
225

226
    private SchemaMapper createSchemaMapper() {
227
        return absoluteIRI -> {
3✔
228
            String iri = absoluteIRI.toString();
3✔
229

230
            String schemaIdRootUri = opts.schemaIdRootUri().toString();
5✔
231
            if (schemaIdRootUri.endsWith("/")) {
4!
232
                schemaIdRootUri = schemaIdRootUri.substring(0, schemaIdRootUri.length() - 1);
×
233
            }
234

235
            if (iri.startsWith(schemaIdRootUri)) {
4!
236
                String subIri = iri.substring(schemaIdRootUri.length() + 1); // Skip trailing slash
7✔
237

238
                int typeNameIdx = subIri.lastIndexOf('/');
4✔
239
                String path = (typeNameIdx == -1) ? "" : subIri.substring(0, typeNameIdx);
6!
240

241
                String typeName = getTypeName(URI.create(iri));
4✔
242
                return AbsoluteIri.of("file:" + Path.of(opts.searchRootDir(), path, "%s.json".formatted(typeName)));
25✔
243
            } else {
NEW
244
                throw new JsonSchema2JavaException("Unexpected root URI in $id: %s".formatted(iri));
×
245
            }
246
        };
247
    }
248
}
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

© 2026 Coveralls, Inc