All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jaxio.celerio.template.PreviousEngine Maven / Gradle / Ivy

The newest version!
/*
 * 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.
* see $velocity.setJavaFilename(...)
* 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 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy