com.jaxio.celerio.template.PreviousEngine.java Source code

Java tutorial

Introduction

Here is the source code for com.jaxio.celerio.template.PreviousEngine.java

Source

/*
 * Copyright 2015 JAXIO http://www.jaxio.com
 *
 * 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 com.jaxio.celerio.template;

import com.jaxio.celerio.Config;
import com.jaxio.celerio.convention.WellKnownFolder;
import com.jaxio.celerio.output.EclipseCodeFormatter;
import com.jaxio.celerio.output.OutputResult;
import com.jaxio.celerio.output.SourceFile;
import com.jaxio.celerio.output.XmlCodeFormatter;
import com.jaxio.celerio.template.pack.Template;
import com.jaxio.celerio.template.pack.TemplatePack;
import com.jaxio.celerio.util.IOUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import java.io.File;
import java.util.Map;

import static java.io.File.separatorChar;
import static java.util.regex.Pattern.*;
import static org.apache.commons.io.FilenameUtils.normalize;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.substringBeforeLast;
import static org.apache.velocity.util.StringUtils.normalizePath;

// -----------
// IMPLEMENTATION NOTE: 
//
// Do not get confused!
// userDomainPath | generatedDomainPath correspond for example to src/main/java | src/main/generated-java
// while userSource | generatedSource correspond to baseDir | outputDirectory
// 
// Files are generated in the path: outputDirectory/(userDomainPath or generatedDomainPath)
// But by default outputDirectory is equals to baseDir.
//
// In case outputDirectory and baseDir are different
// having generatedDomainPath different from userDomainPath is not needed as generated code is already clearly separated.
// That's why in such case we set generatedDomainPath to the same value as userDomainPath (see ProjectFactory code).
// -----------

@Service
@Slf4j
public class PreviousEngine {
    private final static String DYNA_IMPORTS_TAG = "__celerio_dyna_imports__";

    private final static String BASE_SUFFIX = "Base";
    private final static String BASE_SUFFIX_ = BASE_SUFFIX + "_";

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private EclipseCodeFormatter eclipseCodeFormatter;

    @Autowired
    private XmlCodeFormatter xmlCodeFormatter;

    @Autowired
    private IOUtil ioUtil;

    @Autowired
    private ContentWriter contentWriter;

    @Autowired
    private Config config;
    @Autowired
    private VelocityGenerator velocityGenerator;
    @Getter
    private String currentFullFilename = "";
    private String currentClass = "";
    private String currentRootClass = "";
    private String currentRootCast = "";

    @Getter
    private boolean currentEnableDynamicImport;
    private OutputResult outputResult;
    private SourceFile userSource;
    private SourceFile generatedSource;

    // ----------------------------------------------------------------------
    // Take over related (user extends the generated class + "Base")
    // ----------------------------------------------------------------------

    public String enableDynaImports() {
        currentEnableDynamicImport = true;
        return DYNA_IMPORTS_TAG;
    }

    /**
     * called within the velocity script.<br>
     * see $velocity.setJavaFilename(...)<br>
     * this allows us to generate dynamically the filename.
     *
     * @param relativePathOrPackage
     * @param filename
     * @param domain
     * @throws Exception
     */
    private String setCurrentFilename(String relativePathOrPackage, String filename, String domain)
            throws Exception {
        currentFullFilename = convertToFullFilename(relativePathOrPackage, filename, domain);
        return "";
    }

    /**
     * In your velocity template, call this method to set the path and filename of the generated file.
     * The prefix src/main/generated-java or target/.../src/main/java is added automatically.
     * If the file that already exists in the user space uses inheritance, the, it generates a XXBase class instead of XXX
     *
     * @param filePath            for example com.acme.myapp or com/acme/myapp
     * @param filename            for example for example MyClass.java or MyClassTest.java
     * @param userDomainPath      for example src/main/java or src/test/java
     * @param generatedDomainPath for example src/main/generated-java or src/test/generated-java
     * @throws IllegalArgumentException if file extension is not .java, .htm or .html
     */
    public String setCurrentFilename(String filePath, String filename, String userDomainPath,
            String generatedDomainPath) throws Exception {

        if (filename.indexOf(".java") > 0) {
            String comment;

            if (filename.endsWith(".java")) {
                ImportsContext.setCurrentImportsHolder(new ImportsHolder(filePath));
            }

            String userJavaFilename = convertToFullFilename(filePath, filename, userDomainPath);
            String generatedJavaFilename = convertToFullFilename(filePath, filename, generatedDomainPath);

            File userJavaFile = null;
            boolean metaModelFileAndAlreadyExistsInGenerated = false;

            if (filename.endsWith("_.java") && generatedSource.fileExists(generatedJavaFilename)) {
                metaModelFileAndAlreadyExistsInGenerated = true;
            }

            if (userSource.fileExists(userJavaFilename)) {
                userJavaFile = new File(userSource.getFullPath(userJavaFilename));
            } else if (metaModelFileAndAlreadyExistsInGenerated) {
                // Entity meta model special case. Here is how it works:
                // 1- Entity.p.vm.java handle the meta model take over if needed (creates "...class Xxx_ extends XxxBase_ .."
                // 2- EntityMeta.p.vm.java is executed, it detects the above take over and then generate the XxxBase_ ...
                // The trick is that during 1-, the file is created under the generatedSource folder, whereas generally
                // take over happens under the userSource... but we don't want that for the metamodel as it would pollute the
                // user source base and also because the take over is generated.
                userJavaFile = new File(generatedSource.getFullPath(generatedJavaFilename));
            }

            if (userJavaFile != null) {
                String userFileContent = ioUtil.fileToString(userJavaFile);
                String baseClassName = convertFileNameToBaseClassName(filename);

                if (javaFileExtendsClass(userFileContent, baseClassName)) {
                    currentClass = baseClassName;
                    currentRootClass = convertFileNameToClassName(filename);
                    currentRootCast = "(" + currentRootClass + ") ";
                    ImportsContext.setIsExtendedByUser(true);
                    comment = setCurrentFilename(filePath, currentClass + ".java", generatedDomainPath);

                    if (log.isInfoEnabled()) {
                        packInfo("TAKE OVER detected, will generate base class: " + currentClass);
                    }
                } else if (metaModelFileAndAlreadyExistsInGenerated) {
                    comment = setCurrentFilename(filePath, filename, generatedDomainPath);
                    currentClass = convertFileNameToClassName(filename);
                    currentRootClass = currentClass;
                    currentRootCast = "";
                    ImportsContext.setIsExtendedByUser(false);
                } else {
                    // HACK: place it in userDomainPath so it gets detected as a collision by processFile in case baseDir equals outputDirectory
                    comment = setCurrentFilename(filePath, filename, userDomainPath);
                    currentClass = convertFileNameToClassName(filename);
                    currentRootClass = currentClass;
                    currentRootCast = "";
                    ImportsContext.setIsExtendedByUser(false);
                }

                // Move old generated file (when present), as it breaks compilation.
                // Such move cases are not handled in the contentWriter.processFile and must be therefore handled here
                // NOTE: do not try to optimize this if/else check, it would be error prone and not understandable (thanks :-))
                boolean moveOldGeneratedFile = false;
                boolean sameSubPath = normalizePath(userDomainPath).equals(normalizePath(generatedDomainPath));

                if (ImportsContext.isExtendedByUser()) {
                    // We generate XBase.java, existing file could be X.java
                    if (outputResult.sameDirectory()) {
                        if (!sameSubPath) {
                            if (!metaModelFileAndAlreadyExistsInGenerated) {
                                // move generatedDomainPath/X.java as the user has created his own X.java in the userDomainPath
                                moveOldGeneratedFile = true;
                            }
                        } // else: we keep the file as it is the user's one!
                    } else {
                        // no risk as all is clearly separated, except for automatic metamodel takeover which occurs in the generatedSource.
                        // we clearly do not want to move the file that we automatically created.
                        if (!metaModelFileAndAlreadyExistsInGenerated) {
                            moveOldGeneratedFile = true;
                        }
                    }
                } else {
                    if (outputResult.sameDirectory()) {
                        if (!sameSubPath) {
                            if (!metaModelFileAndAlreadyExistsInGenerated) {
                                moveOldGeneratedFile = true;
                            }
                        } // else: we keep the file as it is the user's one!
                    } // else: such case is taken into account in contentWriter.processFile
                }

                if (moveOldGeneratedFile) {
                    String oldGeneratedJavaFilePath = convertToFullFilename(filePath, filename,
                            generatedDomainPath);
                    if (generatedSource.fileExists(oldGeneratedJavaFilePath)) {
                        ioUtil.forceMove(new File(generatedSource.getFullPath(oldGeneratedJavaFilePath)),
                                new File(userSource.getFullPath(
                                        outputResult.getCollisionName(oldGeneratedJavaFilePath) + ".old")));
                    }
                }

                return comment;
            } else {
                // does not exist, we can generate it as is
                comment = setCurrentFilename(filePath, filename, generatedDomainPath);
                currentClass = convertFileNameToClassName(filename);
                currentRootClass = currentClass;
                currentRootCast = "";
                ImportsContext.setIsExtendedByUser(false);
            }

            return comment;
        } else if (generatedDomainPath.equals(WellKnownFolder.FLOWS.getGeneratedFolder())) {
            String comment = setCurrentFilename(filePath, filename, generatedDomainPath);
            return comment;
        } else {
            String comment = setCurrentFilename(filePath, filename, userDomainPath);
            return comment;
        }
    }

    /**
     * Java files (except test file) generated from velocity must use this method to get their class name. When the user has extended the generated class,
     * "Base" is appended to the regular class name. Otherwise, the regular class name is returned. The regular class name is deduced from the java file name.
     */
    public String getCurrentClass() {
        return currentClass;
    }

    public String getCurrentRootClass() {
        return currentRootClass;
    }

    public String getCurrentRootCast(String... generics) {
        if (isBlank(currentRootCast)) {
            return "";
        } else if (generics == null || generics.length == 0) {
            return currentRootCast;
        } else {
            String types = "";
            int i = 0;
            for (String generic : generics) {
                types += generic + (i == generics.length ? "" : ", ");
                i++;
            }
            return currentRootCast + "<" + types + ">";
        }
    }

    public String getCurrentClassWithout_() {
        if (currentClass.endsWith("_")) {
            return substringBeforeLast(currentClass, "_");
        }
        throw new IllegalStateException("Must be invoked only if you know currentClass ends with '_'");
    }

    // -------------------------------------------------------------------------
    // Template processing
    // -------------------------------------------------------------------------

    /**
     * Returns "abstract" if the current class is extended by the user, an empty string otherwise.
     */
    public String getCurrentAbstract() {
        return ImportsContext.isExtendedByUser() ? "abstract" : "";
    }

    /**
     * add Base to a given java filename it is used for transparently subclassing generated classes
     */
    private String convertFileNameToBaseClassName(String filename) {
        if (filename.endsWith("_.java")) {
            return substringBeforeLast(filename, "_.java") + BASE_SUFFIX_;
        } else {
            return substringBeforeLast(filename, ".java") + BASE_SUFFIX;
        }
    }

    // --------------------------------------------
    // private Utils
    // --------------------------------------------

    protected String convertToFullFilename(String relpath_or_package, String filename, String domain)
            throws Exception {

        if (relpath_or_package != null && ".".equals(relpath_or_package.trim())) {
            log.warn("******** BUG IN FilenameUtils ==============> "
                    + normalize(relpath_or_package.replace('.', separatorChar) + separatorChar + filename));

            throw new Exception(
                    "not a good practice, please clean up your template relative path, you may set it to empty string... :"
                            + relpath_or_package + ", " + filename);
        }

        String ret;

        if (hasLength(relpath_or_package)) {
            ret = normalize(relpath_or_package.replace('.', separatorChar) + separatorChar + filename);
        } else {
            ret = filename;
        }

        if (hasLength(domain)) {
            return normalize(domain + File.separator + ret);
        } else {
            return ret;
        }
    }

    /*
     * This method creates a file and generates the class given a template and the type of template @param templateName the velocity template @param
     * templateType the type of template (schema/table/column)
     */
    public void processDynamicFile(Map<String, Object> context, TemplatePack templatePack, Template template)
            throws Exception {
        try {
            if (!(template.getName().indexOf(".vm.") >= 0 || template.getName().endsWith(".vm"))) {
                throw new IllegalStateException("not a velocity template!: " + template.getName());
            }

            String evaluatedTemplate = null;

            try {
                evaluatedTemplate = velocityGenerator.evaluate(context, templatePack, template);
            } catch (StopFileReachedException e) {
                return;
            }

            if (currentFullFilename.endsWith(".donotgenerate")) {
                return;
            }
            if (isBlank(currentFullFilename)) {
                log.error(
                        "In " + templatePack.getName() + ":" + template.getName() + "  target filename is missing");
                return;
            }
            if (!config.getCelerio().getConfiguration().hasFilename(templatePack.getName(), currentFullFilename)) {
                packDebug(templatePack, "SKIPPING:" + currentFullFilename);
                return;
            }

            if (currentEnableDynamicImport) {
                if (ImportsContext.getCurrentImportsHolder().hasImports()) {
                    evaluatedTemplate = evaluatedTemplate.replace(DYNA_IMPORTS_TAG,
                            "\n" + ImportsContext.getCurrentImportsHolder().toJavaImportString());
                } else {
                    evaluatedTemplate = evaluatedTemplate.replace(DYNA_IMPORTS_TAG, "");
                }
            }

            if (currentFullFilename.endsWith(".java")) {
                evaluatedTemplate = eclipseCodeFormatter.format(evaluatedTemplate);
            } else if (currentFullFilename.endsWith(".xml") || currentFullFilename.endsWith(".xhtml")) {
                evaluatedTemplate = xmlCodeFormatter.format(evaluatedTemplate);
            }

            // generated content is in the string writer
            if (log.isDebugEnabled()) {
                packDebug(templatePack,
                        "processing template " + template.getName() + " (" + currentFullFilename + ")");
            }
            try {
                contentWriter.processFile(outputResult, templatePack, template, evaluatedTemplate.getBytes("UTF-8"),
                        currentFullFilename);
            } catch (Exception e) {
                log.error("In " + templatePack.getName() + ":" + template.getName() + " template, got exception "
                        + e.getMessage(), e);
            }

        } finally {
            clearDynamicFileContext();
        }
    }

    private void clearDynamicFileContext() {
        currentFullFilename = "";
        currentClass = "";
        ImportsContext.setIsExtendedByUser(false);
        ImportsContext.setCurrentImportsHolder(null);
        currentEnableDynamicImport = false;
    }

    // --------------------------------------------
    // Logging methods
    // --------------------------------------------

    /**
     * return the class name given a java file.
     */
    private String convertFileNameToClassName(String filename) {
        return StringUtils.substringBefore(filename, ".java");
    }

    private boolean javaFileExtendsClass(String javaFileContent, String extendedClassName) {
        return match(".*\\s+extends\\s+" + extendedClassName + "\\s*.*", javaFileContent);
    }

    private boolean match(String pattern, String content) {
        if (content == null) {
            return false;
        }

        return compile(pattern, MULTILINE | DOTALL | UNIX_LINES).matcher(content).matches();
    }

    protected void packDebug(TemplatePack templatePack, String message) {
        log.debug("[" + templatePack.getName() + "][" + message + "]");
    }

    // -------------------------------------------------
    // Engine Configuration
    // -------------------------------------------------

    protected void packInfo(TemplatePack templatePack, String message) {
        log.info("[" + templatePack.getName() + "][" + message + "]");
    }

    protected void packInfo(String message) {
        log.info("[" + message + "]");
    }

    protected void packWarn(TemplatePack templatePack, String message) {
        log.warn("[" + templatePack.getName() + "][" + message + "]");
    }

    public OutputResult getOutputResult() {
        return outputResult;
    }

    public void setOutputResult(OutputResult outputResult) {
        this.outputResult = outputResult;
        this.userSource = outputResult.getUserSource();
        this.generatedSource = outputResult.getGeneratedSource();
    }

    private boolean hasLength(String s) {
        return s != null && s.length() > 0;
    }

    public void stopFileGeneration() {
        throw new StopFileReachedException();
    }

    public static class StopFileReachedException extends RuntimeException {
        private static final long serialVersionUID = 1L;
    }
}