org.jannocessor.service.render.VelocityTemplateRenderer.java Source code

Java tutorial

Introduction

Here is the source code for org.jannocessor.service.render.VelocityTemplateRenderer.java

Source

/**
 * Copyright 2011 Nikolche Mihajlovski
 *
 * This file is part of JAnnocessor.
 *
 * JAnnocessor is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JAnnocessor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JAnnocessor.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.jannocessor.service.render;

import java.io.File;
import java.io.FileReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.regex.Pattern;

import javax.inject.Inject;

import org.apache.commons.lang.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.log.LogChute;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.apache.velocity.runtime.resource.loader.FileResourceLoader;
import org.jannocessor.JannocessorException;
import org.jannocessor.collection.Power;
import org.jannocessor.model.util.Annotations;
import org.jannocessor.model.util.Classes;
import org.jannocessor.model.util.Constructors;
import org.jannocessor.model.util.Enums;
import org.jannocessor.model.util.Fields;
import org.jannocessor.model.util.Interfaces;
import org.jannocessor.model.util.Methods;
import org.jannocessor.model.util.NestedAnnotations;
import org.jannocessor.model.util.NestedClasses;
import org.jannocessor.model.util.NestedEnums;
import org.jannocessor.model.util.NestedInterfaces;
import org.jannocessor.model.util.New;
import org.jannocessor.service.api.Configurator;
import org.jannocessor.service.api.JavaRepresenter;
import org.jannocessor.service.api.TemplateRenderer;
import org.jannocessor.service.imports.ImportOrganizerImpl;
import org.jannocessor.util.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class VelocityTemplateRenderer implements TemplateRenderer, Settings, RuntimeConstants, LogChute {

    private static final String RESOURCE_LOADER_CLASS = "file.resource.loader.class";

    private Logger logger = LoggerFactory.getLogger("RENDERER");

    private final VelocityEngine engine;

    private boolean configured = false;

    private final JavaRepresenter representer;

    @Inject
    public VelocityTemplateRenderer(Configurator configurator, JavaRepresenter representer) {
        this.representer = representer;
        this.engine = new VelocityEngine();
    }

    @Override
    public void configure(String templatesPath, boolean debugMode) throws JannocessorException {
        logger.info("Configuring Velocity engine: {templates_path={}, debug={}}", templatesPath, debugMode);

        try {
            Properties velocityConfig = new Properties();

            if (templatesPath != null) {
                velocityConfig.setProperty("resource.loader", "file, classpath");

                velocityConfig.setProperty(RESOURCE_LOADER_CLASS, FileResourceLoader.class.getCanonicalName());
                velocityConfig.setProperty(FILE_RESOURCE_LOADER_PATH, templatesPath);

                velocityConfig.setProperty("classpath.resource.loader.class",
                        ClasspathResourceLoader.class.getCanonicalName());
                velocityConfig.setProperty("classpath.resource.loader.cache", "false");
            } else {
                velocityConfig.setProperty(RESOURCE_LOADER_CLASS, ClasspathResourceLoader.class.getCanonicalName());
            }

            velocityConfig.setProperty(VM_LIBRARY, StringUtils.join(VM_LIBRARY_FILES, ","));

            velocityConfig.setProperty(VM_MAX_DEPTH, "1000");
            velocityConfig.setProperty(VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL, "true");
            velocityConfig.setProperty(VM_PERM_INLINE_LOCAL, "false");
            velocityConfig.setProperty(VM_PERM_ALLOW_INLINE, "true");

            // FIXME: deprecated
            velocityConfig.setProperty(VM_CONTEXT_LOCALSCOPE, "true");

            if (debugMode) {
                velocityConfig.setProperty(VM_LIBRARY_AUTORELOAD, "true");
                velocityConfig.setProperty(FILE_RESOURCE_LOADER_CACHE, "false");
            } else {
                velocityConfig.setProperty(VM_LIBRARY_AUTORELOAD, "false");
                velocityConfig.setProperty(FILE_RESOURCE_LOADER_CACHE, "true");
            }

            engine.setProperty(RUNTIME_LOG_LOGSYSTEM, this);
            engine.init(velocityConfig);

            customize(true);

            configured = true;
        } catch (Exception e) {
            throw new JannocessorException("Exception occured while configuring the template renderer", e);
        }
    }

    private void customize(boolean verbose) {
        if (engine.resourceExists(CUSTOM_TEMPLATE)) {
            if (engine.getTemplate(CUSTOM_TEMPLATE).process()) {
                if (verbose) {
                    logger.info("Successfully processed: {}", CUSTOM_TEMPLATE);
                }
            } else {
                logger.warn("Couldn't process: {}", CUSTOM_TEMPLATE);
            }
        } else if (verbose) {
            logger.warn("The templates customization file '{}' wasn't found on classpath", CUSTOM_TEMPLATE);
        }
    }

    @Override
    public String render(String template, Map<String, Object> attributes) throws JannocessorException {
        checkWasConfigured();
        customize(false);

        VelocityContext context = createContext(attributes);

        TypeUtils typeUtils = createTypeUtils();
        context.put("types", typeUtils);

        Writer writer = new StringWriter();
        engine.evaluate(context, writer, '"' + template + '"', template);
        String renderedText = writer.toString();

        return postProcess(renderedText, typeUtils);
    }

    @Override
    public String renderFromFile(String templateFilename, Map<String, Object> attributes)
            throws JannocessorException {
        checkWasConfigured();
        customize(false);

        try {
            logger.info("Retrieving template: {}", templateFilename);

            VelocityContext context = createContext(attributes);

            TypeUtils typeUtils = createTypeUtils();
            context.put("types", typeUtils);

            Writer writer = new StringWriter();

            File file = new File(templateFilename);
            if (file.exists()) {
                Reader reader = new FileReader(file);
                engine.evaluate(context, writer, templateFilename, reader);
            } else {
                Template t = engine.getTemplate(templateFilename);
                t.merge(context, writer);
            }
            String renderedText = writer.toString();

            return postProcess(renderedText, typeUtils);
        } catch (Exception e) {
            String report = String.format("Rendering of template '%s' failed", templateFilename);
            throw new JannocessorException(report, e);
        }
    }

    @Override
    public String renderMacro(String macro, Map<String, Object> attributes, String[] params)
            throws JannocessorException {
        checkWasConfigured();
        customize(false);

        VelocityContext context = createContext(attributes);

        TypeUtils typeUtils = createTypeUtils();
        context.put("types", typeUtils);

        Writer writer = new StringWriter();
        String logTag = "\"#" + macro + '"';
        engine.invokeVelocimacro(macro, logTag, params, context, writer);
        String renderedText = writer.toString();

        return postProcess(renderedText, typeUtils);
    }

    private void checkWasConfigured() {
        if (!configured) {
            throw new IllegalStateException("The template renderer is not configured!");
        }
    }

    private TypeUtils createTypeUtils() {
        return new TypeUtils(new ImportOrganizerImpl());
    }

    private VelocityContext createContext(Map<String, Object> attributes) {
        VelocityContext context = new VelocityContext();

        addModifiersToContext(context);

        context.put("logger", logger);
        context.put("representer", representer);
        context.put("helper", new TemplateHelper(context));

        for (Entry<String, Object> entry : attributes.entrySet()) {
            context.put(entry.getKey(), entry.getValue());
        }

        VelocityEventHandler eventHandler = new VelocityEventHandler();
        eventHandler.listenToContext(context);

        return context;
    }

    private void addModifiersToContext(VelocityContext context) {
        context.put("Annotations", getStaticFields(Annotations.class));
        context.put("Classes", getStaticFields(Classes.class));
        context.put("Code", getStaticFields(New.class));
        context.put("Constructors", getStaticFields(Constructors.class));
        context.put("Enums", getStaticFields(Enums.class));
        context.put("Fields", getStaticFields(Fields.class));
        context.put("Interfaces", getStaticFields(Interfaces.class));
        context.put("Methods", getStaticFields(Methods.class));
        context.put("NestedAnnotations", getStaticFields(NestedAnnotations.class));
        context.put("NestedClasses", getStaticFields(NestedClasses.class));
        context.put("NestedEnums", getStaticFields(NestedEnums.class));
        context.put("NestedInterfaces", getStaticFields(NestedInterfaces.class));
    }

    private Map<String, Object> getStaticFields(Class<?> clazz) {
        Map<String, Object> map = Power.map();

        for (Field field : clazz.getFields()) {
            if (Modifier.isStatic(field.getModifiers())) {
                try {
                    map.put(field.getName(), field.get(null));
                } catch (Exception e) {
                    logger.error("Cannot access field: " + field.getName(), e);
                }
            }
        }

        return map;
    }

    private String replacePlaceholder(String text, String placeholder, String replacement) {
        String pattern = Pattern.quote("(!PLACEHOLDER:" + placeholder + "!)");
        return text.replaceAll(pattern, replacement);
    }

    private String postProcess(String renderedText, TypeUtils typeUtils) {
        String text = postProcessImports(renderedText, typeUtils.getTypeImports());

        return text;
    }

    private String postProcessImports(String renderedText, List<String> imports) {
        StringBuilder sb = new StringBuilder();
        for (String typeImport : imports) {
            sb.append("import ");
            sb.append(typeImport);
            sb.append(";\n");
        }
        String replacement = sb.toString();

        String text = replacePlaceholder(renderedText, "SMART_IMPORT", replacement);
        return text;
    }

    @Override
    public void init(RuntimeServices rs) throws Exception {
    }

    @Override
    public void log(int level, String message) {
        if (level < ERROR_ID) {
            logger.info(message);
        } else {
            logger.warn(message);
        }
    }

    @Override
    public void log(int level, String message, Throwable t) {
        if (level < ERROR_ID) {
            logger.info(message);
        } else {
            logger.warn(message);
        }
    }

    @Override
    public boolean isLevelEnabled(int level) {
        return level >= INFO_ID;
    }

}