org.openflexo.generator.Generator.java Source code

Java tutorial

Introduction

Here is the source code for org.openflexo.generator.Generator.java

Source

/*
 * (c) Copyright 2010-2011 AgileBirds
 *
 * This file is part of OpenFlexo.
 *
 * OpenFlexo 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.
 *
 * OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
 *
 */
package org.openflexo.generator;

import java.io.StringWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
import java.util.TreeMap;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.collections.BidiMap;
import org.apache.commons.collections.bidimap.DualHashBidiMap;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.RuntimeSingleton;
import org.openflexo.foundation.DataFlexoObserver;
import org.openflexo.foundation.DataModification;
import org.openflexo.foundation.FlexoModelObject;
import org.openflexo.foundation.FlexoObservable;
import org.openflexo.foundation.TargetType;
import org.openflexo.foundation.cg.CGFile;
import org.openflexo.foundation.cg.GenerationRepository;
import org.openflexo.foundation.cg.action.AbstractGCAction;
import org.openflexo.foundation.cg.generator.GeneratedCodeResult;
import org.openflexo.foundation.cg.generator.IFlexoResourceGenerator;
import org.openflexo.foundation.cg.templates.CGTemplate;
import org.openflexo.foundation.cg.templates.TemplateDeleted;
import org.openflexo.foundation.cg.templates.TemplateFileChanged;
import org.openflexo.foundation.cg.templates.TemplateFileEdited;
import org.openflexo.foundation.cg.templates.TemplateFileEditionCancelled;
import org.openflexo.foundation.cg.templates.TemplateFileRedefined;
import org.openflexo.foundation.cg.templates.TemplateFileSaved;
import org.openflexo.foundation.rm.FlexoProject;
import org.openflexo.foundation.rm.GeneratedResourceData;
import org.openflexo.foundation.rm.ResourceType;
import org.openflexo.foundation.rm.cg.CGRepositoryFileResource;
import org.openflexo.generator.exception.GenerationException;
import org.openflexo.generator.exception.TemplateNotFoundException;
import org.openflexo.generator.exception.UnexpectedExceptionOccuredException;
import org.openflexo.generator.exception.VelocityException;
import org.openflexo.localization.FlexoLocalization;
import org.openflexo.logging.FlexoLogger;
import org.openflexo.toolbox.HTMLUtils;
import org.openflexo.toolbox.JavaUtils;
import org.openflexo.toolbox.StringUtils;
import org.openflexo.toolbox.ToolBox;
import org.openflexo.velocity.FlexoVelocity;
import org.openflexo.velocity.PostVelocityParser;

public abstract class Generator<T extends FlexoModelObject, R extends GenerationRepository> extends FlexoObservable
        implements DataFlexoObserver {

    public static class Holder<T> {
        private T value;

        public T get() {
            return value;
        }

        public T getValue() {
            return get();
        }

        public void set(T value) {
            this.value = value;
        }

        public void setValue(T value) {
            set(value);
        }
    }

    private static final Logger logger = FlexoLogger.getLogger(Generator.class.getPackage().getName());

    private Vector<CGTemplate> _usedTemplates = new Vector<CGTemplate>();

    private Vector<CGRepositoryFileResource<?, ?, ? extends CGFile>> generatedResources;

    private Vector<CGRepositoryFileResource> concernedResources = null;

    private T object;

    protected AbstractProjectGenerator<R> projectGenerator;

    private Date memoryLastGenerationDate;

    private GenerationException generationException;

    protected GeneratedCodeResult generatedCode;

    private boolean isGenerating = false;

    private boolean notifyObservers = true;

    // This map is static because the velocity engine is static. To avoid this we should have 1 engine velocity per repository
    private static Map<String, Long> macroLibMap = new HashMap<String, Long>(); // <Template Relative Path, Last modified date>

    public Generator(AbstractProjectGenerator<R> projectGenerator, T object) {
        this.projectGenerator = projectGenerator;
        this.object = object;
        this.generatedResources = new Vector<CGRepositoryFileResource<?, ?, ? extends CGFile>>();
        if (projectGenerator != null) {
            projectGenerator.addToGenerators(this);
        }
    }

    public AbstractProjectGenerator<R> getProjectGenerator() {
        return projectGenerator;
    }

    public R getRepository() {
        if (projectGenerator != null) {
            return projectGenerator.getRepository();
        } else {
            if (logger.isLoggable(Level.WARNING)) {
                logger.warning("Project generator is not set.");
            }
            return null;
        }
    }

    public final T getObject() {
        return object;
    }

    public FlexoProject getProject() {
        return getProjectGenerator().getProject();
    }

    public TargetType getTarget() {
        return getProjectGenerator().getTarget();
    }

    public CGRepositoryFileResource resourceForKeyWithCGFile(ResourceType type, String resourceName) {
        CGRepositoryFileResource ret = (CGRepositoryFileResource) getProject().resourceForKey(type, resourceName);
        if (ret != null && ret.getCGFile() == null) {
            ret.delete(false);
            ret = null;
        }
        return ret;
    }

    public final String getPrefix() {
        return getProject().getPrefix();
    }

    protected VelocityContext defaultContext() {
        VelocityContext context = new VelocityContext();
        if (object != null) {
            context.put("object", object);
        }
        context.put("null", null);
        context.put("project", getProject());
        context.put("projectGenerator", getProjectGenerator());
        context.put("repository", getRepository());
        context.put("generator", this);
        context.put("quote", "\"");
        context.put("backslash", "\\");
        context.put("sharp", "#");
        context.put("dollar", "$");
        context.put("prefix", getPrefix());
        context.put("n", StringUtils.LINE_SEPARATOR);
        context.put("trueValue", Boolean.TRUE);
        context.put("falseValue", Boolean.FALSE);
        context.put("toolbox", new ToolBox());
        context.put("stringUtils", new StringUtils());
        context.put("javaUtils", new JavaUtils());
        context.put("globalVariableMap", new HashMap<String, Object>() {
            @Override
            public Object put(String key, Object value) {
                if (value == null) {
                    System.err.println("coucou");
                }
                return super.put(key, value);
            }
        });
        context.put("today", new Date());
        context.put("apacheStringUtils", org.apache.commons.lang.StringUtils.class);
        return context;
    }

    /**
     * Build or retrieve all resources (eventually just created) involved in this repository generation
     * 
     * @param repository
     * @return the list of resources (eventually just created) involved in this repository generation
     */
    private Vector<CGRepositoryFileResource> buildGeneratedResourceListForRepository(R repository) {
        Vector<CGRepositoryFileResource> returned = new Vector<CGRepositoryFileResource>();
        buildResourcesAndSetGenerators(repository, returned);
        return returned;
    }

    // public Vector<CGRepositoryFileResource> getConcernedResources(R repository)
    // {
    // if (concernedResources == null) {
    // concernedResources = refreshConcernedResources();
    // }
    // return concernedResources;
    // }

    public Vector<CGRepositoryFileResource> refreshConcernedResources() {
        return refreshConcernedResources(null);
    }

    public Vector<CGRepositoryFileResource> refreshConcernedResources(
            Vector<CGRepositoryFileResource<? extends GeneratedResourceData, IFlexoResourceGenerator, CGFile>> forResources) {
        Vector<CGRepositoryFileResource> oldConcernedResources = concernedResources;
        concernedResources = buildGeneratedResourceListForRepository(getProjectGenerator().getRepository());
        if (oldConcernedResources != null) {
            for (CGRepositoryFileResource resource : oldConcernedResources) {
                if (!concernedResources.contains(resource) && resource.getCGFile() != null) {
                    resource.getCGFile().setMarkedForDeletion(true);
                }
            }
        }
        for (CGRepositoryFileResource resource : concernedResources) {
            resource.rebuildDependancies();
        }
        for (CGRepositoryFileResource resource : concernedResources) {
            if (resource.needsGeneration() && (forResources == null || forResources.contains(resource))) {
                resource.getDependantResourcesUpToDate();
            }
        }
        return concernedResources;
    }

    public abstract void buildResourcesAndSetGenerators(R repository, Vector<CGRepositoryFileResource> resources);

    /**
     * Generate code related to this generator. If this generator may store result, setting forceGenerate flag to false will result in
     * giving the already generated code (cache scheme).
     * 
     * @param forceRegenerate
     * @throws GenerationException
     */
    public abstract void generate(boolean forceRegenerate) throws GenerationException;

    /**
     * Generate code related to this generator, using cache scheme if present Equivalent for call generate(false)
     * 
     * @throws GenerationException
     */
    public void generate() throws GenerationException {
        generate(false);
    }

    public String merge(String templateName) throws GenerationException {
        return merge(templateName, defaultContext());
    }

    private void updateVelocityMacroIfRequired() throws VelocityException, UnexpectedExceptionOccuredException {
        try {
            // the next lines ensures that the macro library will be added as a template used by this generator

            List<CGTemplate> macroTemplates = getProjectGenerator().getVelocityMacroTemplates();
            macroTemplates.addAll(getVelocityMacroTemplates());

            synchronized (RuntimeSingleton.getRuntimeServices()) {

                Map<String, Long> newMacroLibMap = new HashMap<String, Long>();
                for (CGTemplate template : macroTemplates) {
                    newMacroLibMap.put(template.getRelativePath(), template.getLastUpdate().getTime());
                    notifyTemplateRequired(template);
                }

                if (!newMacroLibMap.equals(macroLibMap)) {
                    FlexoVelocity.addToVelocimacro(getTemplateLocator(), macroTemplates.toArray(new CGTemplate[0]));
                    macroLibMap = newMacroLibMap;
                }
            }
        } catch (ResourceNotFoundException e) {
            e.printStackTrace();
            throw new VelocityException(e, getProjectGenerator());
        } catch (ParseErrorException e) {
            e.printStackTrace();
            throw new VelocityException(e, getProjectGenerator());
        } catch (Exception e) {
            e.printStackTrace();
            throw new UnexpectedExceptionOccuredException(e, getProjectGenerator());
        }
    }

    /**
     * Override this if you want to use one or more velocity macro library. <br>
     * All macro templates from the project generator and from the current generator will be added for generation. Use the project generator
     * one to include macros for all merge. <br>
     * Order is important in case of multiple macro are defined with the same name. In such case, the last one will be taken into account.
     * 
     * @return list of template to use as Macro library.
     */
    public List<CGTemplate> getVelocityMacroTemplates() {
        return new ArrayList<CGTemplate>();
    }

    public String merge(String templateRelativePath, VelocityContext velocityContext) throws GenerationException {
        StringWriter sw = new StringWriter();
        try {
            updateVelocityMacroIfRequired();
            CGTemplate template = templateWithName(templateRelativePath);

            Velocity.setApplicationAttribute("templateLocator", getTemplateLocator());
            Velocity.mergeTemplate(template.getRelativePathWithoutSetPrefix(), "UTF-8", velocityContext, sw);
            Velocity.setApplicationAttribute("templateLocator", null);

        } catch (TemplateNotFoundException e) {
            throw e;
        } catch (Exception e) {
            if (e instanceof MethodInvocationException) {
                if (((MethodInvocationException) e).getWrappedThrowable() != null) {
                    ((MethodInvocationException) e).getWrappedThrowable().printStackTrace();
                }
            }
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("Could not merge template: " + e);
                e.printStackTrace();
                Throwable t = e;
                while (t.getCause() != null) {
                    t = t.getCause();
                }
                if (!e.equals(t)) {
                    logger.fine("Originally caused by: " + t.getMessage());
                    t.printStackTrace();
                }
            }
            throw new VelocityException(e, getProjectGenerator());
        }
        return PostVelocityParser.parseAndRenderCustomTag(sw.toString());
    }

    public AbstractGCAction getAction() {
        return getProjectGenerator().getAction();
    }

    protected boolean hasProgressWindow() {
        return getAction() != null && getAction().getFlexoProgress() != null;
    }

    protected void refreshProgressWindow(String stepName, boolean localize) {
        if (hasProgressWindow()) {
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("************** refreshProgressWindow: " + stepName);
            }
            getAction().getFlexoProgress()
                    .setProgress(localize ? FlexoLocalization.localizedForKey(stepName) : stepName);
        }
    }

    protected void resetSecondaryProgressWindow(int stepNb) {
        if (hasProgressWindow()) {
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("**** resetSecondaryProgressWindow: " + stepNb);
            }
            getAction().getFlexoProgress().resetSecondaryProgress(stepNb);
        }
    }

    protected void refreshSecondaryProgressWindow(String stepName, boolean localize) {
        if (hasProgressWindow()) {
            if (logger.isLoggable(Level.FINE)) {
                logger.fine(">>>>>>>>>>>>> refreshSecondaryProgressWindow: " + stepName);
            }
            getAction().getFlexoProgress()
                    .setSecondaryProgress(localize ? FlexoLocalization.localizedForKey(stepName) : stepName);
        }
    }

    public abstract Logger getGeneratorLogger();

    public void addToGeneratedResourcesGeneratedByThisGenerator(
            CGRepositoryFileResource<?, ?, ? extends CGFile> resource) {
        if (!generatedResources.contains(resource)) {
            generatedResources.add(resource);
        }
    }

    public void removeFromGeneratedResourcesGeneratedByThisGenerator(
            CGRepositoryFileResource<?, ?, ? extends CGFile> resource) {
        generatedResources.remove(resource);
        memoryLastGenerationDate = null;
        generatedCode = null;
    }

    protected Vector<CGRepositoryFileResource<?, ?, ? extends CGFile>> getGeneratedResources() {
        return generatedResources;
    }

    public TemplateLocator getTemplateLocator() {
        return getProjectGenerator().getTemplateLocator();
    }

    public String getTemplatePath(String templateName) throws TemplateNotFoundException {
        // Legacy method for backward compatibility
        CGTemplate templateFile = getTemplateLocator().templateWithName(templateName);
        notifyTemplateRequired(templateFile);
        return templateName;
    }

    public CGTemplate templateWithName(String templateRelativePath) throws TemplateNotFoundException {
        CGTemplate templateFile = getTemplateLocator().templateWithName(templateRelativePath);
        notifyTemplateRequired(templateFile);
        return templateFile;
    }

    public void clearTemplates() {
        clearTemplates(false);
    }

    public void clearTemplates(boolean clearTemplateLocatorCache) {
        if (logger.isLoggable(Level.FINE)) {
            logger.fine("clearTemplates() for " + this);
        }
        for (CGTemplate templateFile : _usedTemplates) {
            templateFile.deleteObserver(this);
        }
        _usedTemplates.clear();
        if (clearTemplateLocatorCache) {
            getTemplateLocator().notifyTemplateModified();
        }
    }

    public void notifyTemplateRequired(CGTemplate templateFile) {
        logger.info("notifyTemplateRequired " + templateFile.getRelativePath() + " for " + this);
        if (!_usedTemplates.contains(templateFile)) {
            _usedTemplates.add(templateFile);
            templateFile.addObserver(this);
        }
    }

    public void silentlyGenerateCode() {
        notifyObservers = false;
        try {
            generate(true);
        } catch (GenerationException e) {
            e.printStackTrace();
        }
        notifyObservers = true;
    }

    public void startGeneration() {
        isGenerating = true;
        // getProjectGenerator().startGeneration(this);
        if (needsRegenerationBecauseOfTemplateChange()) {
            clearTemplates(true);
        }
        generationException = null;
    }

    public void stopGeneration() {
        // getProjectGenerator().stopGeneration(this);
        memoryLastGenerationDate = new Date();
        isGenerating = false;
        if (this instanceof IFlexoResourceGenerator && notifyObservers) {
            setChanged();
            notifyObservers(new ContentRegenerated(this, ((IFlexoResourceGenerator) this).getGeneratedCode()));
        }
    }

    public Date getMemoryLastGenerationDate() {
        if (memoryLastGenerationDate == null) {
            if (generatedResources.size() == 0) {
                memoryLastGenerationDate = new Date(1); // 1 because AFTER default template last update
            } else {
                for (CGRepositoryFileResource<?, ?, ? extends CGFile> r : generatedResources) {
                    Date d = r.getCGFile().getLastAcceptingDate();
                    if (d != null && (memoryLastGenerationDate == null || d.before(memoryLastGenerationDate))) {
                        memoryLastGenerationDate = d;
                    }
                }
            }
        }
        return memoryLastGenerationDate;
    }

    public boolean isCodeAlreadyGenerated() {
        if (this instanceof IFlexoResourceGenerator) {
            return ((IFlexoResourceGenerator) this).getGeneratedCode() != null;
        }
        // Not significant
        return true;
    }

    /**
     * @return
     */
    public GeneratedCodeResult getGeneratedCode() {
        return generatedCode;
    }

    public boolean needsGeneration() {
        if (isGenerating) {
            return false;
        }
        if (getGenerationException() != null) {
            return true;
        }
        if (generatedResources.size() == 0) {
            if (logger.isLoggable(Level.WARNING)) {
                logger.warning("No resource generated by this generator!");
            }
        } else {
            for (CGRepositoryFileResource<?, ?, ?> r : generatedResources) {
                if (r.retrieveNeedsMemoryGenerationFlag()) {
                    return true;
                }
            }
        }
        return needsRegenerationBecauseOfTemplateUpdated() || needsRegenerationBecauseOfTemplateChange()
                || !isCodeAlreadyGenerated();
    }

    /**
     * @return
     */
    private boolean needsRegenerationBecauseOfTemplateChange() {
        return getTemplateLocator() != null && getTemplateLocator().needsRegenerationBecauseOfTemplateChange(this);
    }

    public boolean needsRegenerationBecauseOfTemplateUpdated() {
        return getTemplateLocator() != null
                && getTemplateLocator().needsUpdateForGenerator(getMemoryLastGenerationDate(), this);
    }

    public boolean needsRegenerationBecauseOfTemplateUpdated(Date diskLastGenerationDate) {
        return getTemplateLocator() != null
                && getTemplateLocator().needsUpdateForGenerator(diskLastGenerationDate, this)
                || needsRegenerationBecauseOfTemplateChange();
    }

    public boolean hasFormattingException() {
        return false;
    }

    public boolean hasAppendingException() {
        return false;
    }

    public GenerationException getGenerationException() {
        return generationException;
    }

    public void setGenerationException(GenerationException generationException) {
        this.generationException = generationException;
        if (projectGenerator != null) {
            projectGenerator.notifyExceptionOccured(generationException);
        }
        stopGeneration();
    }

    protected void resetGenerationException() {
        this.generationException = null;
    }

    @Override
    public void update(FlexoObservable observable, DataModification dataModification) {
        if (dataModification instanceof TemplateFileChanged
                || dataModification instanceof TemplateFileEditionCancelled
                || dataModification instanceof TemplateFileEdited
                || dataModification instanceof TemplateFileRedefined
                || dataModification instanceof TemplateFileSaved || dataModification instanceof TemplateDeleted) {
            setChanged();
            notifyObservers(dataModification);
        }
    }

    /**
     * Overrides getUsedTemplates
     * 
     * @see org.openflexo.foundation.cg.generator.IFlexoResourceGenerator#getUsedTemplates()
     */
    public Vector<CGTemplate> getUsedTemplates() {
        return _usedTemplates;
    }

    public void setUsedTemplates(Vector<CGTemplate> templates) {
        this._usedTemplates = templates;
    }

    public Holder<Object> getNewHolder() {
        return new Holder<Object>();
    }

    public Vector<Object> getNewVector() {
        return new Vector<Object>();
    }

    public Hashtable<Object, Object> getNewHashtable() {
        return new Hashtable<Object, Object>();
    }

    public TreeMap<Object, Object> getNewTreeMap() {
        return new TreeMap<Object, Object>();
    }

    public Properties getNewProperties() {
        return new Properties();
    }

    public TreeMap<FlexoModelObject, Object> getNewModelObjectTreeMap() {
        return new TreeMap<FlexoModelObject, Object>(
                new FlexoModelObject.FlexoDefaultComparator<FlexoModelObject>());
    }

    public BidiMap getNewBidiMap() {
        return new DualHashBidiMap();
    }

    public StringBuilder getNewStringBuilder() {
        return new StringBuilder();
    }

    public Stack<Object> getNewStack() {
        return new Stack<Object>();
    }

    public <C extends Comparable<C>> void sortVector(List<C> vectorToSort) {
        Collections.sort(vectorToSort);
    }

    public void sortVectorOfModelObject(List<FlexoModelObject> vectorToSort) {
        Collections.sort(vectorToSort, new FlexoModelObject.FlexoDefaultComparator<FlexoModelObject>());
    }

    public static String escapeStringForHTML(String s) {
        return HTMLUtils.escapeStringForHTML(s, false);
    }

    public static String escapeStringForHTML(String s, boolean removeNewLine) {
        return HTMLUtils.escapeStringForHTML(s, removeNewLine);
    }

    public static String extractBodyContent(String html) {
        return HTMLUtils.extractBodyContent(html);
    }

    public static String extractBodyContent(String html, boolean returnHtmlIfNoBodyFound) {
        return HTMLUtils.extractBodyContent(html, returnHtmlIfNoBodyFound);
    }

    public static String escapeStringForCsv(String s) {
        return ToolBox.escapeStringForCsv(s);
    }

    public String capitalize(String s) {
        return ToolBox.capitalize(s);
    }

    public static DateFormat getDateFormat(String pattern) {
        return new SimpleDateFormat(pattern);
    }

    public static String escapeStringForJS(String s) {
        return ToolBox.escapeStringForJS(s);
    }

    public static String escapeStringForProperties(String s) {
        return ToolBox.escapeStringForProperties(s);
    }

    public static String removeAllWhiteCharacters(String text) {
        return removeAllWhiteCharacters(text, " ");
    }

    public static String escapeStringForXML(String string) {
        String escapedString = StringEscapeUtils.escapeXml(string);
        return escapedString != null ? StringUtils.replaceBreakLinesBy(escapedString, " ") : "";
    }

    public static String removeAllWhiteCharacters(String text, String replacement) {
        if (text == null) {
            return null;
        }
        String replaced = text.replaceAll("(\\s)+", replacement);
        return replaced;
    }

    public static Integer getNumberAsInteger(Number number) {
        if (number != null) {
            return new Long(Math.round(number.doubleValue())).intValue();
        }
        return null;
    }
}