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