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

Java tutorial

Introduction

Here is the source code for org.acmsl.queryj.api.AbstractTemplate.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: AbstractTemplate.java
 *
 * Author: Jose San Leandro Armendariz
 *
 * Description: Common logic for all templates.
 *
 * Date: 2013/08/15
 * Time: 08:24
 *
 */
package org.acmsl.queryj.api;

/*
 * Importing QueryJ Core classes.
 */
import org.acmsl.queryj.Literals;
import org.acmsl.queryj.QueryJCommand;
import org.acmsl.queryj.api.exceptions.CannotFindPlaceholderImplementationException;
import org.acmsl.queryj.api.exceptions.CannotFindTemplateGroupException;
import org.acmsl.queryj.api.exceptions.CannotFindTemplateInGroupException;
import org.acmsl.queryj.api.exceptions.DevelopmentModeException;
import org.acmsl.queryj.api.exceptions.InvalidTemplateException;
import org.acmsl.queryj.api.exceptions.QueryJBuildException;
import org.acmsl.queryj.api.handlers.fillhandlers.FillHandler;

/*
 * Importing QueryJ Placeholders classes.
 */
import org.acmsl.queryj.api.placeholders.FillTemplateChainFactory;

/*
 * Importing ACM-SL Commons classes.
 */
import org.acmsl.commons.logging.UniqueLogFactory;
import org.acmsl.commons.utils.ClassLoaderUtils;

/*
 * Importing ANTLR classes.
 */
import org.antlr.v4.parse.ANTLRParser;

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

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

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

/*
 * Importing StringTemplate classes.
 */
import org.stringtemplate.v4.ST;
import org.stringtemplate.v4.STErrorListener;
import org.stringtemplate.v4.STGroup;
import org.stringtemplate.v4.misc.STMessage;

/*
 * Importing JDK classes.
 */
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

/**
 * Common logic for all templates.
 * @param <C> the context type.
 * @author <a href="mailto:queryj@acm-sl.org">Jose San Leandro</a>
 * @since 3.0
 * Created: 2013/08/15 08/24
 */
@ThreadSafe
public abstract class AbstractTemplate<C extends TemplateContext>
        implements STTemplate<C>, DefaultThemeConstants, Serializable {
    /**
     * The default StringTemplate error listener.
     */
    @NotNull
    protected static final STErrorListener DEFAULT_ST_ERROR_LISTENER = new STErrorListener() {
        /**
         * {@inheritDoc}
         */
        @Override
        public void compileTimeError(@NotNull final STMessage stMessage) {
            @Nullable
            final Log log = UniqueLogFactory.getLog(AbstractQueryJTemplate.class);

            if (log != null) {
                log.error(stMessage.toString());
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void runTimeError(@NotNull final STMessage stMessage) {
            @Nullable
            final Log log = UniqueLogFactory.getLog(AbstractQueryJTemplate.class);

            if (log != null) {
                log.error(stMessage.toString());
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void IOError(@NotNull final STMessage stMessage) {
            @Nullable
            final Log log = UniqueLogFactory.getLog(AbstractQueryJTemplate.class);

            if (log != null) {
                log.error(stMessage.toString());
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void internalError(@NotNull final STMessage stMessage) {
            @Nullable
            final Log log = UniqueLogFactory.getLog(AbstractQueryJTemplate.class);

            if (log != null) {
                log.error(stMessage.toString());
            }
        }
    };

    /**
     * The template context.
     */
    private C m__TemplateContext;

    /**
     * Caches the StringTemplateGroup for each template class.
     */
    private static Map<String, ?> m__mSTCache;

    /**
     * The placeholder package.
     */
    @NotNull
    private String m__strPlaceholderPackage;

    /**
     * Whether we are in debug mode.
     */
    private boolean m__bDebugEnabled;

    /**
     * A singleton container to avoid the double-checking lock idiom.
     */
    protected static final class FinalizingThreadSingletonContainer {
        /**
         * The singleton instance.
         */
        private static final FinalizingThread THREAD = new FinalizingThread();

        /**
         * Retrieves the thread.
         * @return such information.
         */
        @NotNull
        public static FinalizingThread getInstance() {
            return THREAD;
        }
    }

    /**
     * Builds a {@code AbstractTemplate} with given context.
     * @param context the context.
     */
    protected AbstractTemplate(@NotNull final C context) {
        this(context, Literals.DEFAULT_PLACEHOLDER_PACKAGE);
    }

    /**
     * Builds a {@code AbstractTemplate} with given context.
     * @param context the context.
     * @param placeholderPackage the package of the placeholder classes.
     */
    protected AbstractTemplate(@NotNull final C context, @NotNull final String placeholderPackage) {
        this(context, placeholderPackage, false);
    }

    /**
     * Builds a {@code AbstractTemplate} with given context.
     * @param context the context.
     * @param placeholderPackage the package of the placeholder classes.
     * @param debugEnabled whether to enable debugging or not.
     */
    protected AbstractTemplate(@NotNull final C context, @NotNull final String placeholderPackage,
            final boolean debugEnabled) {
        immutableSetTemplateContext(context);
        immutableSetPlaceholderPackage(placeholderPackage);
        setSTCache(new HashMap<String, Object>());
        immutableSetDebugEnabled(debugEnabled);
        //ManagementFactory.getRuntimeMXBean(). getInputArguments().toString().contains(XRUNJDWP_TRANSPORT));
    }

    /**
     * Specifies the {@link QueryJTemplateContext}.
     * @param context the context.
     */
    private void immutableSetTemplateContext(@NotNull final C context) {
        m__TemplateContext = context;
    }

    /**
     * Specifies the {@link QueryJTemplateContext}.
     * @param context the context.
     */
    @SuppressWarnings("unused")
    protected void setTemplateContext(@NotNull final C context) {
        immutableSetTemplateContext(context);
    }

    /**
     * Retrieves the {@link QueryJTemplateContext context}.
     * @return such context.
     */
    @NotNull
    @Override
    public C getTemplateContext() {
        return m__TemplateContext;
    }

    /**
     * Specifies the placeholder package.
     * @param pkg such package.
     */
    protected final void immutableSetPlaceholderPackage(@NotNull final String pkg) {
        this.m__strPlaceholderPackage = pkg;
    }

    /**
     * Specifies the placeholder package.
     * @param pkg such package.
     */
    @SuppressWarnings("unused")
    protected void setPlaceholderPackage(@NotNull final String pkg) {
        immutableSetPlaceholderPackage(pkg);
    }

    /**
     * Retrieves the placeholder package.
     * @return such package.
     */
    @NotNull
    protected String getPlaceholderPackage() {
        return this.m__strPlaceholderPackage;
    }

    /**
     * Specifies the ST cache.
     * @param map the map to use as cache.
     */
    protected static void setSTCache(@NotNull final Map<String, ?> map) {
        m__mSTCache = map;
    }

    /**
     * Retrieves the ST cache.
     * @return the map being used as cache.
     */
    @NotNull
    protected static Map<String, ?> getSTCache() {
        return m__mSTCache;
    }

    /**
     * Specifies the debug mode.
     * @param mode such mode.
     */
    protected final void immutableSetDebugEnabled(final boolean mode) {
        this.m__bDebugEnabled = mode;
    }

    /**
     * Specifies whether debugging is enabled for this template.
     * @param flag the desired behavior.
     */
    @SuppressWarnings("unused")
    public void setDebugEnabled(final boolean flag) {
        immutableSetDebugEnabled(flag);
    }

    /**
     * Whether debugging is enabled for this template.
     * @return {@code true} in such case.
     */
    public boolean isDebugEnabled() {
        return m__bDebugEnabled;
    }

    /**
     * Retrieves the string template group.
     * @param path the path.
     * @param lookupPaths the lookup paths.
     * @return such instance.
     */
    @Nullable
    @SuppressWarnings("unchecked")
    protected STGroup retrieveGroup(@NotNull final String path, @NotNull final List<String> lookupPaths) {
        return retrieveGroup(path, lookupPaths, Charset.defaultCharset(), (Map<String, STGroup>) getSTCache(),
                isDebugEnabled());
    }

    /**
     * Retrieves the string template group.
     * @param path the path.
     * @param lookupPaths the lookup paths.
     * @param charset the charset.
     * @param cache the ST cache.
     * @param debugMode if we're debugging.
     * @return such instance.
     */
    @SuppressWarnings("unchecked")
    @Nullable
    protected STGroup retrieveGroup(@NotNull final String path, @NotNull final List<String> lookupPaths,
            @NotNull final Charset charset, @NotNull final Map<String, STGroup> cache, final boolean debugMode) {
        @Nullable
        final STGroup result;

        @Nullable
        final STGroup cached;

        @Nullable
        final String t_Key;

        if (!debugMode) {
            t_Key = buildSTGroupKey(path);
            cached = cache.get(t_Key);
        } else {
            t_Key = null;
            cached = null;
        }

        if ((debugMode) || (cached == null)) {
            result = retrieveGroup(path, lookupPaths, retrieveStErrorListener(), charset, debugMode,
                    STUtils.getInstance());

            if ((!debugMode) && (result != null)) {
                cache.put(t_Key, result);
            }
        } else {
            result = cached;
        }

        return result;
    }

    /**
     * Retrieves the StringTemplate error listener.
     * @return such instance.
     */
    @NotNull
    protected STErrorListener retrieveStErrorListener() {
        return DEFAULT_ST_ERROR_LISTENER;
    }

    /**
     * Builds a key to store the ST group associated to
     * given path and theme.
     * @param path the ST path.
     * @return such key.
     */
    @NotNull
    protected final String buildSTGroupKey(@NotNull final String path) {
        return ".:\\AbstractQueryJTemplate//STCACHE//" + path + "//";
    }

    /**
     * Retrieves the string template group.
     * @param path the path.
     * @param lookupPaths the lookup paths.
     * @param errorListener the {@link STErrorListener} instance.
     * @param charset the charset.
     * @param debugMode whether we're debugging or not.
     * @param stUtils the {@link STUtils} instance.
     * @return such instance.
     */
    @Nullable
    protected STGroup retrieveGroup(@NotNull final String path, @NotNull final List<String> lookupPaths,
            @NotNull final STErrorListener errorListener, @NotNull final Charset charset, final boolean debugMode,
            @NotNull final STUtils stUtils) {
        return stUtils.retrieveGroup(path, lookupPaths, errorListener, charset, debugMode);
    }

    /**
     * Configures given {@link org.stringtemplate.v4.ST} instance.
     * @param stringTemplate such template.
     */
    @SuppressWarnings("unused")
    protected void configure(@NotNull final ST stringTemplate) {
        //stringTemplate.setPassThroughAttributes(true);
        //stringTemplate.setLintMode(true);
        //stringTemplate.setErrorListener(retrieveStErrorListener());
    }

    /**
     * Retrieves the template in given group.
     * @param group the StringTemplate group.
     * @return the template.
     */
    @Nullable
    protected ST retrieveTemplate(@Nullable final STGroup group) {
        @Nullable
        ST result = null;

        if (group != null) {
            result = group.getInstanceOf(TEMPLATE_NAME);
        }

        return result;
    }

    /**
     * Logs a custom header.
     * @param header the header.
     */
    protected void logHeader(@Nullable final String header) {
        @Nullable
        final Log t_Log = UniqueLogFactory.getLog(AbstractQueryJTemplate.class);

        if (t_Log != null) {
            t_Log.info(header);
        }
    }

    /**
     * Builds the header for logging purposes.
     * @return such header.
     */
    @NotNull
    protected String buildHeader() {
        return Literals.GENERATING + getClass().getName() + ".";
    }

    /**
     * Prints a log message displaying ClassLoader issues related
     * to ANTLR.jar and StringTemplate.jar.
     */
    @SuppressWarnings("unused")
    protected synchronized void traceClassLoaders() {
        @NotNull
        final FinalizingThread t_FinalizingThread = FinalizingThreadSingletonContainer.getInstance();

        if (t_FinalizingThread.isNew()) {
            @Nullable
            final Runtime t_Runtime = Runtime.getRuntime();

            if (t_Runtime != null) {
                t_Runtime.removeShutdownHook(t_FinalizingThread);
                t_Runtime.addShutdownHook(t_FinalizingThread);
            }
        }
    }

    /**
     * Cleans up the thread to trace class loaders on shutdown.
     */
    @SuppressWarnings("unused")
    protected void cleanUpClassLoaderTracing() {
        @NotNull
        final FinalizingThread t_FinalizingThread = FinalizingThreadSingletonContainer.getInstance();

        @Nullable
        final Runtime t_Runtime = Runtime.getRuntime();

        if (t_Runtime != null) {
            t_Runtime.removeShutdownHook(t_FinalizingThread);
        }
    }

    /**
     * Thread to provide some information about ANTLR class loaders,
     * since it's likely to have triggered the VM shutdown.
     */
    protected static class FinalizingThread extends Thread {
        /**
         * Whether the thread is new or not.
         */
        private boolean m__bNew = true;

        /**
         * Retrieves whether this thread has just been created or not.
         * @return such information.
         */
        @SuppressWarnings("unused")
        public boolean isNew() {
            final boolean result = m__bNew;

            m__bNew = false;

            return result;
        }

        /**
         * Runs the thread.
         */
        @Override
        public void run() {
            traceANTLRClassLoadingIssues();
        }

        /**
         * {@inheritDoc}
         */
        @NotNull
        @Override
        public String toString() {
            return "{ \"new\": " + m__bNew + ", \"class\": \"" + FinalizingThread.class.getSimpleName() + "\""
                    + ", \"package\": \"" + FinalizingThread.class.getPackage() + "\" }";
        }
    }

    /**
     * Prints a log message displaying ClassLoader issues related
     * to ANTLR.jar and StringTemplate.jar.
     */
    protected static void traceANTLRClassLoadingIssues() {
        // CharScanner; panic: ClassNotFoundException:
        // org.antlr.stringtemplate.language.ChunkToken
        // can happen if ANTLR gets loaded by a ClassLoader
        // with no reference to StringTemplate classes.
        @Nullable
        final Log t_Log = UniqueLogFactory.getLog(AbstractQueryJTemplate.class);

        @NotNull
        final ClassLoaderUtils t_ClassLoaderUtils = ClassLoaderUtils.getInstance();

        if (t_Log != null) {
            @NotNull
            final StringBuilder t_sbMessage = new StringBuilder();

            @Nullable
            final String t_strAntlrLocation = t_ClassLoaderUtils.findLocation(ANTLRParser.class);

            @Nullable
            final String t_strStringTemplateLocation = t_ClassLoaderUtils.findLocation(ST.class);

            t_sbMessage.append("A fatal error in StringTemplate-based generation "
                    + "has stopped QueryJ build process.\n" + "If you see error messages from StringTemplate, "
                    + "review your templates. Otherwise, if the VM " + "exited due to ClassNotFoundException or "
                    + "NoClassDefFoundException regarding ANTLR or StringTemplate "
                    + "classes (i.e. ChunkToken), then " + "antlr-X.Y.jar has been loaded by a class loader "
                    + "with no idea of where StringTemplate classes "
                    + "are, and therefore ANTLR's reflection-based "
                    + "instantiation fails. Check your classpath or the way "
                    + "it's defined by Ant or any other tool you might be " + "using. ");
            final boolean t_bAntlrLocationFound = ((t_strAntlrLocation != null)
                    && (t_strAntlrLocation.trim().length() > 0));
            final boolean t_bStringTemplateLocationFound = ((t_strStringTemplateLocation != null)
                    && (t_strStringTemplateLocation.trim().length() > 0));

            if ((t_bAntlrLocationFound) || (t_bStringTemplateLocationFound)) {
                t_sbMessage.append("Hint: ");

                if ((t_strAntlrLocation != null) && (t_strAntlrLocation.trim().length() > 0)) {
                    t_sbMessage.append("ANTLR is loaded from ");
                    t_sbMessage.append(t_strAntlrLocation);
                }

                if (t_bStringTemplateLocationFound) {
                    if (t_bAntlrLocationFound) {
                        t_sbMessage.append(" whereas ");
                    }
                    t_sbMessage.append("StringTemplate is loaded from ");
                    t_sbMessage.append(t_strStringTemplateLocation);
                }
            }

            t_Log.fatal(t_sbMessage.toString());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Nullable
    @Override
    public ST generate(final boolean relevantOnly) throws InvalidTemplateException, DevelopmentModeException {
        return generate(getTemplateContext(), relevantOnly);
    }

    /**
     * Generates the source code.
     * @param context the {@link QueryJTemplateContext} instance.
     * @param relevantOnly whether to include only relevant placeholders.
     * @return such output.
     * @throws InvalidTemplateException if the template is invalid.
     * @throws DevelopmentModeException if we're running in development mode.
     */
    @Nullable
    protected ST generate(@NotNull final C context, final boolean relevantOnly)
            throws InvalidTemplateException, DevelopmentModeException {
        @Nullable
        final ST result;

        if (!relevantOnly) {
            logHeader(buildHeader());
        }

        //traceClassLoaders();

        result = generateOutput(context, relevantOnly);

        //cleanUpClassLoaderTracing();

        return result;
    }

    /**
     * Retrieves the source code generated by this template.
     * @param context the context.
     * @param relevantOnly whether to include only relevant placeholders.
     * @return such code.
     * @throws InvalidTemplateException if the template is invalid.
     * @throws DevelopmentModeException if we're running in development mode.
     */
    @Nullable
    protected ST generateOutput(@NotNull final C context, final boolean relevantOnly)
            throws InvalidTemplateException, DevelopmentModeException {
        @Nullable
        final ST result;

        @Nullable
        Throwable t_ExceptionToWrap = null;

        @Nullable
        final STGroup t_Group = retrieveGroup();

        if (t_Group == null) {
            throw new CannotFindTemplateGroupException(getTemplateName());
        } else {
            result = retrieveTemplate(t_Group);

            if (result != null) {
                try {
                    @NotNull
                    final Map<String, Object> placeHolders = new HashMap<>();

                    @NotNull
                    final List<FillTemplateChain<? extends FillHandler<?>>> fillChains = buildFillTemplateChains(
                            context);

                    for (@NotNull
                    final FillTemplateChain<? extends FillHandler<?>> chain : fillChains) {
                        @NotNull
                        final QueryJCommand command = chain.providePlaceholders(relevantOnly);

                        for (@NotNull
                        final String key : command.getKeys()) {
                            placeHolders.put(key, command.getObjectSetting(key));
                        }
                    }

                    result.add(CONTEXT, placeHolders);

                } catch (@NotNull final QueryJBuildException invalidTemplate) {
                    t_ExceptionToWrap = invalidTemplate;
                }

                if (t_ExceptionToWrap != null) {
                    throw buildInvalidTemplateException(context, result, t_ExceptionToWrap);
                }
            } else {
                throw new CannotFindTemplateInGroupException(t_Group, TEMPLATE_NAME);
            }
        }

        return result;
    }

    /**
     * Builds the correct chain.
     * @param context the context.
     * @return the specific {@link FillTemplateChain}.
     * @throws QueryJBuildException if the template chain cannot be built.
     */
    @SuppressWarnings("unchecked")
    @NotNull
    public List<FillTemplateChain<? extends FillHandler<?>>> buildFillTemplateChains(@NotNull final C context)
            throws QueryJBuildException {
        @NotNull
        final List<FillTemplateChain<? extends FillHandler<?>>> result = new ArrayList<>();

        @Nullable
        final Class<FillTemplateChainFactory<C>> factoryClass = retrieveFillTemplateChainFactoryClass(context,
                getPlaceholderPackage());

        if (factoryClass != null) {
            @Nullable
            final ServiceLoader<FillTemplateChainFactory<C>> loader = ServiceLoader.load(factoryClass);

            if (loader != null) {
                for (@NotNull
                final FillTemplateChainFactory<C> factory : loader) {
                    result.add((FillTemplateChain<? extends FillHandler<?>>) factory.createFillChain(context));
                }
            } else {
                throw new CannotFindPlaceholderImplementationException(factoryClass);
            }
        } else {
            throw new CannotFindPlaceholderImplementationException(context.getClass().getName());
        }

        return result;
    }

    /**
     * Retrieves the placeholder chain provider.
     * @param context the context.
     * @param placeholderPackage the placeholder package.
     * @return the {@link org.acmsl.queryj.tools.PlaceholderChainProvider} class.
     * @throws QueryJBuildException if the factory class is invalid.
     */
    @Nullable
    @SuppressWarnings("unchecked")
    protected Class<FillTemplateChainFactory<C>> retrieveFillTemplateChainFactoryClass(@NotNull final C context,
            @NotNull final String placeholderPackage) throws QueryJBuildException {
        @Nullable
        Class<FillTemplateChainFactory<C>> result = null;

        @NotNull
        final String contextName = context.getClass().getSimpleName().replaceAll("^TemplateDef", "");

        @NotNull
        final String baseName = buildFillTemplateChainFactoryClass(contextName, placeholderPackage);

        try {
            result = (Class<FillTemplateChainFactory<C>>) Class.forName(baseName);
        } catch (@NotNull final ClassNotFoundException classNotFound) {
            @Nullable
            final Log t_Log = UniqueLogFactory.getLog(AbstractQueryJTemplate.class);

            if (t_Log != null) {
                t_Log.info("Template context " + contextName + " not supported", classNotFound);
            }
        }

        return result;
    }

    /**
     * Builds the {@link FillTemplateChainFactory} class name.
     * @param contextName the context name.
     * @param placeholderPackage the placeholder package.
     * @return the class name.
     */
    @NotNull
    String buildFillTemplateChainFactoryClass(@NotNull final String contextName,
            @NotNull final String placeholderPackage) {
        @NotNull
        final String result;

        @NotNull
        String aux = contextName;

        if (aux.endsWith(Literals.CONTEXT)) {
            aux = aux.substring(0, aux.lastIndexOf(Literals.CONTEXT));
        }

        if (aux.endsWith(Literals.TEMPLATE)) {
            aux = aux.substring(0, aux.lastIndexOf(Literals.TEMPLATE));
        }

        if (!aux.endsWith(Literals.FILL_TEMPLATE_CHAIN_FACTORY)) {
            aux = aux + Literals.FILL_TEMPLATE_CHAIN_FACTORY;
        }

        result = placeholderPackage + "." + aux;

        return result;
    }

    /**
     * Builds a context-specific exception.
     * @param context the context.
     * @param template the {@link ST} instance.
     * @param actualException the actual exception.
     * @return the specific {@link InvalidTemplateException} for the template.
     */
    @NotNull
    public abstract InvalidTemplateException buildInvalidTemplateException(@NotNull final C context,
            @NotNull final ST template, @NotNull final Throwable actualException);

    /**
     * {@inheritDoc}
     */
    @NotNull
    @Override
    public String toString() {
        return "{ \"placeholderPackage\": \"" + this.m__strPlaceholderPackage + '"' + ", \"debug\": "
                + this.m__bDebugEnabled + ", \"class\": \"AbstractTemplate" + '"' + ", \"templateContext\": "
                + this.m__TemplateContext + ", \"package\": \"org.acmsl.queryj.api\" }";
    }
}