Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.imsweb.validation.translation.RuntimeGenerator Maven / Gradle / Ivy
/*
* Copyright (C) 2021 Information Management Services, Inc.
*/
package com.imsweb.validation.translation;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.imsweb.seerutils.SeerUtils;
import com.imsweb.validation.ValidationXmlUtils;
import com.imsweb.validation.entities.Rule;
import com.imsweb.validation.entities.Validator;
import com.imsweb.validation.runtime.RuntimeUtils;
public class RuntimeGenerator {
protected static final Logger _LOG = LogManager.getLogger(RuntimeGenerator.class);
protected static final Pattern _INNER_METHOD_PATTERN = Pattern.compile("^def [A-Za-z0-9_]+\\(\\) \\{$");
public void createRuntimeFiles(Validator v, TranslationConfiguration conf) throws IOException, TranslationException {
File rootSrcDir;
if (conf.getGroovySourceCodeDirectoryPath() != null)
rootSrcDir = new File(conf.getGroovySourceCodeDirectoryPath());
else
rootSrcDir = new File(conf.getWorkingDirectoryPath(), conf.getOutputDirectoryName());
if (!rootSrcDir.exists() && !rootSrcDir.mkdirs())
throw new IOException("Unable to create " + rootSrcDir.getPath());
String packageName = conf.getGroovySourceCodePackage();
if (StringUtils.isBlank(packageName))
packageName = "com.imsweb.validation.edits.translated." + conf.getTranslationPrefix().toLowerCase();
File runtimeSourceDir = new File(rootSrcDir, "src/main/groovy/" + packageName.replace(".", "/"));
if (!runtimeSourceDir.exists() && !runtimeSourceDir.mkdirs())
throw new IOException("Unable to create " + runtimeSourceDir.getPath());
SeerUtils.emptyDirectory(runtimeSourceDir);
// create logic sources files; some metafiles are too big to create one source file and have to be split (that's defined in the configuration)
for (Pair pair : createCompiledRules(v, runtimeSourceDir, packageName, conf.getGroovySourceCodeNumFiles()))
_LOG.info(" > created " + pair.getLeft().getPath() + " [" + pair.getRight() + " edits]");
// create parsed properties source file
_LOG.info(" > created " + createParsedProperties(v, runtimeSourceDir, packageName).getPath());
// create parsed lookups source file
_LOG.info(" > created " + createParsedLookups(v, runtimeSourceDir, packageName).getPath());
// create parsed context entries source file
_LOG.info(" > created " + createParsedContexts(v, runtimeSourceDir, packageName).getPath());
// create the validator XML file in resources
File resourcesDir = new File(rootSrcDir, "src/main/resources/" + getResourcePath(conf));
if (!resourcesDir.exists() && !resourcesDir.mkdirs())
throw new IOException("Unable to create " + resourcesDir.getPath());
SeerUtils.emptyDirectory(resourcesDir);
File targetXmlFile = new File(resourcesDir, conf.getTranslationPrefix().toLowerCase() + "-translated-edits.xml");
ValidationXmlUtils.writeValidatorToXml(v, targetXmlFile);
_LOG.info(" > created " + targetXmlFile.getPath());
// create "static" runtime file (this one doesn't change over time)
_LOG.info(" > created " + createStaticRuntime(v, runtimeSourceDir, packageName, conf, targetXmlFile).getPath());
}
public List> createCompiledRules(Validator v, File targetDir, String packageName, int numFiles) throws IOException, TranslationException {
List> groovyEditsFiles = new ArrayList<>();
List sortedRules = getSortedRules(v.getRules());
// some metafiles are too big to create one source file...
List> splitLists = new ArrayList<>();
if (numFiles > 1) {
int size = sortedRules.size() / numFiles;
for (int i = 0; i < numFiles; i++)
splitLists.add(sortedRules.subList(i * size, i == numFiles - 1 ? sortedRules.size() : i * size + size));
}
else
splitLists = Collections.singletonList(sortedRules);
int counter = 1;
for (List rules : splitLists) {
String className = RuntimeUtils.createCompiledRulesClassName(v.getId()) + (splitLists.size() > 1 ? counter++ : "");
File groovyEditsFile = new File(targetDir, className + ".groovy");
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(groovyEditsFile), StandardCharsets.UTF_8))) {
writer.write("package " + packageName + "\r\n");
writer.write("\r\n");
writer.write("import com.imsweb.validation.functions.MetafileContextFunctions\r\n");
writer.write("import com.imsweb.validation.runtime.CompiledRules\r\n");
writer.write("import groovy.transform.CompileStatic\r\n");
writer.write("\r\n");
writer.write("@CompileStatic\r\n");
writer.write("class " + className + " implements CompiledRules {\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public String getValidatorId() {\r\n");
writer.write(" return '" + v.getId() + "'\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public String getValidatorVersion() {\r\n");
writer.write(" return '" + v.getVersion() + "'\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public Map>> getMethodParameters() {\r\n");
writer.write(" return [\r\n");
writer.write(" 'untrimmedlines' : [Binding.class, Map.class, MetafileContextFunctions.class, List.class],\r\n");
writer.write(" 'untrimmedlines.untrimmedline' : [Binding.class, Map.class, MetafileContextFunctions.class, List.class, Map.class]\r\n");
writer.write(" ]\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
if (splitLists.size() > 1) {
writer.write(" @Override\r\n");
writer.write(" public boolean containsRuleId(String id) {\r\n");
writer.write(" return '" + rules.get(0).getId() + "' <= id && id <= '" + rules.get(rules.size() - 1).getId() + "'\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
}
for (Rule r : rules) {
List lines = Arrays.asList(r.getExpression().split("\r?\n"));
if (!hasInnerMethod(lines)) {
writer.write(" // ID: " + r.getId() + "; TAG: " + (r.getTag() == null ? "" : r.getTag()) + "; NAME: " + r.getName() + "\r\n");
writer.write(" public boolean " + RuntimeUtils.createMethodName(r.getId()));
writer.write("(Binding binding, Map context, MetafileContextFunctions functions, List> untrimmedlines, Map untrimmedline)");
writer.write(" throws Exception {\r\n");
for (String line : lines)
writer.write(" " + line.replace("Functions.", "functions.").replace("Context.", "context.") + "\r\n");
writer.write("\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
}
else
throw new TranslationException("can't pre-compile " + r.getId() + " because it has an inner function...");
}
writer.write("}\r\n");
}
groovyEditsFiles.add(Pair.of(groovyEditsFile, rules.size()));
}
return groovyEditsFiles;
}
public File createParsedProperties(Validator v, File targetDir, String packageName) throws IOException {
File groovyPropertiesFile = new File(targetDir, RuntimeUtils.createParsedPropertiesClassName(v.getId()) + ".groovy");
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(groovyPropertiesFile), StandardCharsets.UTF_8))) {
writer.write("package " + packageName + "\r\n");
writer.write("\r\n");
writer.write("import com.imsweb.validation.runtime.ParsedProperties\r\n");
writer.write("import groovy.transform.CompileStatic\r\n");
writer.write("\r\n");
writer.write("@CompileStatic\r\n");
writer.write("class " + RuntimeUtils.createParsedPropertiesClassName(v.getId()) + " implements ParsedProperties {\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public String getValidatorId() {\r\n");
writer.write(" return '" + v.getId() + "'\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public String getValidatorVersion() {\r\n");
writer.write(" return '" + v.getVersion() + "'\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
for (Rule r : getSortedRules(v.getRules())) {
List lines = Arrays.asList(r.getExpression().split("\r?\n"));
if (!hasInnerMethod(lines)) {
writer.write(" // ID: " + r.getId() + "; TAG: " + (r.getTag() == null ? "" : r.getTag()) + "; NAME: " + r.getName() + "\r\n");
writer.write(" public Set " + RuntimeUtils.createMethodName(r.getId()) + "() {\r\n");
if (r.getUsedProperties().isEmpty())
writer.write(" return new HashSet()\r\n");
else {
writer.write(" Set s = new HashSet()\r\n");
for (String s : r.getUsedProperties())
writer.write(" s.add('" + s + "')\r\n");
writer.write(" return s\r\n");
}
writer.write(" }\r\n");
writer.write("\r\n");
}
}
writer.write("}\r\n");
}
return groovyPropertiesFile;
}
public File createParsedLookups(Validator v, File targetDir, String packageName) throws IOException {
File groovyLookupsFile = new File(targetDir, RuntimeUtils.createParsedLookupsClassName(v.getId()) + ".groovy");
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(groovyLookupsFile), StandardCharsets.UTF_8))) {
writer.write("package " + packageName + "\r\n");
writer.write("\r\n");
writer.write("import com.imsweb.validation.runtime.ParsedLookups\r\n");
writer.write("import groovy.transform.CompileStatic\r\n");
writer.write("\r\n");
writer.write("@CompileStatic\r\n");
writer.write("class " + RuntimeUtils.createParsedLookupsClassName(v.getId()) + " implements ParsedLookups {\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public String getValidatorId() {\r\n");
writer.write(" return '" + v.getId() + "'\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public String getValidatorVersion() {\r\n");
writer.write(" return '" + v.getVersion() + "'\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
for (Rule r : getSortedRules(v.getRules())) {
List lines = Arrays.asList(r.getExpression().split("\r?\n"));
if (!hasInnerMethod(lines)) {
writer.write(" // ID: " + r.getId() + "; TAG: " + (r.getTag() == null ? "" : r.getTag()) + "; NAME: " + r.getName() + "\r\n");
writer.write(" public Set " + RuntimeUtils.createMethodName(r.getId()) + "() {\r\n");
if (r.getUsedLookupIds().isEmpty())
writer.write(" return new HashSet()\r\n");
else {
writer.write(" Set s = new HashSet()\r\n");
for (String s : r.getUsedLookupIds())
writer.write(" s.add('" + s + "')\r\n");
writer.write(" return s\r\n");
}
writer.write(" }\r\n");
writer.write("\r\n");
}
}
writer.write("}\r\n");
}
return groovyLookupsFile;
}
public File createParsedContexts(Validator v, File targetDir, String packageName) throws IOException {
File groovyContextsFile = new File(targetDir, RuntimeUtils.createParsedContextsClassName(v.getId()) + ".groovy");
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(groovyContextsFile), StandardCharsets.UTF_8))) {
writer.write("package " + packageName + "\r\n");
writer.write("\r\n");
writer.write("import com.imsweb.validation.runtime.ParsedContexts\r\n");
writer.write("import groovy.transform.CompileStatic\r\n");
writer.write("\r\n");
writer.write("@CompileStatic\r\n");
writer.write("class " + RuntimeUtils.createParsedContextsClassName(v.getId()) + " implements ParsedContexts {\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public String getValidatorId() {\r\n");
writer.write(" return '" + v.getId() + "'\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public String getValidatorVersion() {\r\n");
writer.write(" return '" + v.getVersion() + "'\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
for (Rule r : getSortedRules(v.getRules())) {
List lines = Arrays.asList(r.getExpression().split("\r?\n"));
if (!hasInnerMethod(lines)) {
writer.write(" // ID: " + r.getId() + "; TAG: " + (r.getTag() == null ? "" : r.getTag()) + "; NAME: " + r.getName() + "\r\n");
writer.write(" public Set " + RuntimeUtils.createMethodName(r.getId()) + "() {\r\n");
Set contexts = new HashSet<>(r.getUsedContextKeys());
contexts.remove("binding"); // obvious false positive, let's remove it...
if (contexts.isEmpty())
writer.write(" return new HashSet()\r\n");
else {
writer.write(" Set s = new HashSet()\r\n");
for (String s : contexts)
writer.write(" s.add('" + s + "')\r\n");
writer.write(" return s\r\n");
}
writer.write(" }\r\n");
writer.write("\r\n");
}
}
writer.write("}\r\n");
}
return groovyContextsFile;
}
public File createStaticRuntime(Validator v, File targetDir, String packageName, TranslationConfiguration conf, File xmlFile) throws IOException {
StringBuilder className = new StringBuilder();
for (String s : StringUtils.split(v.getId(), "-"))
className.append(StringUtils.capitalize(s));
className.append("RuntimeEdits");
File groovyRuntimeFile = new File(targetDir, className + ".groovy");
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(groovyRuntimeFile), StandardCharsets.UTF_8))) {
writer.write("package " + packageName + "\r\n");
writer.write("\r\n");
writer.write("import com.imsweb.validation.ValidationXmlUtils\r\n");
writer.write("import com.imsweb.validation.entities.Validator\r\n");
writer.write("import com.imsweb.validation.runtime.*\r\n");
writer.write("import groovy.transform.CompileStatic\r\n");
writer.write("\r\n");
writer.write("@CompileStatic\r\n");
writer.write("class " + className + " implements RuntimeEdits {\r\n");
writer.write("\r\n");
writer.write(" public static Validator loadValidator() {\r\n");
writer.write(" try {\r\n");
writer.write(" return ValidationXmlUtils.loadValidatorFromXml(getXmlUrl(), new " + className + "())\r\n");
writer.write(" }\r\n");
writer.write(" catch (IOException e) {\r\n");
writer.write(" throw new RuntimeException(\"Unable to load validator\", e)\r\n");
writer.write(" }\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
writer.write(" public static URL getXmlUrl() {\r\n");
writer.write(" return Thread.currentThread().getContextClassLoader().getResource(\"" + getResourcePath(conf) + "/" + xmlFile.getName() + "\")\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public CompiledRules getCompiledRules() {\r\n");
if (conf.getGroovySourceCodeNumFiles() == 1)
writer.write(" return new " + RuntimeUtils.createCompiledRulesClassName(v.getId()) + "()'\r\n");
else {
writer.write(" return new CompiledRulesBundle(\r\n");
for (int i = 1; i <= conf.getGroovySourceCodeNumFiles(); i++) {
writer.write(" new " + RuntimeUtils.createCompiledRulesClassName(v.getId()) + i + "()");
if (i < conf.getGroovySourceCodeNumFiles())
writer.write(",\r\n");
else
writer.write(")\r\n");
}
}
writer.write(" }\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public ParsedProperties getParsedProperties() {\r\n");
writer.write(" return new " + RuntimeUtils.createParsedPropertiesClassName(v.getId()) + "()\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public ParsedContexts getParsedContexts() {\r\n");
writer.write(" return new " + RuntimeUtils.createParsedContextsClassName(v.getId()) + "()\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
writer.write(" @Override\r\n");
writer.write(" public ParsedLookups getParsedLookups() {\r\n");
writer.write(" return new " + RuntimeUtils.createParsedLookupsClassName(v.getId()) + "()\r\n");
writer.write(" }\r\n");
writer.write("\r\n");
writer.write("}\r\n");
}
return groovyRuntimeFile;
}
protected String getResourcePath(TranslationConfiguration conf) {
String path = conf.getXmlFileSourcePath();
if (StringUtils.isBlank(path))
path = "edits/translated/" + conf.getTranslationPrefix().toLowerCase();
return path;
}
// can't pre-compile edits that have inner methods (yet)
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
protected boolean hasInnerMethod(List lines) {
for (String line : lines)
if (_INNER_METHOD_PATTERN.matcher(line).matches())
return true;
return false;
}
// helper
protected List getSortedRules(Set rules) {
List list = new ArrayList<>(rules);
list.sort(Comparator.comparing(Rule::getId));
return list;
}
}