1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.codegen;
7   
8   import static org.junit.jupiter.api.Assertions.assertAll;
9   
10  import gov.nist.secauto.metaschema.core.model.IBoundObject;
11  import gov.nist.secauto.metaschema.core.model.IModule;
12  import gov.nist.secauto.metaschema.core.model.MetaschemaException;
13  import gov.nist.secauto.metaschema.core.model.constraint.IConstraintSet;
14  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
15  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
16  import gov.nist.secauto.metaschema.databind.IBindingContext;
17  import gov.nist.secauto.metaschema.databind.codegen.config.DefaultBindingConfiguration;
18  import gov.nist.secauto.metaschema.databind.io.Format;
19  import gov.nist.secauto.metaschema.databind.io.IDeserializer;
20  
21  import org.apache.logging.log4j.LogManager;
22  import org.apache.logging.log4j.Logger;
23  
24  import java.io.IOException;
25  import java.io.Writer;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.nio.file.Paths;
29  import java.nio.file.StandardOpenOption;
30  import java.util.Collection;
31  
32  import edu.umd.cs.findbugs.annotations.NonNull;
33  import edu.umd.cs.findbugs.annotations.Nullable;
34  
35  @SuppressWarnings("PMD.AbstractClassWithoutAbstractMethod")
36  public abstract class AbstractMetaschemaTest {
37  
38    private static final Logger LOGGER = LogManager.getLogger(AbstractMetaschemaTest.class);
39    // @TempDir
40    // Path generationDir;
41    @NonNull
42    protected Path generationDir = ObjectUtils.notNull(Paths.get("target/generated-test-sources/metaschema"));
43  
44    @NonNull
45    protected static IBindingContext newBindingContext() throws IOException {
46      return newBindingContext(CollectionUtil.emptyList());
47    }
48  
49    @NonNull
50    protected static IBindingContext newBindingContext(@NonNull Collection<IConstraintSet> constraints)
51        throws IOException {
52      Path generationDir = Paths.get("target/generated-modules");
53      Files.createDirectories(generationDir);
54  
55      return IBindingContext.builder()
56          .compilePath(ObjectUtils.notNull(Files.createTempDirectory(generationDir, "modules-")))
57          .constraintSet(constraints)
58          .build();
59    }
60  
61    @NonNull
62    public Class<? extends IBoundObject> compileModule(
63        @NonNull Path moduleFile,
64        @Nullable Path bindingFile,
65        @NonNull String rootClassName,
66        @NonNull Path classDir)
67        throws IOException, ClassNotFoundException, MetaschemaException {
68      IModule module = newBindingContext().loadMetaschema(moduleFile);
69  
70      DefaultBindingConfiguration bindingConfiguration = new DefaultBindingConfiguration();
71      if (bindingFile != null && Files.exists(bindingFile) && Files.isRegularFile(bindingFile)) {
72        bindingConfiguration.load(bindingFile);
73      }
74  
75      ModuleCompilerHelper.compileModule(module, classDir, bindingConfiguration);
76  
77      // Load classes
78      return ObjectUtils.asType(ObjectUtils.notNull(ModuleCompilerHelper.newClassLoader(
79          classDir,
80          ObjectUtils.notNull(Thread.currentThread().getContextClassLoader()))
81          .loadClass(rootClassName)));
82    }
83  
84    @NonNull
85    private static <T extends IBoundObject> T read(
86        @NonNull Format format,
87        @NonNull Path file,
88        @NonNull Class<T> rootClass,
89        @NonNull IBindingContext context)
90        throws IOException {
91      IDeserializer<T> deserializer = context.newDeserializer(format, rootClass);
92      LOGGER.info("Reading content: {}", file);
93      return deserializer.deserialize(file);
94    }
95  
96    private static <T extends IBoundObject> void write(
97        @NonNull Format format,
98        @NonNull Path file,
99        @NonNull T rootObject,
100       @NonNull IBindingContext context) throws IOException {
101     @SuppressWarnings("unchecked")
102     Class<T> clazz = (Class<T>) rootObject.getClass();
103 
104     try (Writer writer = Files.newBufferedWriter(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE,
105         StandardOpenOption.TRUNCATE_EXISTING)) {
106       assert writer != null;
107       context.newSerializer(format, clazz).serialize(rootObject, writer);
108     }
109   }
110 
111   public void runTests(@NonNull String testPath, @NonNull String rootClassName, @NonNull Path classDir)
112       throws ClassNotFoundException, IOException, MetaschemaException {
113     runTests(testPath, rootClassName, classDir, null);
114   }
115 
116   public void runTests(
117       @NonNull String testPath,
118       @NonNull String rootClassName,
119       @NonNull Path classDir,
120       java.util.function.Consumer<Object> assertions)
121       throws ClassNotFoundException, IOException, MetaschemaException {
122     runTests(
123         ObjectUtils.notNull(Paths.get(String.format("src/test/resources/metaschema/%s/metaschema.xml", testPath))),
124         ObjectUtils.notNull(Paths.get(String.format("src/test/resources/metaschema/%s/binding.xml", testPath))),
125         ObjectUtils.notNull(Paths.get(String.format("src/test/resources/metaschema/%s/example.xml", testPath))),
126         rootClassName,
127         classDir,
128         assertions);
129   }
130 
131   public void runTests(
132       @NonNull Path metaschemaPath,
133       @NonNull Path bindingPath,
134       @Nullable Path examplePath,
135       @NonNull String rootClassName,
136       @NonNull Path classDir,
137       java.util.function.Consumer<Object> assertions)
138       throws ClassNotFoundException, IOException, MetaschemaException {
139 
140     Class<? extends IBoundObject> rootClass = compileModule(
141         metaschemaPath,
142         bindingPath,
143         rootClassName,
144         classDir);
145     runTests(examplePath, rootClass, assertions);
146   }
147 
148   public <T extends IBoundObject> void runTests(
149       @Nullable Path examplePath,
150       @NonNull Class<? extends T> rootClass,
151       java.util.function.Consumer<Object> assertions) throws IOException {
152 
153     if (examplePath != null && Files.exists(examplePath)) {
154       IBindingContext context = newBindingContext();
155       if (LOGGER.isInfoEnabled()) {
156         LOGGER.info("Testing XML file: {}", examplePath.toString());
157       }
158 
159       {
160 
161         T root = read(Format.XML, examplePath, rootClass, context);
162         if (LOGGER.isDebugEnabled()) {
163           LOGGER.atDebug().log("Read XML: Object: {}", root.toString());
164         }
165         if (assertions != null) {
166           assertAll("Deserialize XML", () -> {
167             assertions.accept(root);
168           });
169         }
170 
171         if (LOGGER.isDebugEnabled()) {
172           LOGGER.atDebug().log("Write XML:");
173         }
174         write(Format.XML, ObjectUtils.notNull(Paths.get("target/out.xml")), root, context);
175 
176         if (LOGGER.isDebugEnabled()) {
177           LOGGER.atDebug().log("Write JSON:");
178         }
179         write(Format.XML, ObjectUtils.notNull(Paths.get("target/out.json")), root, context);
180       }
181 
182       Object root = read(Format.XML, ObjectUtils.notNull(Paths.get("target/out.xml")), rootClass, context);
183       if (assertions != null) {
184         assertAll("Deserialize XML (roundtrip)", () -> assertions.accept(root));
185       }
186     }
187   }
188 
189 }