001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.databind.io; 007 008import org.eclipse.jdt.annotation.NotOwning; 009import org.eclipse.jdt.annotation.Owning; 010 011import java.io.IOException; 012import java.io.InputStream; 013import java.net.URI; 014import java.net.URL; 015import java.util.Map; 016 017import dev.metaschema.core.configuration.DefaultConfiguration; 018import dev.metaschema.core.configuration.IConfiguration; 019import dev.metaschema.core.configuration.IMutableConfiguration; 020import dev.metaschema.core.metapath.item.node.IDocumentNodeItem; 021import dev.metaschema.core.model.AbstractResourceResolver; 022import dev.metaschema.core.model.IBoundObject; 023import dev.metaschema.core.util.ObjectUtils; 024import dev.metaschema.databind.IBindingContext; 025import dev.metaschema.databind.io.ModelDetector.Result; 026import edu.umd.cs.findbugs.annotations.NonNull; 027 028/** 029 * A default implementation of an {@link IBoundLoader}. 030 */ 031@SuppressWarnings("PMD.CouplingBetweenObjects") 032public class DefaultBoundLoader 033 extends AbstractResourceResolver 034 implements IBoundLoader { 035 /** 036 * The number of bytes to read ahead when determining the source format. 037 */ 038 public static final int LOOK_AHEAD_BYTES = 32_768; 039 // @NonNull 040 // private static final JsonFactory JSON_FACTORY = new JsonFactory(); 041 // @NonNull 042 // private static final XmlFactory XML_FACTORY = new XmlFactory(); 043 // @NonNull 044 // private static final YAMLFactory YAML_FACTORY = new YAMLFactory(); 045 046 private FormatDetector formatDetector; 047 048 private ModelDetector modelDetector; 049 050 @NonNull 051 private final IBindingContext bindingContext; 052 @NonNull 053 private final IMutableConfiguration<DeserializationFeature<?>> configuration; 054 055 /** 056 * Construct a new loader instance, using the provided {@link IBindingContext}. 057 * 058 * @param bindingContext 059 * the Module binding context to use to load Java types 060 */ 061 public DefaultBoundLoader(@NonNull IBindingContext bindingContext) { 062 this.bindingContext = bindingContext; 063 this.configuration = new DefaultConfiguration<>(); 064 } 065 066 @NonNull 067 private IMutableConfiguration<DeserializationFeature<?>> getConfiguration() { 068 return configuration; 069 } 070 071 @Override 072 public boolean isFeatureEnabled(DeserializationFeature<?> feature) { 073 return getConfiguration().isFeatureEnabled(feature); 074 } 075 076 @Override 077 public Map<DeserializationFeature<?>, Object> getFeatureValues() { 078 return getConfiguration().getFeatureValues(); 079 } 080 081 @Override 082 public IBoundLoader applyConfiguration(@NonNull IConfiguration<DeserializationFeature<?>> other) { 083 getConfiguration().applyConfiguration(other); 084 resetDetector(); 085 return this; 086 } 087 088 @SuppressWarnings("PMD.NullAssignment") 089 private void resetDetector() { 090 // reset the detector 091 formatDetector = null; 092 } 093 094 @Override 095 public IBoundLoader set(DeserializationFeature<?> feature, Object value) { 096 getConfiguration().set(feature, value); 097 resetDetector(); 098 return this; 099 } 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 // we cannot close this stream, since it will cause the underlying stream to be 195 // closed 196 FormatDetector.Result match = getFormatDetector().detect(is); 197 Format format = match.getFormat(); 198 199 try (InputStream remainingStream = match.getDataStream()) { 200 // is autoclosing ok? 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}