org.acmsl.queryj.api.AbstractTemplateGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.acmsl.queryj.api.AbstractTemplateGenerator.java

Source

/*
                    QueryJ Core
    
Copyright (C) 2002-today  Jose San Leandro Armendariz
                          chous@acm-sl.org
    
This library 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 2 of the License, or any later version.
    
This library 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 this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    
Thanks to ACM S.L. for distributing this library under the GPL license.
Contact info: jose.sanleandro@acm-sl.com
    
 ******************************************************************************
 *
 * Filename: AbstractTemplateGenerator.java
 *
 * Author: Jose San Leandro Armendariz
 *
 * Description: Common logic for template generators.
 *
 * Date: 2013/08/17
 * Time: 10:26
 */
package org.acmsl.queryj.api;

/*
 * Importing QueryJ Core classes.
 */
import org.acmsl.queryj.api.exceptions.Sha256NotSupportedException;
import org.acmsl.queryj.api.exceptions.QueryJBuildException;

/*
 * Importing some ACM-SL classes.
 */
import org.acmsl.commons.logging.UniqueLogFactory;
import org.acmsl.commons.utils.io.FileUtils;

/*
 * Importing some Apache Commons-Logging classes.
 */
import org.apache.commons.logging.Log;

/*
 * Importing StringTemplate classes.
 */
import org.stringtemplate.v4.ST;

/*
 * Importing some JetBrains annotations.
 */
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/*
 * Importing checkthread.org annotations.
 */
import org.checkthread.annotations.ThreadSafe;

/*
 * Importing some JDK classes.
 */
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Common logic for template generators.
 * @param <N> the template.
 * @param <C> the template context.
 * @author <a href="mailto:chous@acm-sl.org">Jose San Leandro Armendariz</a>
 * @since 3.0
 * Created: 2013/08/17 10:26
 */
@ThreadSafe
public abstract class AbstractTemplateGenerator<N extends Template<C>, C extends TemplateContext>
        implements TemplateGenerator<N, C> {
    /**
     * String literal: "Cannot serialize template ";
     */
    protected static final String CANNOT_SERIALIZE_TEMPLATE_LITERAL = "Cannot serialize template ";

    /**
     * Whether to enable template caching.
     */
    private boolean m__bCaching = true;

    /**
     * The thread count.
     */
    private int m__iThreadCount = 1;

    /**
     * Creates an {@code AbstractTemplateGenerator} with given settings.
     * @param caching whether to support caching or not.
     * @param threadCount the number of threads to use.
     */
    protected AbstractTemplateGenerator(final boolean caching, final int threadCount) {
        immutableSetCaching(caching);
        immutableSetThreadCount(threadCount);
    }

    /**
     * Specifies whether to cache templates or not.
     * @param flag such setting.
     */
    protected final void immutableSetCaching(final boolean flag) {
        m__bCaching = flag;
    }

    /**
     * Specifies whether to cache templates or not.
     * @param flag such setting.
     */
    @SuppressWarnings("unused")
    protected void setCaching(final boolean flag) {
        immutableSetCaching(flag);
    }

    /**
     * Retrieves whether to cache templates or not.
     * @return such setting.
     */
    public boolean isCaching() {
        return m__bCaching;
    }

    /**
     * Specifies the thread count.
     * @param count such value.
     */
    protected final void immutableSetThreadCount(final int count) {
        m__iThreadCount = count;
    }

    /**
     * Specifies the thread count.
     * @param count such value.
     */
    @SuppressWarnings("unused")
    protected void setThreadCount(final int count) {
        immutableSetThreadCount(count);
    }

    /**
     * Retrieves the thread count.
     * @return such value.
     */
    @SuppressWarnings("unused")
    public int getThreadCount() {
        return m__iThreadCount;
    }

    /**
     * Writes a table template to disk.
     * @param template the table template to write.
     * @param outputDir the output folder.
     * @param rootFolder the root folder.
     * @param charset the file encoding.
     */
    @Override
    public boolean write(@NotNull final N template, @NotNull final File outputDir, @NotNull final File rootFolder,
            @NotNull final Charset charset) throws IOException, QueryJBuildException {
        return generate(template, isCaching(), template.getTemplateContext().getFileName(), outputDir, rootFolder,
                charset, FileUtils.getInstance(), UniqueLogFactory.getLog(AbstractQueryJTemplateGenerator.class));
    }

    /**
     * Performs the generation process.
     * @param template the template.
     * @param caching whether template caching is enabled.
     * @param fileName the file name.
     * @param outputDir the output folder.
     * @param rootFolder the root folder.
     * @param charset the {@link Charset} to use.
     * @param fileUtils the {@link FileUtils} instance.
     * @param log the {@link Log} instance.
     * @return whether it gets written to disk.
     * @throws IOException if the template cannot be written to disk.
     * @throws QueryJBuildException if the template cannot be generated.
     */
    protected boolean generate(@NotNull final N template, final boolean caching, @NotNull final String fileName,
            @NotNull final File outputDir, @NotNull final File rootFolder, @NotNull final Charset charset,
            @NotNull final FileUtils fileUtils, @Nullable final Log log) throws IOException, QueryJBuildException {
        boolean result = false;

        @Nullable
        final ST relevantStTemplate = template.generate(true);

        @Nullable
        final String relevantContent;

        if (relevantStTemplate != null) {
            relevantContent = relevantStTemplate.render();
        } else {
            relevantContent = null;
        }

        if (relevantContent != null) {
            @NotNull
            final String newHash = computeHash(relevantContent, charset);

            @Nullable
            final String oldHash = retrieveHash(fileName, outputDir, rootFolder, charset, fileUtils);

            if ((oldHash == null) || (!newHash.equals(oldHash))) {
                result = true;
            }

            if (result) {
                @NotNull
                final String t_strOutputFile = outputDir.getAbsolutePath() + File.separator + fileName;

                if (caching) {
                    serializeTemplate(template, getOutputDir(outputDir, rootFolder).getAbsolutePath()
                            + File.separator + "." + fileName + ".ser");
                }

                @Nullable
                final ST stTemplate = template.generate(false);

                @Nullable
                String t_strFileContents = "";

                if (stTemplate != null) {
                    try {
                        t_strFileContents = stTemplate.render();
                    } catch (@NotNull final Throwable throwable) {
                        @Nullable
                        final Log t_Log = UniqueLogFactory.getLog(AbstractQueryJTemplate.class);

                        if (t_Log != null) {
                            t_Log.error("Error in template " + template.getTemplateName(), throwable);
                        }
                        /*                    @Nullable final STTreeView debugTool =
                            new StringTemplateTreeView("Debugging " + getTemplateName(), t_Template);
                            
                        debugTool.setVisible(true);
                            
                        while (debugTool.isVisible())
                        {
                            try
                            {
                                Thread.sleep(1000);
                            }
                            catch (InterruptedException e)
                            {
                                e.printStackTrace();
                            }
                        }*/
                    }
                }

                if (!"".equals(t_strFileContents)) {
                    @NotNull
                    final File t_FinalDir = new File(t_strOutputFile).getParentFile();

                    final boolean folderCreated = t_FinalDir.mkdirs();

                    if ((!folderCreated) && (!outputDir.exists())) {
                        throw new IOException("Cannot create output dir: " + t_FinalDir);
                    } else if (t_strFileContents != null) {
                        if ((log != null) && (log.isDebugEnabled())) {
                            log.debug("Writing " + (t_strFileContents.length() * 2) + " bytes (" + charset + "): "
                                    + t_strOutputFile);
                        }
                    }

                    if (t_strFileContents != null) {
                        fileUtils.writeFile(t_strOutputFile, t_strFileContents, charset);
                    }

                    writeHash(newHash, fileName, outputDir, rootFolder, charset, fileUtils);
                } else {
                    if ((log != null) && (log.isDebugEnabled())) {
                        log.debug("Not writing " + t_strOutputFile + " since the generated content is empty");
                    }
                }
            }
        }

        return result;
    }

    /**
     * Tries to read the hash from disk.
     * @param fileName  the file name.
     * @param outputDir the output folder.
     * @param rootFolder the root folder.
     * @param charset the charset.
     * @param fileUtils the {@link org.acmsl.commons.utils.io.FileUtils} instance.
     * @return the hash, if found.
     */
    @Nullable
    protected String retrieveHash(@NotNull final String fileName, @NotNull final File outputDir,
            @NotNull final File rootFolder, @NotNull final Charset charset, @NotNull final FileUtils fileUtils) {
        @Nullable
        String result = fileUtils.readFileIfPossible(buildHashFile(fileName, outputDir, rootFolder), charset);

        if ((result != null) && (result.endsWith("\n"))) {
            result = result.substring(0, result.length() - 1);
        }

        return result;
    }

    /**
     * Builds the hash file.
     * @param fileName the file name.
     * @param outputDir the output folder.
     * @param rootFolder the root folder.
     * @return the hash file.
     */
    @NotNull
    protected File buildHashFile(@NotNull final String fileName, @NotNull final File outputDir,
            @NotNull final File rootFolder) {
        return new File(getOutputDir(outputDir, rootFolder).getAbsolutePath() + File.separator + "." + fileName
                + ".sha256");
    }

    /**
     * Serializes given template.
     * @param template the template.
     * @param outputFilePath the output file path.
     */
    protected void serializeTemplate(@NotNull final N template, @NotNull final String outputFilePath) {
        ObjectOutputStream t_osCache = null;

        try {
            @NotNull
            final File outputFile = new File(outputFilePath);
            @NotNull
            final File baseFolder = outputFile.getParentFile();

            if (!baseFolder.exists()) {
                if (!baseFolder.mkdirs()) {
                    throw new IOException(baseFolder + " does not exist and cannot be created");
                }
            }

            if (!baseFolder.canWrite()) {
                throw new IOException(baseFolder + " is not writable");
            }

            t_osCache = new ObjectOutputStream(new FileOutputStream(new File(outputFilePath)));

            t_osCache.writeObject(template);
        } catch (@NotNull final IOException cannotSerialize) {
            @Nullable
            final Log t_Log = UniqueLogFactory.getLog(AbstractQueryJTemplateGenerator.class);

            if (t_Log != null) {
                t_Log.warn(CANNOT_SERIALIZE_TEMPLATE_LITERAL + outputFilePath + " (" + cannotSerialize + ")",
                        cannotSerialize);
            }
        } finally {
            if (t_osCache != null) {
                try {
                    t_osCache.close();
                } catch (@NotNull final IOException cannotCloseCacheFile) {
                    @Nullable
                    final Log t_Log = UniqueLogFactory.getLog(AbstractQueryJTemplateGenerator.class);

                    if (t_Log != null) {
                        t_Log.warn(CANNOT_SERIALIZE_TEMPLATE_LITERAL + outputFilePath + " (" + cannotCloseCacheFile
                                + ")", cannotCloseCacheFile);
                    }
                }
            }
        }
    }

    /**
     * Retrieves the actual base folder.
     * @param outputDir the output dir.
     * @param rootFolder the root folder.
     * @return the actual folder.
     */
    @NotNull
    protected File getOutputDir(@NotNull final File outputDir, @NotNull final File rootFolder) {
        @NotNull
        File result = outputDir;

        @NotNull
        final String rootPath = rootFolder.getAbsolutePath();
        @NotNull
        final String outputPath = outputDir.getAbsolutePath();

        if (outputPath.startsWith(rootPath)) {
            result = new File(rootPath + File.separator + ".queryj" + outputPath.substring(rootPath.length()));

            if ((!result.exists()) && (!result.mkdirs())) {
                result = outputDir;
            }
        }

        return result;
    }

    /**
     * Writes the hash value to disk, to avoid unnecessary generation.
     * @param hashValue the content to write
     * @param fileName the file name.
     * @param outputDir the output dir.
     * @param rootFolder the root folder.
     * @param charset the charset.
     * @param fileUtils the {@link FileUtils} instance.
     */
    protected void writeHash(@NotNull final String hashValue, @NotNull final String fileName,
            @NotNull final File outputDir, @NotNull final File rootFolder, @NotNull final Charset charset,
            @NotNull final FileUtils fileUtils) {
        fileUtils.writeFileIfPossible(buildHashFile(fileName, outputDir, rootFolder), hashValue, charset);
    }

    /**
     * Computes the hash of given content.
     * @param relevantContent the content.
     * @param charset the charset.
     * @return the hash.
     * @throws QueryJBuildException if the hash cannot be computed.
     */
    @NotNull
    protected String computeHash(@NotNull final String relevantContent, @NotNull final Charset charset)
            throws QueryJBuildException {
        @NotNull
        final byte[] result;

        @NotNull
        final byte[] content = relevantContent.getBytes(charset);

        try {
            @NotNull
            final MessageDigest md = MessageDigest.getInstance("SHA-256");

            md.update(content, 0, content.length);

            result = md.digest();
        } catch (@NotNull final NoSuchAlgorithmException e) {
            throw new Sha256NotSupportedException(e);
        }

        return toHex(result);
    }

    /**
     * Converts given bytes to hexadecimal.                            v
     * @param buffer the buffer to convert.
     * @return the hexadecimal representation.
     */
    @NotNull
    protected String toHex(@NotNull final byte[] buffer) {
        @NotNull
        final StringBuilder result = new StringBuilder(buffer.length);

        for (final byte currentByte : buffer) {
            result.append(Integer.toHexString(0xFF & currentByte));
        }

        return result.toString();
    }

    /**
     * {@inheritDoc}
     */
    @NotNull
    @Override
    public String toString() {
        return "{ \"class\": \"" + AbstractTemplateGenerator.class.getName() + '"' + ", \"caching\": " + m__bCaching
                + ", \"threadCount\": " + m__iThreadCount + " }";
    }
}