001package org.andromda.core.cartridge;
002
003import java.io.File;
004import java.io.StringWriter;
005import java.net.URL;
006import java.util.ArrayList;
007import java.util.Collection;
008import java.util.LinkedHashMap;
009import java.util.LinkedHashSet;
010import java.util.List;
011import java.util.Map;
012import org.andromda.core.cartridge.template.ModelElement;
013import org.andromda.core.cartridge.template.ModelElements;
014import org.andromda.core.cartridge.template.Template;
015import org.andromda.core.cartridge.template.Type;
016import org.andromda.core.common.AndroMDALogger;
017import org.andromda.core.common.BasePlugin;
018import org.andromda.core.common.ExceptionUtils;
019import org.andromda.core.common.Introspector;
020import org.andromda.core.common.PathMatcher;
021import org.andromda.core.common.PostProcessor;
022import org.andromda.core.common.ResourceUtils;
023import org.andromda.core.common.ResourceWriter;
024import org.andromda.core.common.TemplateObject;
025import org.andromda.core.configuration.Namespaces;
026import org.andromda.core.metafacade.MetafacadeBase;
027import org.andromda.core.metafacade.MetafacadeFactory;
028import org.andromda.core.metafacade.ModelAccessFacade;
029import org.apache.commons.io.FileUtils;
030import org.apache.commons.lang.BooleanUtils;
031import org.apache.commons.lang.StringUtils;
032import org.apache.log4j.Logger;
033
034/**
035 * The AndroMDA Cartridge implementation of the Plugin. Cartridge instances are configured from
036 * <code>META-INF/andromda/cartridge.xml</code> files discovered on the classpath.
037 *
038 * @author <a href="http://www.mbohlen.de">Matthias Bohlen </a>
039 * @author Chad Brandon
040 * @author Bob Fields
041 * @author Michail Plushnikov
042 */
043public class Cartridge
044    extends BasePlugin
045{
046    /** The logger instance. */
047    private static final Logger LOGGER = Logger.getLogger(Cartridge.class);
048
049    /**
050     * Processes all model elements with relevant stereotypes by retrieving the model elements from the model facade
051     * contained within the context.
052     *
053     * @param factory the metafacade factory (which is used to manage the lifecycle of metafacades).
054     */
055    public void processModelElements(final MetafacadeFactory factory)
056    {
057        ExceptionUtils.checkNull(
058            "factory",
059            factory);
060        final Collection<Resource> resources = this.getResources();
061        if (resources != null && !resources.isEmpty())
062        {
063            for (Resource resource : resources)
064            {
065                try
066                {
067                    if (resource instanceof Template)
068                    {
069                        this.processTemplate(
070                            factory,
071                            (Template)resource);
072                    }
073                    else
074                    {
075                        this.processResource(resource);
076                    }
077                }
078                catch (Exception e)
079                {
080                    // Don't kill the entire code generation if one output fails
081                    LOGGER.error("Error processing resource " + resource.toString(), e);
082                }
083            }
084        }
085    }
086
087    /**
088     * Processes the given <code>template</code>.
089     *
090     * @param factory the metafacade factory instance.
091     * @param template the Template instance to process.
092     */
093    protected void processTemplate(
094        final MetafacadeFactory factory,
095        final Template template)
096    {
097        ExceptionUtils.checkNull(
098            "template",
099            template);
100        final ModelElements templateModelElements = template.getSupportedModeElements();
101
102        // - handle the templates WITH model elements
103        if (templateModelElements != null && !templateModelElements.isEmpty())
104        {
105            for (ModelElement templateModelElement : templateModelElements.getModelElements())
106            {
107                // - if the template model element has a stereotype
108                //   defined, then we filter the metafacades based
109                //   on that stereotype, otherwise we get all metafacades
110                //   and let the templateModelElement perform filtering on the
111                //   metafacades by type and properties
112                if (templateModelElement.hasStereotype())
113                {
114                    templateModelElement.setMetafacades(
115                        factory.getMetafacadesByStereotype(templateModelElement.getStereotype()));
116                }
117                else if (templateModelElement.hasTypes())
118                {
119                    templateModelElement.setMetafacades(factory.getAllMetafacades());
120                }
121            }
122            this.processTemplateWithMetafacades(
123                factory,
124                template);
125        }
126        else
127        {
128            // - handle any templates WITHOUT metafacades.
129            this.processTemplateWithoutMetafacades(template);
130        }
131    }
132
133    /**
134     * Processes all <code>modelElements</code> for this template.
135     *
136     * @param factory the metafacade factory
137     * @param template the template to process
138     */
139    protected void processTemplateWithMetafacades(
140        final MetafacadeFactory factory,
141        final Template template)
142    {
143        ExceptionUtils.checkNull(
144            "template",
145            template);
146        final ModelElements modelElements = template.getSupportedModeElements();
147        if (modelElements != null && !modelElements.isEmpty())
148        {
149            try
150            {
151                final Collection<MetafacadeBase> allMetafacades = modelElements.getAllMetafacades();
152                // Tell us which template is processed against how many metafacade elements
153                LOGGER.info("Processing " + template.getPath() + " with " + allMetafacades.size() + " metafacades from " + modelElements.getModelElements().size() + " model elements");
154
155                // - if outputToSingleFile is true AND outputOnEmptyElements
156                //   is true or we have at least one metafacade in the
157                //   allMetafacades collection, then we collect the template
158                //   model elements and place them into the template context
159                //   by their variable names.
160                if (template.isOutputToSingleFile() &&
161                    (template.isOutputOnEmptyElements() || !allMetafacades.isEmpty()))
162                {
163                    final Map<String, Object> templateContext = new LinkedHashMap<String, Object>();
164
165                    // - first place all relevant model elements by the
166                    //   <modelElements/> variable name. If the variable
167                    //   isn't defined (which is possible), ignore.
168                    final String modelElementsVariable = modelElements.getVariable();
169                    if (StringUtils.isNotBlank(modelElementsVariable))
170                    {
171                        templateContext.put(
172                            modelElementsVariable,
173                            allMetafacades);
174                    }
175
176                    // - now place the collections of elements by the given variable names.
177                    //   (skip if the variable is NOT defined)
178                    for (final ModelElement modelElement : modelElements.getModelElements())
179                    {
180                        final String modelElementVariable = modelElement.getVariable();
181                        if (StringUtils.isNotBlank(modelElementVariable))
182                        {
183                            // - if a modelElement has the same variable defined
184                            //   more than one time, then get the existing
185                            //   model elements added from the last iteration
186                            //   and add the new ones to that collection
187                            Collection<MetafacadeBase> metafacades = (Collection<MetafacadeBase>)templateContext.get(modelElementVariable);
188                            if (metafacades != null)
189                            {
190                                metafacades.addAll(modelElement.getMetafacades());
191                            }
192                            else
193                            {
194                                metafacades = modelElement.getMetafacades();
195                                templateContext.put(
196                                    modelElementVariable,
197                                    new LinkedHashSet<MetafacadeBase>(metafacades));
198                            }
199                        }
200                    }
201                    this.processWithTemplate(
202                        template,
203                        templateContext,
204                        null,
205                        null);
206                }
207                else
208                {
209                    // - if outputToSingleFile isn't true, then
210                    //   we just place the model element with the default
211                    //   variable defined on the <modelElements/> into the
212                    //   template.
213                    for (Object metafacade : allMetafacades)
214                    {
215                        final Map<String, Object> templateContext = new LinkedHashMap<String, Object>();
216                        final ModelAccessFacade model = factory.getModel();
217                        for (final ModelElement modelElement : modelElements.getModelElements())
218                        {
219                            String variable = modelElement.getVariable();
220
221                            // - if the variable isn't defined on the <modelElement/>, try
222                            //   the <modelElements/>
223                            if (StringUtils.isBlank(variable))
224                            {
225                                variable = modelElements.getVariable();
226                            }
227
228                            // - only add the metafacade to the template context if the variable
229                            //   is defined (which is possible)
230                            if (StringUtils.isNotBlank(variable))
231                            {
232                                templateContext.put(
233                                    variable,
234                                    metafacade);
235                            }
236
237                            // - now we process any property templates (if any 'variable' attributes are defined on one or
238                            //   more type's given properties), otherwise we process the single metafacade as usual
239                            if (!this.processPropertyTemplates(
240                                    template,
241                                    metafacade,
242                                    templateContext,
243                                    modelElement))
244                            {
245                                this.processWithTemplate(
246                                    template,
247                                    templateContext,
248                                    model.getName(metafacade),
249                                    model.getPackageName(metafacade));
250                            }
251                        }
252                    }
253                }
254            }
255            catch (final Throwable throwable)
256            {
257                LOGGER.error("Error Processing " + template.getPath(), throwable);
258                throw new CartridgeException(throwable);
259            }
260        }
261    }
262
263    /**
264     * Determines if any property templates need to be processed (that is templates
265     * that are processed given related <em>properties</em> of a metafacade).
266     *
267     * @param template the template to use for processing.
268     * @param metafacade the metafacade instance (the property value is retrieved from this).
269     * @param templateContext the template context containing the instance to pass to the template.
270     * @param modelElement the model element from which we retrieve the corresponding types and then
271     *        properties to determine if any properties have been mapped for template processing.
272     * @return true if any property templates have been evaluated (false otherwise).
273     */
274    private boolean processPropertyTemplates(
275        final Template template,
276        final Object metafacade,
277        final Map<String, Object> templateContext,
278        final ModelElement modelElement)
279    {
280        boolean propertyTemplatesEvaluated = false;
281        for (final Type type : modelElement.getTypes())
282        {
283            for (final Type.Property property : type.getProperties())
284            {
285                final String variable = property.getVariable();
286                propertyTemplatesEvaluated = StringUtils.isNotBlank(variable);
287                if (propertyTemplatesEvaluated)
288                {
289                    final Object value = Introspector.instance().getProperty(
290                            metafacade,
291                            property.getName());
292                    if (value instanceof Collection)
293                    {
294                        for (Object entry : (Collection) value)
295                        {
296                            templateContext.put(
297                                variable,
298                                entry);
299                            this.processWithTemplate(
300                                template,
301                                templateContext,
302                                null,
303                                null);
304                        }
305                    }
306                    else
307                    {
308                        templateContext.put(
309                            variable,
310                            value);
311                        this.processWithTemplate(
312                            template,
313                            templateContext,
314                            null,
315                            null);
316                    }
317                }
318            }
319        }
320        return propertyTemplatesEvaluated;
321    }
322
323    /**
324     * Processes the <code>template</code> without metafacades. This is useful if you need to generate something that
325     * is part of your cartridge, however you only need to use a property passed in from a namespace or a template
326     * object defined in your cartridge descriptor.
327     *
328     * @param template the template to process.
329     */
330    protected void processTemplateWithoutMetafacades(final Template template)
331    {
332        ExceptionUtils.checkNull(
333            "template",
334            template);
335        final Map<String, Object> templateContext = new LinkedHashMap<String, Object>();
336        this.processWithTemplate(
337            template,
338            templateContext,
339            null,
340            null);
341    }
342
343    /**
344     * <p>
345     * Perform processing with the <code>template</code>.
346     * </p>
347     *
348     * @param template the Template containing the template path to process.
349     * @param templateContext the context to which variables are added and made
350     *        available to the template engine for processing. This will contain
351     *        any model elements being made available to the template(s) as well
352     *        as properties/template objects.
353     * @param metafacadeName the name of the model element (if we are
354     *        processing a single model element, otherwise this will be
355     *        ignored).
356     * @param metafacadePackage the name of the package (if we are processing
357     *        a single model element, otherwise this will be ignored).
358     */
359    private void processWithTemplate(
360        final Template template,
361        final Map<String, Object> templateContext,
362        final String metafacadeName,
363        final String metafacadePackage)
364    {
365        ExceptionUtils.checkNull(
366            "template",
367            template);
368        ExceptionUtils.checkNull(
369            "templateContext",
370            templateContext);
371
372        File outputFile = null;
373        try
374        {
375            // - populate the template context with cartridge descriptor
376            //   properties and template objects
377            this.populateTemplateContext(templateContext);
378
379            final StringWriter output = new StringWriter();
380
381            // - process the template with the set TemplateEngine
382            this.getTemplateEngine().processTemplate(
383                template.getPath(),
384                templateContext,
385                output);
386
387            // - if we have an outputCondition defined make sure it evaluates to true before continuing
388            if (this.isValidOutputCondition(template.getOutputCondition(), templateContext))
389            {
390                // - get the location and at the same time evaluate the outlet as a template engine variable (in case
391                //   its defined as that).
392                final String location =
393                    Namespaces.instance().getPropertyValue(
394                        this.getNamespace(),
395                        this.getTemplateEngine().getEvaluatedExpression(
396                            template.getOutlet(),
397                            templateContext));
398                if (location == null)
399                {
400                    LOGGER.warn("Template outlet location '" + template.getOutlet() + "' is not defined in namespace " + this.getNamespace());
401                }
402                else
403                {
404                    outputFile =
405                        template.getOutputLocation(
406                            metafacadeName,
407                            metafacadePackage,
408                            new File(location),
409                            this.getTemplateEngine().getEvaluatedExpression(
410                                template.getOutputPattern(),
411                                templateContext));
412                    if (outputFile == null)
413                    {
414                        LOGGER.warn("Template outputFile is null for location " + location);
415                    }
416                    else
417                    {
418                        // - only write files that do NOT exist, and
419                        //   those that have overwrite set to 'true'
420                        if (!outputFile.exists() || template.isOverwrite())
421                        {
422                            String outputString = output.toString();
423                            AndroMDALogger.setSuffix(this.getNamespace());
424
425                            // - check to see if generateEmptyFiles is true and if
426                            //   outString is not blank
427                            if (StringUtils.isNotBlank(outputString) ||
428                                template.isGenerateEmptyFiles())
429                            {
430                                for (PostProcessor postProcessor : getTemplatePostProcessor())
431                                {
432                                    if(postProcessor.acceptFile(outputFile))
433                                    {
434                                        try
435                                        {
436                                            String lResult = postProcessor.postProcess(outputString, null);
437                                            if(StringUtils.isNotBlank(lResult))
438                                            {
439                                                outputString = lResult;
440                                            }
441                                            else
442                                            {
443                                                LOGGER.warn("Error PostProcessing " + outputFile.toURI());
444                                            }
445                                        }
446                                        catch (Exception exc)
447                                        {
448                                            LOGGER.warn("Error PostProcessing " + outputFile.toURI() + ": " + exc.getMessage());
449                                        }
450                                    }
451                                }
452
453                                ResourceWriter.instance().writeStringToFile(
454                                    outputString,
455                                    outputFile,
456                                    this.getNamespace());
457                                AndroMDALogger.info("Output: '" + outputFile.toURI() + '\'');
458                            }
459                            else
460                            {
461                                if (this.getLogger().isDebugEnabled())
462                                {
463                                    this.getLogger().debug("Empty Output: '" + outputFile.toURI() + "' --> not writing");
464                                }
465                            }
466                            AndroMDALogger.reset();
467                        }
468                    }
469                }
470            }
471        }
472        catch (final Throwable throwable)
473        {
474            if (FileUtils.deleteQuietly(outputFile))
475            {
476                this.getLogger().info("Removed: '" + outputFile + '\'');
477            }
478            final String message =
479                "Error processing template '" + template.getPath() + "' with template context '" + templateContext +
480                "' using cartridge '" + this.getNamespace() + '\'';
481            LOGGER.error(message, throwable);
482            //throw new CartridgeException(message, throwable);
483        }
484    }
485
486    /**
487     * Processes the given <code>resource</code>
488     *
489     * @param resource the resource to process.
490     */
491    protected void processResource(final Resource resource)
492    {
493        ExceptionUtils.checkNull(
494            "resource",
495            resource);
496
497        URL resourceUrl = ResourceUtils.getResource(
498                resource.getPath(),
499                this.getMergeLocation());
500        if (resourceUrl == null)
501        {
502            // - if the resourceUrl is null, the path is probably a regular
503            //   outputCondition pattern so we'll see if we can match it against
504            //   the contents of the plugin and write any contents that do match
505            final List<String> contents = this.getContents();
506            if (contents != null)
507            {
508                AndroMDALogger.setSuffix(this.getNamespace());
509                for (final String content : contents)
510                {
511                    if (StringUtils.isNotBlank(content))
512                    {
513                        if (PathMatcher.wildcardMatch(
514                                content,
515                                resource.getPath()))
516                        {
517                            resourceUrl = ResourceUtils.getResource(
518                                    content,
519                                    this.getMergeLocation());
520                            // - don't attempt to write the directories within the resource
521                            if (!resourceUrl.toString().endsWith(FORWARD_SLASH))
522                            {
523                                this.writeResource(
524                                    resource,
525                                    resourceUrl);
526                            }
527                        }
528                    }
529                }
530                AndroMDALogger.reset();
531            }
532        }
533        else
534        {
535            this.writeResource(
536                resource,
537                resourceUrl);
538        }
539    }
540
541    /**
542     * The forward slash constant.
543     */
544    private static final String FORWARD_SLASH = "/";
545
546    private static final String PATH_PATTERN = "\\*.*";
547
548    /**
549     * Writes the contents of <code>resourceUrl</code> to the outlet specified by <code>resource</code>.
550     *
551     * @param resource contains the outlet where the resource is written.
552     * @param resourceUrl the URL contents to write.
553     */
554    private void writeResource(
555        final Resource resource,
556        final URL resourceUrl)
557    {
558        File outputFile = null;
559        try
560        {
561            // - make sure we don't have any back slashes
562            final String resourceUri = ResourceUtils.normalizePath(resourceUrl.toString());
563            String uriSuffix = resource.getPath().replaceAll(PATH_PATTERN, "");
564            if (resourceUri.contains(uriSuffix))
565            {
566                uriSuffix = resourceUri.substring(resourceUri.indexOf(uriSuffix) + uriSuffix.length(), resourceUri.length());
567            }
568            else
569            {
570                uriSuffix =
571                    resourceUri.substring(
572                        resourceUri.lastIndexOf(FORWARD_SLASH),
573                        resourceUri.length());
574            }
575
576            final Map<String, Object> templateContext = new LinkedHashMap<String, Object>();
577            this.populateTemplateContext(templateContext);
578
579            // - if we have an outputCondition defined make sure it evaluates to true before continuing
580            if (this.isValidOutputCondition(resource.getOutputCondition(), templateContext))
581            {
582                // - get the location and at the same time evaluate the outlet as a template engine variable (in case
583                //   its defined as that).
584                final String location =
585                    Namespaces.instance().getPropertyValue(
586                        this.getNamespace(),
587                        this.getTemplateEngine().getEvaluatedExpression(
588                            resource.getOutlet(),
589                            templateContext));
590
591                if (location != null)
592                {
593                    outputFile =
594                        resource.getOutputLocation(
595                            new String[] {uriSuffix},
596                            new File(location),
597                            this.getTemplateEngine().getEvaluatedExpression(
598                                resource.getOutputPattern(),
599                                templateContext));
600
601                    final boolean lastModifiedCheck = resource.isLastModifiedCheck();
602                    // - if we have the last modified check set, then make sure the last modified time is greater than the outputFile
603                    if (!lastModifiedCheck || (lastModifiedCheck && ResourceUtils.getLastModifiedTime(resourceUrl) > outputFile.lastModified()))
604                    {
605                        // - only write files that do NOT exist, and
606                        //   those that have overwrite set to 'true'
607                        if (!outputFile.exists() || resource.isOverwrite())
608                        {
609                            ResourceWriter.instance().writeUrlToFile(
610                                resourceUrl,
611                                outputFile.toString());
612                            AndroMDALogger.info("Output: '" + outputFile.toURI() + '\'');
613                        }
614                    }
615                }
616            }
617        }
618        catch (final Throwable throwable)
619        {
620            if (outputFile != null)
621            {
622                outputFile.delete();
623                this.getLogger().info("Removed: '" + outputFile + '\'');
624            }
625            LOGGER.error("Error writing resource " + resource.getOutlet() + " to URL " + resourceUrl.toString(), throwable);
626            //throw new CartridgeException(throwable);
627        }
628    }
629
630    /**
631     * Stores the loaded resources to be processed by this cartridge instance.
632     */
633    private final List<Resource> resources = new ArrayList<Resource>();
634
635    /**
636     * Returns the list of templates configured in this cartridge.
637     *
638     * @return List the template list.
639     */
640    public List<Resource> getResources()
641    {
642        return this.resources;
643    }
644
645    /**
646     * Adds a resource to the list of defined resources.
647     *
648     * @param resource the new resource to add
649     */
650    public void addResource(final Resource resource)
651    {
652        ExceptionUtils.checkNull(
653            "resource",
654            resource);
655        resource.setCartridge(this);
656        resources.add(resource);
657    }
658
659    /**
660     * Populates the <code>templateContext</code> with the properties and template objects defined in the
661     * <code>plugin</code>'s descriptor. If the <code>templateContext</code> is null, a new Map instance will be created
662     * before populating the context.
663     *
664     * @param templateContext the context of the template to populate.
665     */
666    protected void populateTemplateContext(Map<String, Object> templateContext)
667    {
668        super.populateTemplateContext(templateContext);
669        templateContext.putAll(this.getEvaluatedConditions(templateContext));
670    }
671
672    /**
673     * Stores the global conditions from cartridge.xml condition expressions
674     */
675    private final Map<String, String> conditions = new LinkedHashMap<String, String>();
676
677    /**
678     * Stores the evaluated conditions from cartridge.xml condition expressions
679     */
680    private final Map<String, Boolean> evaluatedConditions = new LinkedHashMap<String, Boolean>();
681
682    /**
683     * Stores the postProcessor from cartridge.xml configuration
684     */
685    private final Collection<PostProcessor> templatePostProcessor = new ArrayList<PostProcessor>();
686
687    /**
688     * Adds the outputCondition given the <code>name</code> and <code>value</code>
689     * to the outputConditions map.
690     *
691     * @param name the name of the outputCondition.
692     * @param value the value of the outputCondition.
693     */
694    public void addCondition(final String name, final String value)
695    {
696        this.conditions.put(name, StringUtils.trimToEmpty(value));
697    }
698
699    /**
700     * Gets the current outputConditions defined within this cartridge
701     * @return this.conditions
702     */
703    public Map<String, String> getConditions()
704    {
705        return this.conditions;
706    }
707
708    /**
709     * Indicates whether or not the global outputConditions have been evaluated.
710     */
711    private boolean conditionsEvaluated = false;
712
713    /**
714     * Evaluates all conditions and stores the results in the <code>evaluatedConditions</code>
715     * and returns that Map
716     *
717     * @param templateContext the template context used to evaluate the conditions.
718     * @return the map containing the evaluated conditions.
719     */
720    private Map<String, Boolean> getEvaluatedConditions(final Map<String, Object> templateContext)
721    {
722        if (!this.conditionsEvaluated)
723        {
724            for (final Map.Entry<String, String> entry : conditions.entrySet())
725            {
726                final String value = entry.getValue();
727                if (StringUtils.isNotBlank(value))
728                {
729                    final String evaluationResult = this.getTemplateEngine().getEvaluatedExpression(
730                        value,
731                        templateContext);
732                    final String name = entry.getKey();
733                    this.evaluatedConditions.put(name, BooleanUtils.toBoolean(evaluationResult));
734                }
735            }
736            this.conditionsEvaluated = true;
737        }
738        return this.evaluatedConditions;
739    }
740
741    /**
742     * Gets the evaluated outputCondition result of a global outputCondition.
743     *
744     * @param outputCondition the outputCondition to evaluate.
745     * @param templateContext the current template context to pass the template engine if
746     *        evaluation has yet to occur.
747     * @return the evaluated outputCondition results.
748     */
749    private Boolean getGlobalConditionResult(final String outputCondition, final Map<String, Object> templateContext)
750    {
751        return this.getEvaluatedConditions(templateContext).get(outputCondition);
752    }
753
754    /**
755     * Indicates whether or not the given <code>outputCondition</code> is a valid
756     * outputCondition, that is, whether or not it returns true.
757     *
758     * @param outputCondition the outputCondition to evaluate.
759     * @param templateContext the template context containing the variables to use.
760     * @return true/false
761     */
762    private boolean isValidOutputCondition(final String outputCondition, final Map<String, Object> templateContext)
763    {
764        boolean validOutputCondition = true;
765        if (StringUtils.isNotBlank(outputCondition))
766        {
767            Boolean result = this.getGlobalConditionResult(outputCondition, templateContext);
768            if (result == null)
769            {
770                final String outputConditionResult = this.getTemplateEngine().getEvaluatedExpression(
771                    outputCondition,
772                    templateContext);
773                result = outputConditionResult != null ? BooleanUtils.toBoolean(outputConditionResult.trim()) : null;
774            }
775            validOutputCondition = BooleanUtils.toBoolean(result);
776        }
777        return validOutputCondition;
778    }
779
780    /**
781     * Gets the current postProcessors defined within this cartridge
782     * @return this.postProcessors
783     */
784    public Collection<PostProcessor> getTemplatePostProcessor()
785    {
786        return this.templatePostProcessor;
787    }
788
789    /**
790     * Adds new postProcessor to the cartridge
791     * @param postProcessor new postProcessor
792     */
793    public void addTemplatePostProcessor(final TemplateObject postProcessor)
794    {
795        final PostProcessor lPostProcessor = (PostProcessor) postProcessor.getObject();
796        this.templatePostProcessor.add(lPostProcessor);
797    }
798
799    /**
800     * Override to provide cartridge specific shutdown (
801     *
802     * @see org.andromda.core.common.Plugin#shutdown()
803     */
804    public void shutdown()
805    {
806        super.shutdown();
807        this.conditions.clear();
808        this.evaluatedConditions.clear();
809        this.templatePostProcessor.clear();
810    }
811
812    /**
813     * @see Object#toString()
814     */
815    @Override
816    public String toString()
817    {
818        StringBuilder builder = new StringBuilder();
819        builder.append(super.toString());
820        //builder.append(" [resources=").append(this.resources); // StackOverflow
821        builder.append(" [conditions=").append(this.conditions);
822        builder.append(", evaluatedConditions=").append(this.evaluatedConditions);
823        builder.append(", templatePostProcessor=").append(this.templatePostProcessor);
824        builder.append(", conditionsEvaluated=").append(this.conditionsEvaluated).append("]");
825        return builder.toString();
826    }
827}