1
2
3
4
5
6 package dev.metaschema.databind.io;
7
8 import org.eclipse.jdt.annotation.NotOwning;
9 import org.eclipse.jdt.annotation.Owning;
10
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.net.URI;
14 import java.net.URL;
15 import java.util.Map;
16
17 import dev.metaschema.core.configuration.DefaultConfiguration;
18 import dev.metaschema.core.configuration.IConfiguration;
19 import dev.metaschema.core.configuration.IMutableConfiguration;
20 import dev.metaschema.core.metapath.item.node.IDocumentNodeItem;
21 import dev.metaschema.core.model.AbstractResourceResolver;
22 import dev.metaschema.core.model.IBoundObject;
23 import dev.metaschema.core.util.ObjectUtils;
24 import dev.metaschema.databind.IBindingContext;
25 import dev.metaschema.databind.io.ModelDetector.Result;
26 import edu.umd.cs.findbugs.annotations.NonNull;
27
28
29
30
31 @SuppressWarnings("PMD.CouplingBetweenObjects")
32 public class DefaultBoundLoader
33 extends AbstractResourceResolver
34 implements IBoundLoader {
35
36
37
38 public static final int LOOK_AHEAD_BYTES = 32_768;
39
40
41
42
43
44
45
46 private FormatDetector formatDetector;
47
48 private ModelDetector modelDetector;
49
50 @NonNull
51 private final IBindingContext bindingContext;
52 @NonNull
53 private final IMutableConfiguration<DeserializationFeature<?>> configuration;
54
55
56
57
58
59
60
61 public DefaultBoundLoader(@NonNull IBindingContext bindingContext) {
62 this.bindingContext = bindingContext;
63 this.configuration = new DefaultConfiguration<>();
64 }
65
66 @NonNull
67 private IMutableConfiguration<DeserializationFeature<?>> getConfiguration() {
68 return configuration;
69 }
70
71 @Override
72 public boolean isFeatureEnabled(DeserializationFeature<?> feature) {
73 return getConfiguration().isFeatureEnabled(feature);
74 }
75
76 @Override
77 public Map<DeserializationFeature<?>, Object> getFeatureValues() {
78 return getConfiguration().getFeatureValues();
79 }
80
81 @Override
82 public IBoundLoader applyConfiguration(@NonNull IConfiguration<DeserializationFeature<?>> other) {
83 getConfiguration().applyConfiguration(other);
84 resetDetector();
85 return this;
86 }
87
88 @SuppressWarnings("PMD.NullAssignment")
89 private void resetDetector() {
90
91 formatDetector = null;
92 }
93
94 @Override
95 public IBoundLoader set(DeserializationFeature<?> feature, Object value) {
96 getConfiguration().set(feature, value);
97 resetDetector();
98 return this;
99 }
100
101 @Override
102 public IBindingContext getBindingContext() {
103 return bindingContext;
104 }
105
106 @Override
107 public Format detectFormat(@NonNull URI uri) throws IOException {
108 URI resourceUri = resolve(uri);
109 URL resource = resourceUri.toURL();
110
111 try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
112 return detectFormat(is, uri).getFormat();
113 }
114 }
115
116 @Override
117 public FormatDetector.Result detectFormat(InputStream is, URI resource) throws IOException {
118 return getFormatDetector().detect(is);
119 }
120
121 @NonNull
122 private FormatDetector getFormatDetector() {
123 if (formatDetector == null) {
124 formatDetector = new FormatDetector(getConfiguration());
125 }
126 assert formatDetector != null;
127 return formatDetector;
128 }
129
130 @NonNull
131 private ModelDetector getModelDetector() {
132 if (modelDetector == null) {
133 modelDetector = new ModelDetector(
134 getBindingContext(),
135 getConfiguration());
136 }
137 assert modelDetector != null;
138 return modelDetector;
139 }
140
141 @Override
142 @Owning
143 public Result detectModel(@NotOwning InputStream is, URI resource, Format format) throws IOException {
144 return getModelDetector().detect(is, resource, format);
145 }
146
147 @Override
148 public <CLASS extends IBoundObject> CLASS load(@NonNull URI uri) throws IOException {
149 URI resourceUri = resolve(uri);
150 URL resource = resourceUri.toURL();
151
152 try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
153 return load(is, uri);
154 }
155 }
156
157 @SuppressWarnings("unchecked")
158 @Override
159 @NonNull
160 public <CLASS extends IBoundObject> CLASS load(
161 @NotOwning @NonNull InputStream is,
162 @NonNull URI resource)
163 throws IOException {
164 FormatDetector.Result formatMatch = getFormatDetector().detect(is);
165 Format format = formatMatch.getFormat();
166
167 try (InputStream formatStream = formatMatch.getDataStream()) {
168 try (ModelDetector.Result modelMatch = detectModel(formatStream, resource, format)) {
169
170 IDeserializer<?> deserializer = getDeserializer(
171 modelMatch.getBoundClass(),
172 format,
173 getConfiguration());
174 try (InputStream modelStream = modelMatch.getDataStream()) {
175 return (CLASS) deserializer.deserialize(modelStream, resource);
176 }
177 }
178 }
179 }
180
181 @Override
182 public <CLASS extends IBoundObject> CLASS load(Class<CLASS> clazz, URI uri) throws IOException {
183 URI resourceUri = resolve(uri);
184 URL resource = resourceUri.toURL();
185
186 try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
187 return load(clazz, is, resourceUri);
188 }
189 }
190
191 @Override
192 public <CLASS extends IBoundObject> CLASS load(Class<CLASS> clazz, InputStream is, URI documentUri)
193 throws IOException {
194
195
196 FormatDetector.Result match = getFormatDetector().detect(is);
197 Format format = match.getFormat();
198
199 try (InputStream remainingStream = match.getDataStream()) {
200
201 return load(clazz, format, remainingStream, documentUri);
202 }
203 }
204
205 @Override
206 @NonNull
207 public <CLASS extends IBoundObject> CLASS load(
208 @NonNull Class<CLASS> clazz,
209 @NonNull Format format,
210 @NonNull InputStream is,
211 @NonNull URI documentUri) throws IOException {
212
213 IDeserializer<CLASS> deserializer = getDeserializer(clazz, format, getConfiguration());
214 return deserializer.deserialize(is, documentUri);
215 }
216
217 @Override
218 public IDocumentNodeItem loadAsNodeItem(URI uri) throws IOException {
219 URI resourceUri = resolve(uri);
220 URL resource = resourceUri.toURL();
221
222 try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
223 return loadAsNodeItem(is, resourceUri);
224 }
225 }
226
227 @NonNull
228 private IDocumentNodeItem loadAsNodeItem(@NonNull InputStream is, @NonNull URI documentUri) throws IOException {
229 FormatDetector.Result formatMatch = getFormatDetector().detect(is);
230 Format format = formatMatch.getFormat();
231
232 try (InputStream formatStream = formatMatch.getDataStream()) {
233 return loadAsNodeItem(format, formatStream, documentUri);
234 }
235 }
236
237 @Override
238 public IDocumentNodeItem loadAsNodeItem(Format format, URI uri) throws IOException {
239 URI resourceUri = resolve(uri);
240 URL resource = resourceUri.toURL();
241
242 try (InputStream is = ObjectUtils.notNull(resource.openStream())) {
243 return loadAsNodeItem(format, is, resourceUri);
244 }
245 }
246
247 @Override
248 public IDocumentNodeItem loadAsNodeItem(Format format, InputStream is, URI resource)
249 throws IOException {
250 try (ModelDetector.Result modelMatch = detectModel(is, resource, format)) {
251
252 IDeserializer<?> deserializer = getDeserializer(
253 modelMatch.getBoundClass(),
254 format,
255 getConfiguration());
256 try (InputStream modelStream = modelMatch.getDataStream()) {
257 return (IDocumentNodeItem) deserializer.deserializeToNodeItem(modelStream, resource);
258 }
259 }
260 }
261
262 @NonNull
263 private <CLASS extends IBoundObject> IDeserializer<CLASS> getDeserializer(
264 @NonNull Class<CLASS> clazz,
265 @NonNull Format format,
266 @NonNull IConfiguration<DeserializationFeature<?>> config) {
267 IDeserializer<CLASS> retval = getBindingContext().newDeserializer(format, clazz);
268 retval.applyConfiguration(config);
269 return retval;
270 }
271 }