/**
* Copyright 2012 Red Hat, Inc. and/or its affiliates.
*
* 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.kie.workbench.common.services.datamodeller.codegen;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.kie.workbench.common.services.datamodeller.core.Annotation;
import org.kie.workbench.common.services.datamodeller.core.DataObject;
import org.kie.workbench.common.services.datamodeller.core.HasAnnotations;
import org.kie.workbench.common.services.datamodeller.core.JavaClass;
import org.kie.workbench.common.services.datamodeller.core.Method;
import org.kie.workbench.common.services.datamodeller.core.ObjectProperty;
import org.kie.workbench.common.services.datamodeller.util.DataModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;
import java.util.Properties;
/**
* Simple velocity based code adf engine.
*/
//TODO Eventually, weed out everything that is no longer needed (unused templates, listener, unused context attributes - currentDataObject for example, or context alltoghether, ... )
public class GenerationEngine {
private static final Logger logger = LoggerFactory.getLogger(GenerationEngine.class);
private static GenerationEngine singleton;
private VelocityEngine velocityEngine = new VelocityEngine();
private static boolean inited = false;
public static GenerationEngine getInstance() throws Exception {
if (singleton == null) {
singleton = new GenerationEngine();
singleton.init();
}
return singleton;
}
/**
* Initializes the code adf engine
*/
private void init() throws Exception {
if (!inited) {
// Init velocity engine
Properties properties = new Properties();
properties.setProperty("resource.loader", "class");
properties.setProperty("class.resource.loader.description", "Velocity Classpath Resource Loader");
properties.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
//TODO REVIEW THIS
properties.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.JdkLogChute");
// init velocity engine
velocityEngine.init(properties);
inited = true;
}
}
/**
* Runs the code adf.
*
* @param generationContext Context information for the adf.
*
* @throws Exception
*
*/
public void generate(GenerationContext generationContext) throws Exception {
VelocityContext context = buildContext(generationContext);
String templatesPath = generationContext.getTemplatesPath();
String initialTemplate = generationContext.getInitialTemplate();
if (logger.isDebugEnabled()) {
logger.debug("Starting code adf with templatesPath: " + templatesPath + ", initialTemplate: " + initialTemplate);
}
// Always start by the initial template
String templatePath = getFullVelocityPath(templatesPath, initialTemplate);
if (logger.isDebugEnabled()) logger.debug("Initial templatePath: " + templatePath);
StringWriter writer = new StringWriter();
Template t = velocityEngine.getTemplate(templatePath);
t.merge(context, writer);
}
/**
* Creates a VelocityContext and inject common variables into it.
*
* @param generationContext Generation context provided by user.
*
* @return A properly initialized VelocityContext.
*/
private VelocityContext buildContext(GenerationContext generationContext) {
VelocityContext context = new VelocityContext();
// Add main objects to velocity context
context.put("engine", this);
context.put("context", generationContext);
context.put("dataModel", generationContext.getDataModel());
context.put("nameTool", new GenerationTools());
generationContext.setVelocityContext(context);
return context;
}
/**
* Invoked from template files when a new asset has to be generated.
*
* @param generationContext The context currently executing.
*
* @param template The template id to use.
*
* @param filePath The file to be generated.
*
* @throws java.io.IOException
*
*/
public void generateAsset(GenerationContext generationContext, String template, String filePath) throws Exception {
//read the template to use
String templatePath = getFullVelocityPath(generationContext.getTemplatesPath(), template);
VelocityContext context = buildContext(generationContext);
Template t = velocityEngine.getTemplate(templatePath); //obs, templates are already cached by Velocity
//generate asset content.
StringWriter writer = new StringWriter();
generationContext.setCurrentOutput(writer);
t.merge(context, writer);
if (generationContext.getOutputPath() != null) {
//generate the java file in the filesystem only if the output path was set in the adf context.
File fout = new File(generationContext.getOutputPath(), filePath);
fout.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(fout, false);
IOUtils.write(writer.toString(), fos);
}
if (generationContext.getGenerationListener() != null) {
generationContext.getGenerationListener().assetGenerated(filePath, writer.toString());
}
}
public void generateConstructors(GenerationContext generationContext, String template) throws Exception {
generateSubTemplate(generationContext, template);
}
public void generateAttribute(GenerationContext generationContext, ObjectProperty attribute, String template) throws Exception {
generateSubTemplate(generationContext, template);
}
public void generateMethod( GenerationContext generationContext, Method method, String template) throws Exception {
generateSubTemplate(generationContext, template);
}
public void generateNestedClass( GenerationContext generationContext, JavaClass nestedClass, String template) throws Exception {
generateSubTemplate(generationContext, template);
}
public void generateSetterGetter(GenerationContext generationContext, ObjectProperty attribute, String template) throws Exception {
generateSubTemplate(generationContext, template);
}
public void generateEquals(GenerationContext generationContext, String template) throws Exception {
generateSubTemplate(generationContext, template);
}
public void generateHashCode(GenerationContext generationContext, String template) throws Exception {
generateSubTemplate(generationContext, template);
}
public void generateTypeAnnotation(GenerationContext generationContext, Annotation annotation, String template) throws Exception {
generateSubTemplate(generationContext, template);
}
public void generateFieldAnnotation(GenerationContext generationContext, Annotation annotation, String template) throws Exception {
generateSubTemplate(generationContext, template);
}
public void generateSubTemplate(GenerationContext generationContext, String template) throws Exception {
//read the template to use
String templatePath = null;
try {
templatePath = getFullVelocityPath(generationContext.getTemplatesPath(), template);
Template t = velocityEngine.getTemplate(templatePath);
t.merge(generationContext.getVelocityContext(), generationContext.getCurrentOutput());
} catch (Exception e) {
logger.error("An error was produced during template adf: template: " + template + ", templatePath: " + templatePath, e);
}
}
public String generateDefaultConstructorString(GenerationContext generationContext, DataObject dataObject) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("currentDataObject", dataObject);
return generateSubTemplateString(generationContext, "java_default_constructor");
}
public String generateAllFieldsConstructorString(GenerationContext generationContext, DataObject dataObject) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("currentDataObject", dataObject);
return generateSubTemplateString(generationContext, "java_allfields_constructor");
}
public String generateKeyFieldsConstructorString(GenerationContext generationContext, DataObject dataObject) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("currentDataObject", dataObject);
return generateSubTemplateString(generationContext, "java_keyfields_constructor");
}
public String generatePositionFieldsConstructorString(GenerationContext generationContext, DataObject dataObject) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("currentDataObject", dataObject);
return generateSubTemplateString(generationContext, "java_positionfields_constructor");
}
public String generateAnnotationString(GenerationContext generationContext, Annotation annotation) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("annotation", annotation);
return generateSubTemplateString(generationContext, "java_annotation");
}
public String generateMethodString(GenerationContext generationContext, Method method, String indent) throws Exception {
return indentLines( generateMethodString( generationContext, method), indent);
}
public String generateMethodString(GenerationContext generationContext, Method method) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("attr", method);
return generateSubTemplateString(generationContext, "java_method");
}
public String generateNestedClassString(GenerationContext generationContext, JavaClass javaClass, String indent) throws Exception {
return indentLines( generateNestedClassString( generationContext, javaClass ), indent);
}
public String generateNestedClassString(GenerationContext generationContext, JavaClass javaClass) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("attr", javaClass);
return generateSubTemplateString(generationContext, "java_nested_class");
}
public String generateFieldString(GenerationContext generationContext, ObjectProperty attribute) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("attr", attribute);
return generateSubTemplateString(generationContext, "java_attribute_2");
}
public String generateFieldGetterString(GenerationContext generationContext, ObjectProperty attribute) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("attr", attribute);
return generateSubTemplateString(generationContext, "java_getter");
}
public String generateFieldSetterString(GenerationContext generationContext, ObjectProperty attribute) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("attr", attribute);
return generateSubTemplateString(generationContext, "java_setter");
}
public String generateEqualsString(GenerationContext generationContext, DataObject dataObject, String indent) throws Exception {
return indentLines( generateEqualsString( generationContext, dataObject ), indent );
}
public String generateEqualsString(GenerationContext generationContext, DataObject dataObject) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("currentDataObject", dataObject);
return generateSubTemplateString(generationContext, "java_equals2");
}
public String generateHashCodeString(GenerationContext generationContext, DataObject dataObject, String indent) throws Exception {
return indentLines( generateHashCodeString( generationContext, dataObject ), indent );
}
public String generateHashCodeString(GenerationContext generationContext, DataObject dataObject) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("currentDataObject", dataObject);
return generateSubTemplateString(generationContext, "java_hashCode2");
}
// Shortcuts
public String generateAllConstructorsString(GenerationContext generationContext, DataObject dataObject) throws Exception {
return generateAllConstructorsString( generationContext, dataObject, null );
}
/**
* Generate all constructors
*/
public String generateAllConstructorsString(GenerationContext generationContext, DataObject dataObject, String indent) throws Exception {
StringBuilder sb = new StringBuilder();
//get the sorted list of all fields, position annotated and key annotated fields. These lists will be used
//to identify collisions with client provided constructors.
List<ObjectProperty> allFields = DataModelUtils.sortByFileOrder( DataModelUtils.filterAssignableFields( dataObject ) );
List<ObjectProperty> positionFields = DataModelUtils.sortByPosition( DataModelUtils.filterPositionFields( dataObject ) );
List<ObjectProperty> keyFields = DataModelUtils.sortByFileOrder( DataModelUtils.filterKeyFields( dataObject ) );
boolean needsAllFieldsConstructor = allFields.size() > 0;
boolean needsPositionFieldsConstructor = positionFields.size() > 0 &&
!DataModelUtils.equalsByFieldName( allFields, positionFields ) &&
!DataModelUtils.equalsByFieldType( allFields, positionFields );
boolean needsKeyFieldsConstructor = keyFields.size() > 0 &&
!DataModelUtils.equalsByFieldName( allFields, keyFields ) &&
!DataModelUtils.equalsByFieldType( allFields, keyFields ) &&
!DataModelUtils.equalsByFieldName( positionFields, keyFields ) &&
!DataModelUtils.equalsByFieldType( positionFields, keyFields );
sb.append( generateDefaultConstructorString( generationContext, dataObject ) );
if ( needsAllFieldsConstructor ) {
sb.append( GenerationTools.EOL ).append( GenerationTools.EOL );
sb.append( generateAllFieldsConstructorString( generationContext, dataObject ) );
}
if ( needsPositionFieldsConstructor ) {
sb.append( GenerationTools.EOL ).append( GenerationTools.EOL );
sb.append( generatePositionFieldsConstructorString( generationContext, dataObject ) );
}
if ( needsKeyFieldsConstructor ) {
sb.append( GenerationTools.EOL ).append( GenerationTools.EOL );
sb.append( generateKeyFieldsConstructorString( generationContext, dataObject ) );
}
return indentLines( sb.toString(), indent );
}
public String generateAllAnnotationsString(GenerationContext generationContext, HasAnnotations hasAnnotations) throws Exception {
return generateAllAnnotationsString( generationContext, hasAnnotations, null );
}
/**
* Generate all annotations for a specific element (field, class, or method)
*/
public String generateAllAnnotationsString(GenerationContext generationContext, HasAnnotations hasAnnotations, String indent) throws Exception {
VelocityContext vc = buildContext(generationContext);
StringBuilder sb = new StringBuilder();
List<Annotation> annotations = ( (GenerationTools) vc.get( "nameTool" ) ).sortedAnnotations( hasAnnotations );
boolean isFirst = true;
for ( Annotation a : annotations) {
if (!isFirst) {
sb.append( GenerationTools.EOL );
}
isFirst = false;
sb.append( generateAnnotationString( generationContext, a ) );
}
return indentLines( sb.toString(), indent );
}
public String generateCompleteFieldString(GenerationContext generationContext, ObjectProperty attribute) throws Exception {
return generateCompleteFieldString( generationContext, attribute, null );
}
/**
* Generate the complete code fragment for a field (annotations + field declaration)
*/
public String generateCompleteFieldString(GenerationContext generationContext, ObjectProperty attribute, String indent) throws Exception {
StringBuilder sb = new StringBuilder();
String annotationsString = generateAllAnnotationsString( generationContext, attribute );
if (annotationsString != null && !"".endsWith( annotationsString )) {
sb.append( annotationsString );
sb.append( GenerationTools.EOL );
}
sb.append( generateFieldString( generationContext, attribute ) );
return indentLines( sb.toString(), indent );
}
public String generateFieldGetterSetterString(GenerationContext generationContext, ObjectProperty attribute) throws Exception {
return generateFieldGetterSetterString( generationContext, attribute, null );
}
/**
* Generate getter + setter for a field
*/
public String generateFieldGetterSetterString(GenerationContext generationContext, ObjectProperty attribute, String indent) throws Exception {
StringBuilder sb = new StringBuilder();
sb.append( generateFieldGetterString( generationContext, attribute ) ).append( GenerationTools.EOL).append( GenerationTools.EOL);
sb.append( generateFieldSetterString( generationContext, attribute ) );
return indentLines( sb.toString(), indent );
}
/**
* Generate the complete java class code
*/
// TODO indentation
public String generateJavaClassString(GenerationContext generationContext, DataObject dataObject) throws Exception {
VelocityContext vc = buildContext(generationContext);
vc.put("currentDataObject", dataObject);
return generateSubTemplateString(generationContext, "java_class2");
}
//TODO We could dispense with the use of templates alltogether
public String generateSubTemplateString(GenerationContext generationContext, String template) throws Exception {
StringWriter writer = new StringWriter();
// This is necessary to cover possible included sub-templates
generationContext.setCurrentOutput(writer);
//read the template to use
String templatePath = null;
try {
templatePath = getFullVelocityPath(generationContext.getTemplatesPath(), template);
Template t = velocityEngine.getTemplate(templatePath);
t.merge(generationContext.getVelocityContext(), writer);
} catch (Exception e) {
logger.error("An error was produced during template adf: template: " + template + ", templatePath: " + templatePath, e);
}
return writer.toString();
}
public static String indentLines(String source, String indent) throws Exception {
if (indent == null || "".equals( indent )) return source;
BufferedReader reader = new BufferedReader( new StringReader( source ) );
StringBuilder out = new StringBuilder( );
String line;
String lineSeparator = System.getProperty( "line.separator" );
line = reader.readLine( );
if ( line != null ) {
out.append( indent );
out.append( line );
while ( ( line = reader.readLine( ) ) != null ) {
out.append( lineSeparator );
out.append( indent );
out.append( line );
}
}
return out.toString();
}
/**
* Returns the path for a given template name.
*
* @param templatesPath Templates path location.
*
* @param template The template name.
*
* @return a full path to the given template.
*/
private String getFullVelocityPath(String templatesPath, String template) {
return "/" + templatesPath + "/" + template + ".vm";
}
}