/*
* Copyright 2014-2016 CyberVision, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kaaproject.kaa.avro.avrogen.compiler;
import static org.apache.commons.lang.StringUtils.join;
import org.apache.avro.Schema;
import org.apache.avro.Schema.Field;
import org.apache.avro.Schema.Type;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.kaaproject.kaa.avro.avrogen.GenerationContext;
import org.kaaproject.kaa.avro.avrogen.KaaGeneratorException;
import org.kaaproject.kaa.avro.avrogen.StyleUtils;
import org.kaaproject.kaa.avro.avrogen.TypeConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The type Compiler.
*/
public abstract class Compiler {
private static final String DIRECTION_PROP = "direction";
private static final Logger LOG = LoggerFactory.getLogger(Compiler.class);
protected final Map<Schema, GenerationContext> schemaGenerationQueue;
private final String generatedSourceName;
protected VelocityEngine engine;
protected PrintWriter headerWriter;
protected PrintWriter sourceWriter;
protected String namespacePrefix;
// list of schemas that should be skipped during generation
protected Set<Schema> generatedSchemas = new HashSet<>();
private List<Schema> schemas = new ArrayList<>();
private Compiler(String sourceName) throws KaaGeneratorException {
this.namespacePrefix = "kaa";
this.generatedSourceName = sourceName;
this.schemaGenerationQueue = new LinkedHashMap<>();
initVelocityEngine();
}
/**
* Instantiates a new Compiler.
*
* @param schema the schema that used to generate source files
* @param sourceName the name of source file
* @param hdrS the stream of header file
* @param srcS the stream of source file
*/
public Compiler(Schema schema, String sourceName, OutputStream hdrS, OutputStream srcS)
throws KaaGeneratorException {
this(sourceName);
this.schemas.add(schema);
this.headerWriter = new PrintWriter(hdrS);
this.sourceWriter = new PrintWriter(srcS);
prepareTemplates(false);
}
/**
* Instantiates a new Compiler.
*
* @param schemas list of schemas that used to generate source files
* @param sourceName name of source files
* @param hdrS stream of the header file
* @param srcS stream of the source file
*/
public Compiler(List<Schema> schemas, String sourceName, OutputStream hdrS, OutputStream srcS)
throws KaaGeneratorException {
this(sourceName);
this.schemas.addAll(schemas);
this.headerWriter = new PrintWriter(hdrS);
this.sourceWriter = new PrintWriter(srcS);
prepareTemplates(false);
}
public Compiler(List<Schema> schemas, String sourceName, OutputStream hdrS, OutputStream srcS,
Set<Schema> generatedSchemas) throws KaaGeneratorException {
this(schemas, sourceName, hdrS, srcS);
this.generatedSchemas = new HashSet<>(generatedSchemas);
}
/**
* Instantiates a new Compiler.
*
* @param schemaPath path to file that contains schema
* @param outputPath destination path for generated sources
* @param sourceName name of source files
*/
public Compiler(String schemaPath, String outputPath, String sourceName)
throws KaaGeneratorException {
this(sourceName);
try {
this.schemas.add(new Schema.Parser().parse(new File(schemaPath)));
prepareTemplates(true);
File outputDir = new File(outputPath);
outputDir.mkdirs();
String headerPath = outputPath + File.separator + generatedSourceName + ".h";
String sourcePath = outputPath + File.separator + generatedSourceName + getSourceExtension();
Files.move(new File(headerTemplateGen()).toPath(),
new File(headerPath).toPath(), StandardCopyOption.REPLACE_EXISTING);
Files.move(new File(sourceTemplateGen()).toPath(),
new File(sourcePath).toPath(), StandardCopyOption.REPLACE_EXISTING);
this.headerWriter = new PrintWriter(new BufferedWriter(new FileWriter(headerPath, true)));
this.sourceWriter = new PrintWriter(new BufferedWriter(new FileWriter(sourcePath, true)));
} catch (Exception ex) {
LOG.error("Failed to create ouput path: ", ex);
throw new KaaGeneratorException("Failed to create output path: " + ex.toString());
}
}
private void initVelocityEngine() {
engine = new VelocityEngine();
engine.addProperty("resource.loader", "class, file");
engine.addProperty("class.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
engine.addProperty("file.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.FileResourceLoader");
engine.addProperty("file.resource.loader.path", "/, .");
engine.setProperty("runtime.references.strict", true);
engine.setProperty("runtime.log.logsystem.class",
"org.apache.velocity.runtime.log.NullLogSystem");
}
protected abstract String headerTemplateGen();
protected abstract String sourceTemplateGen();
protected abstract String headerTemplate();
protected abstract String sourceTemplate();
protected abstract String getSourceExtension();
private void prepareTemplates(boolean toFile) throws KaaGeneratorException {
try {
VelocityContext context = new VelocityContext();
context.put("headerName", generatedSourceName);
StringWriter hdrWriter = new StringWriter();
engine.getTemplate(headerTemplate()).merge(context, hdrWriter);
StringWriter srcWriter = new StringWriter();
engine.getTemplate(sourceTemplate()).merge(context, srcWriter);
if (toFile) {
writeToFile(hdrWriter, srcWriter);
} else {
writeToStream(hdrWriter, srcWriter);
}
} catch (Exception ex) {
LOG.error("Failed to prepare source templates: ", ex);
throw new KaaGeneratorException("Failed to prepare source templates: " + ex.toString());
}
}
private void writeToStream(StringWriter hdrWriter, StringWriter srcWriter) {
headerWriter.write(hdrWriter.toString());
sourceWriter.write(srcWriter.toString());
}
private void writeToFile(StringWriter hdrWriter, StringWriter srcWriter) throws Exception {
FileOutputStream hdrOs = new FileOutputStream(headerTemplateGen());
hdrOs.write(hdrWriter.toString().getBytes());
hdrOs.close();
FileOutputStream srcOs = new FileOutputStream(sourceTemplateGen());
srcOs.write(srcWriter.toString().getBytes());
srcOs.close();
}
/**
* Generate source files using the schemas and write them to specified source file.
*/
public Set<Schema> generate() throws KaaGeneratorException {
try {
LOG.debug("Processing schemas: [" + join(schemas, ", ") + "]");
for (Schema schema : schemas) {
if (schema.getType() == Type.UNION) {
for (Schema s : schema.getTypes()) {
addAllSchemasToQueue(s, null);
}
} else {
addAllSchemasToQueue(schema, null);
}
}
doGenerate();
LOG.debug("Sources were successfully generated");
return schemaGenerationQueue.keySet();
} catch (Exception ex) {
LOG.error("Failed to generate C sources: ", ex);
throw new KaaGeneratorException("Failed to generate sources: " + ex.toString());
} finally {
headerWriter.close();
sourceWriter.close();
}
}
/**
* Recursively add all unique dependencies of a passed schema and the one to generation queue,
* that used to generate sources.
*/
private void addAllSchemasToQueue(Schema schema, GenerationContext context) {
GenerationContext existingContext = schemaGenerationQueue.get(schema);
if (existingContext != null) {
existingContext.updateDirection(context);
return;
}
switch (schema.getType()) {
case RECORD:
for (Field f : schema.getFields()) {
addAllSchemasToQueue(f.schema(), new GenerationContext(
schema.getName(), f.name(), schema.getProp(DIRECTION_PROP)));
}
schemaGenerationQueue.put(schema, null);
break;
case UNION:
for (Schema branchSchema : schema.getTypes()) {
addAllSchemasToQueue(branchSchema, context);
}
schemaGenerationQueue.put(schema, context);
break;
case ARRAY:
addAllSchemasToQueue(schema.getElementType(), context);
break;
case ENUM:
schemaGenerationQueue.put(schema, null);
break;
default:
break;
}
}
protected abstract void doGenerate();
protected void processRecord(Schema schema, String headerTemplate, String sourceTemplate) {
VelocityContext context = new VelocityContext();
context.put("schema", schema);
context.put("StyleUtils", StyleUtils.class);
context.put("TypeConverter", TypeConverter.class);
context.put("namespacePrefix", namespacePrefix);
StringWriter hdrWriter = new StringWriter();
engine.getTemplate(headerTemplate).merge(context, hdrWriter);
appendResult(hdrWriter.toString(), true);
StringWriter srcWriter = new StringWriter();
engine.getTemplate(sourceTemplate).merge(context, srcWriter);
appendResult(srcWriter.toString(), false);
}
protected void processEnum(Schema schema, String template) {
VelocityContext context = new VelocityContext();
List<String> symbols = schema.getEnumSymbols();
context.put("schema", schema);
context.put("symbols", symbols);
context.put("StyleUtils", StyleUtils.class);
context.put("namespacePrefix", namespacePrefix);
StringWriter writer = new StringWriter();
engine.getTemplate(template).merge(context, writer);
appendResult(writer.toString(), true);
}
protected void appendResult(String str, boolean toHeader) {
if (toHeader) {
headerWriter.write(str);
} else {
sourceWriter.write(str);
}
}
public void setNamespacePrefix(String namespacePrefix) {
this.namespacePrefix = namespacePrefix;
}
}