001package org.andromda.core.engine;
002
003import java.text.Collator;
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.Collection;
007import java.util.Collections;
008import java.util.Comparator;
009import java.util.LinkedHashMap;
010import java.util.List;
011import java.util.Map;
012import org.andromda.core.ModelValidationException;
013import org.andromda.core.cartridge.Cartridge;
014import org.andromda.core.common.AndroMDALogger;
015import org.andromda.core.common.BuildInformation;
016import org.andromda.core.common.ComponentContainer;
017import org.andromda.core.common.ExceptionRecorder;
018import org.andromda.core.common.Introspector;
019import org.andromda.core.common.ResourceWriter;
020import org.andromda.core.common.XmlObjectFactory;
021import org.andromda.core.configuration.Configuration;
022import org.andromda.core.configuration.Filters;
023import org.andromda.core.configuration.Model;
024import org.andromda.core.configuration.Namespace;
025import org.andromda.core.configuration.Namespaces;
026import org.andromda.core.configuration.Property;
027import org.andromda.core.configuration.Repository;
028import org.andromda.core.metafacade.MetafacadeFactory;
029import org.andromda.core.metafacade.ModelAccessFacade;
030import org.andromda.core.metafacade.ModelValidationMessage;
031import org.andromda.core.namespace.NamespaceComponents;
032import org.andromda.core.repository.Repositories;
033import org.apache.commons.collections.CollectionUtils;
034import org.apache.commons.collections.Predicate;
035import org.apache.commons.collections.comparators.ComparatorChain;
036import org.apache.commons.lang.StringUtils;
037import org.apache.log4j.Logger;
038
039/**
040 * <p>
041 * Handles the processing of models. Facilitates Model Driven
042 * Architecture by enabling the generation of source code, configuration files, and other such artifacts from a single
043 * or multiple models. </p>
044 *
045 * @author Chad Brandon
046 * @author Bob Fields
047 * @author Michail Plushnikov
048 */
049public class ModelProcessor
050{
051    /**
052     * The logger instance.
053     */
054    private static final Logger logger = Logger.getLogger(ModelProcessor.class);
055
056    /**
057     * Creates a new instance the ModelProcessor.
058     *
059     * @return the shared ModelProcessor instance.
060     */
061    public static ModelProcessor newInstance()
062    {
063        return new ModelProcessor();
064    }
065
066    private ModelProcessor()
067    {
068        // - do not allow instantiation
069    }
070
071    /**
072     * Re-configures this model processor from the given <code>configuration</code>
073     * instance (if different from that of the one passed in during the call to
074     * {@link #initialize(Configuration)}), and runs the model processor.
075     *
076     * @param configuration the configuration from which to configure this model
077     *        processor instance.
078     * @return any model validation messages collected during model processing (if
079     *         model validation is enabled).
080     */
081    public ModelValidationMessage[] process(final Configuration configuration)
082    {
083        this.configure(configuration);
084        final List<ModelValidationMessage> messages = this.process(configuration.getRepositories());
085        return messages != null ? messages.toArray(new ModelValidationMessage[messages.size()])
086                                : new ModelValidationMessage[0];
087    }
088
089    /**
090     * Configures (or re-configures) the model processor if configuration
091     * is required (the configuration has changed since the previous, or has
092     * yet to be used).
093     *
094     * @param configuration the AndroMDA configuration instance.
095     */
096    private void configure(final Configuration configuration)
097    {
098        if (this.requiresConfiguration(configuration))
099        {
100            configuration.initialize();
101            this.reset();
102            final Property[] properties = configuration.getProperties();
103            final Introspector introspector = Introspector.instance();
104            for (Property property : properties)
105            {
106                try
107                {
108                    introspector.setProperty(
109                        this,
110                        property.getName(),
111                        property.getValue());
112                }
113                catch (final Throwable throwable)
114                {
115                    AndroMDALogger.warn(
116                        "Could not set model processor property '" + property.getName() + "' with a value of '" +
117                        property.getValue() + '\'');
118                }
119            }
120            this.currentConfiguration = configuration;
121        }
122    }
123
124    /**
125     * Processes all models contained within the <code>repositories</code>
126     * with the discovered cartridges.
127     *
128     * @return any model validation messages that may have been collected during model loading/validation.
129     */
130    private List<ModelValidationMessage> process(final Repository[] repositories)
131    {
132        List<ModelValidationMessage> messages = null;
133        final long startTime = System.currentTimeMillis();
134        for (Repository repository : repositories)
135        {
136            if (repository != null)
137            {
138                final String repositoryName = repository.getName();
139
140                // - filter out any invalid models (ones that don't have any uris defined)
141                final Model[] models = this.filterInvalidModels(repository.getModels());
142                if (models.length > 0)
143                {
144                    messages = this.processModels(
145                            repositoryName,
146                            models);
147                    AndroMDALogger.info(
148                        "completed model processing --> TIME: " + this.getDurationInSeconds(startTime) +
149                        "[s], RESOURCES WRITTEN: " + ResourceWriter.instance().getWrittenCount());
150                }
151                else
152                {
153                    AndroMDALogger.warn("No model(s) found to process for repository '" + repositoryName + '\'');
154                }
155            }
156        }
157        if(messages == null)
158        {
159            messages = Collections.emptyList();
160        }
161        return messages;
162    }
163
164    /**
165     * The shared metafacade factory instance.
166     */
167    private final MetafacadeFactory factory = MetafacadeFactory.getInstance();
168
169    /**
170     * The shared namespaces instance.
171     */
172    private final Namespaces namespaces = Namespaces.instance();
173
174    /**
175     * The shared repositories instance.
176     */
177    private final Repositories repositories = Repositories.instance();
178
179    /**
180     * Check if files have not been modified since last model modification date.
181     * Do not generate from model if no modifications made.
182     */
183    private boolean lastModifiedCheck = true;
184
185    /**
186     * Location where model generation file list is written, normally /target.
187     */
188    private String historyDir = null;
189
190    /**
191     * @param lastModifiedCheck the lastModifiedCheck to set
192     */
193    public void setLastModifiedCheck(boolean lastModifiedCheck)
194    {
195        this.lastModifiedCheck = lastModifiedCheck;
196    }
197
198    /**
199     * @param historyDir the historyDir to set
200     */
201    public void setHistoryDir(String historyDir)
202    {
203        this.historyDir = historyDir;
204    }
205
206    /**
207     * Processes multiple <code>models</code>.
208     *
209     * @param repositoryName the name of the repository that loads/reads the model.
210     * @param models the Model(s) to process.
211     * @return any model validation messages that may have been collected during validation/loading of
212     *         the <code>models</code>.
213     */
214    private List<ModelValidationMessage> processModels(
215        final String repositoryName,
216        final Model[] models)
217    {
218        List<ModelValidationMessage> messages = null;
219        String cartridgeName = null;
220        try
221        {
222            // If lastModifiedCheck = true, always check for modification times
223            long lastModified = 0;
224            final ResourceWriter writer = ResourceWriter.instance();
225            writer.setHistoryStorage(historyDir);
226
227            // - get the time from the model that has the latest modified time
228            for (Model model : models)
229            {
230                writer.resetHistory(model.getUris()[0]);
231                // lastModifiedCheck from andromda.xml can override the global lastModifiedCheck from maven if true
232                this.lastModifiedCheck = model.isLastModifiedCheck() || this.lastModifiedCheck;
233
234                // - we go off the model that was most recently modified.
235                if (model.getLastModified() > lastModified)
236                {
237                    lastModified = model.getLastModified();
238                }
239            }
240
241            if (!this.lastModifiedCheck || writer.isHistoryBefore(lastModified))
242            {
243                final Collection<Cartridge> cartridges = ComponentContainer.instance().findComponentsOfType(Cartridge.class);
244                if (cartridges.isEmpty())
245                {
246                    AndroMDALogger.warn("WARNING! No cartridges found, check your classpath!");
247                }
248
249                final Map<String, Cartridge> cartridgesByNamespace = this.loadCartridgesByNamespace(cartridges);
250
251                // - we want to process by namespace so that the order within the configuration is kept
252                final Collection<Namespace> namespaces = this.namespaces.getNamespaces();
253
254                // - pre-load the models
255                messages = this.loadIfNecessary(models);
256                for (Namespace namespace : namespaces)
257                {
258                    final Cartridge cartridge = cartridgesByNamespace.get(namespace.getName());
259                    if (cartridge != null)
260                    {
261                        cartridgeName = cartridge.getNamespace();
262                        if (this.shouldProcess(cartridgeName))
263                        {
264                             // - set the active namespace on the shared factory and profile instances
265                            this.factory.setNamespace(cartridgeName);
266                            cartridge.initialize();
267
268                            // - process each model with the cartridge
269                            for (Model model : models)
270                            {
271                                AndroMDALogger.info("Processing cartridge " + cartridge.getNamespace() + " on model " + model);
272
273                                // - set the namespace on the metafacades instance so we know the
274                                //   correct facades to use
275                                this.factory.setModel(
276                                    this.repositories.getImplementation(repositoryName).getModel(),
277                                    model.getType());
278                                cartridge.processModelElements(this.factory);
279                                writer.writeHistory();
280                            }
281                            cartridge.shutdown();
282                        }
283                    }
284                }
285            }
286            else
287            {
288                AndroMDALogger.info("Files are up-to-date, skipping AndroMDA execution");
289            }
290        }
291        catch (final ModelValidationException exception)
292        {
293            // - we don't want to record model validation exceptions
294            throw exception;
295        }
296        catch (final Throwable throwable)
297        {
298            final String messsage =
299                "Error performing ModelProcessor.process with model(s) --> '" + StringUtils.join(
300                    models,
301                    ",") + '\'';
302            logger.error(messsage);
303            ExceptionRecorder.instance().record(
304                messsage,
305                throwable,
306                cartridgeName);
307            throw new ModelProcessorException(messsage, throwable);
308        }
309        if(messages == null)
310        {
311            messages = Collections.emptyList();
312        }
313        return messages;
314    }
315
316    /**
317     * Loads the given list of <code>cartridges</code> into a map keyed by namespace.
318     *
319     * @param cartridges the cartridges loaded.
320     * @return the loaded cartridge map.
321     */
322    private Map<String, Cartridge> loadCartridgesByNamespace(final Collection<Cartridge> cartridges)
323    {
324        final Map<String, Cartridge> cartridgesByNamespace = new LinkedHashMap<String, Cartridge>();
325        for (Cartridge cartridge : cartridges)
326        {
327            cartridgesByNamespace.put(cartridge.getNamespace(), cartridge);
328        }
329        return cartridgesByNamespace;
330    }
331
332    /**
333     * Initializes this model processor instance with the given
334     * configuration.  This configuration information is overridden (if changed)
335     * when calling {@link #process(Configuration)}
336     *
337     * @param configuration the configuration instance by which to initialize this
338     *        model processor instance.
339     */
340    public void initialize(final Configuration configuration)
341    {
342        final long startTime = System.currentTimeMillis();
343
344        // - first, print the AndroMDA header
345        this.printConsoleHeader();
346
347        // - second, configure this model processor
348        // - the ordering of this step is important: it needs to occur
349        //   before everything else in the framework is initialized so that
350        //   we have all configuration information available (such as the
351        //   namespace properties)
352        this.configure(configuration);
353
354        // - the logger configuration may have changed - re-init the logger.
355        AndroMDALogger.initialize();
356
357        // - discover all namespace components
358        NamespaceComponents.instance().discover();
359
360        // - find and initialize any repositories
361        repositories.initialize();
362
363        // - finally initialize the metafacade factory
364        this.factory.initialize();
365        this.printWorkCompleteMessage(
366            "core initialization",
367            startTime);
368    }
369
370    /**
371     * Loads the model into the repository only when necessary (the model has a timestamp
372     * later than the last timestamp of the loaded model).
373     *
374     * @param model the model to be loaded.
375     * @return List validation messages
376     */
377    protected final List<ModelValidationMessage> loadModelIfNecessary(final Model model)
378    {
379        final List<ModelValidationMessage> validationMessages = new ArrayList<ModelValidationMessage>();
380        final long startTime = System.currentTimeMillis();
381        if (this.repositories.loadModel(model))
382        {
383            this.printWorkCompleteMessage(
384                "loading",
385                startTime);
386
387            // - validate the model since loading has successfully occurred
388            final Repository repository = model.getRepository();
389            final String repositoryName = repository != null ? repository.getName() : null;
390            validationMessages.addAll(this.validateModel(
391                    repositoryName,
392                    model));
393        }
394        return validationMessages;
395    }
396
397    /**
398     * Validates the entire model with each cartridge namespace,
399     * and returns any validation messages that occurred during validation
400     * (also logs any validation failures).
401     *
402     * @param repositoryName the name of the repository storing the model to validate.
403     * @param model the model to validate
404     * @return any {@link ModelValidationMessage} instances that may have been collected
405     *         during validation.
406     */
407    private List<ModelValidationMessage> validateModel(
408        final String repositoryName,
409        final Model model)
410    {
411        final Filters constraints = model != null ? model.getConstraints() : null;
412        final List<ModelValidationMessage> validationMessages = new ArrayList<ModelValidationMessage>();
413        if (ModelProcessor.modelValidation && model != null)
414        {
415            final long startTime = System.currentTimeMillis();
416            AndroMDALogger.info("- validating model -");
417            final Collection<Cartridge> cartridges = ComponentContainer.instance().findComponentsOfType(Cartridge.class);
418            final ModelAccessFacade modelAccessFacade =
419                this.repositories.getImplementation(repositoryName).getModel();
420
421            // - clear out the factory's caches (such as any previous validation messages, etc.)
422            this.factory.clearCaches();
423            this.factory.setModel(
424                modelAccessFacade,
425                model.getType());
426            for (Cartridge cartridge : cartridges)
427            {
428                final String cartridgeName = cartridge.getNamespace();
429                if (this.shouldProcess(cartridgeName))
430                {
431                    // - set the active namespace on the shared factory and profile instances
432                    this.factory.setNamespace(cartridgeName);
433                    this.factory.validateAllMetafacades();
434                }
435            }
436            final List<ModelValidationMessage> messages = this.factory.getValidationMessages();
437            this.filterAndSortValidationMessages(
438                messages,
439                constraints);
440            this.printValidationMessages(messages);
441            this.printWorkCompleteMessage(
442                "validation",
443                startTime);
444            if (messages != null && !messages.isEmpty())
445            {
446                validationMessages.addAll(messages);
447            }
448        }
449        return validationMessages;
450    }
451
452    /**
453     * Prints a work complete message using the type of <code>unitOfWork</code> and
454     * <code>startTime</code> as input.
455     * @param unitOfWork the type of unit of work that was completed
456     * @param startTime the time the unit of work was started.
457     */
458    private void printWorkCompleteMessage(
459        final String unitOfWork,
460        final long startTime)
461    {
462        AndroMDALogger.info("- " + unitOfWork + " complete: " + this.getDurationInSeconds(startTime) + "[s] -");
463    }
464
465    /**
466     * Calculates the duration in seconds between the
467     * given <code>startTime</code> and the current time.
468     * @param startTime the time to compare against.
469     * @return the duration of time in seconds.
470     */
471    private double getDurationInSeconds(final long startTime)
472    {
473        return ((System.currentTimeMillis() - startTime) / 1000.0);
474    }
475
476    /**
477     * Prints any model validation errors stored within the <code>factory</code>.
478     */
479    private void printValidationMessages(final List<ModelValidationMessage> messages)
480    {
481        // - log all error messages
482        if (messages != null && !messages.isEmpty())
483        {
484            final StringBuilder header =
485                new StringBuilder("Model Validation Failed - " + messages.size() + " VALIDATION ERROR");
486            if (messages.size() > 1)
487            {
488                header.append('S');
489            }
490            AndroMDALogger.error(header);
491            int ctr = 1;
492            for (ModelValidationMessage message : messages)
493            {
494                AndroMDALogger.error(ctr + ") " + message);
495                ctr++;
496            }
497            AndroMDALogger.reset();
498            if (this.failOnValidationErrors)
499            {
500                throw new ModelValidationException("Model validation failed!");
501            }
502        }
503    }
504
505    /**
506     * The current configuration of this model processor.
507     */
508    private Configuration currentConfiguration = null;
509
510    /**
511     * Determines whether or not this model processor needs to be reconfigured.
512     * This is based on whether or not the new configuration is different
513     * than the <code>currentConfiguration</code>.  We determine this checking
514     * if their contents are equal or not, if not equal this method will
515     * return true, otherwise false.
516     *
517     * @param configuration the configuration to compare to the lastConfiguration.
518     * @return true/false
519     */
520    private boolean requiresConfiguration(final Configuration configuration)
521    {
522        boolean requiresConfiguration =
523            this.currentConfiguration == null || this.currentConfiguration.getContents() == null ||
524            configuration.getContents() == null;
525        if (!requiresConfiguration)
526        {
527            requiresConfiguration = !this.currentConfiguration.getContents().equals(configuration.getContents());
528        }
529        return requiresConfiguration;
530    }
531
532    /**
533     * Checks to see if <em>any</em> of the repositories contain models
534     * that need to be reloaded, and if so, re-loads them.
535     *
536     * @param repositories the repositories from which to load the model(s).
537     * @return messages
538     */
539    final List<ModelValidationMessage> loadIfNecessary(final org.andromda.core.configuration.Repository[] repositories)
540    {
541        final List<ModelValidationMessage> messages = new ArrayList<ModelValidationMessage>();
542        if (repositories != null)
543        {
544            for (Repository repository : repositories)
545            {
546                if (repository != null)
547                {
548                    messages.addAll(this.loadIfNecessary(repository.getModels()));
549                }
550            }
551        }
552        return messages;
553    }
554
555    /**
556     * Checks to see if <em>any</em> of the models need to be reloaded, and if so, re-loads them.
557     *
558     * @param models that will be loaded (if necessary).
559     * @return any validation messages collected during loading.
560     */
561    private List<ModelValidationMessage> loadIfNecessary(final Model[] models)
562    {
563        final List<ModelValidationMessage> messages = new ArrayList<ModelValidationMessage>();
564        if (models != null)
565        {
566            for (Model model : models)
567            {
568                messages.addAll(this.loadModelIfNecessary(model));
569            }
570        }
571        return messages;
572    }
573
574    /**
575     * Stores the current version of AndroMDA.
576     */
577    private static final String VERSION = BuildInformation.instance().getBuildVersion();
578
579    /**
580     * Prints the console header.
581     */
582    protected void printConsoleHeader()
583    {
584        AndroMDALogger.info("");
585        AndroMDALogger.info("A n d r o M D A  -  " + VERSION);
586        AndroMDALogger.info("");
587    }
588
589    /**
590     * Whether or not model validation should be performed.
591     */
592    private static boolean modelValidation = true;
593
594    /**
595     * Sets whether or not model validation should occur. This is useful for
596     * performance reasons (i.e. if you have a large model it can significantly decrease the amount of time it takes for
597     * AndroMDA to process a model). By default this is set to <code>true</code>.
598     *
599     * @param modelValidationIn true/false on whether model validation should be performed or not.
600     */
601    public void setModelValidation(final boolean modelValidationIn)
602    {
603        ModelProcessor.modelValidation = modelValidationIn;
604    }
605
606    /**
607     * Gets whether or not model validation should occur. This is useful for
608     * performance reasons (i.e. if you have a large model it can significantly decrease the amount of time it takes for
609     * AndroMDA to process a model). By default this is set to <code>true</code>.
610     *
611     * @return modelValidation true/false on whether model validation should be performed or not.
612     */
613    public static boolean getModelValidation()
614    {
615        return ModelProcessor.modelValidation;
616    }
617
618    /**
619     * A flag indicating whether or not failure should occur
620     * when model validation errors are present.
621     */
622    private boolean failOnValidationErrors = true;
623
624    /**
625     * Sets whether or not processing should fail when validation errors occur, default is <code>true</code>.
626     *
627     * @param failOnValidationErrors whether or not processing should fail if any validation errors are present.
628     */
629    public void setFailOnValidationErrors(final boolean failOnValidationErrors)
630    {
631        this.failOnValidationErrors = failOnValidationErrors;
632    }
633
634    /**
635     * Stores the cartridge filter.
636     */
637    private List cartridgeFilter = null;
638
639    /**
640     * Denotes whether or not the complement of filtered cartridges should be processed
641     */
642    private boolean negateCartridgeFilter = false;
643
644    /**
645     * Indicates whether or not the <code>namespace</code> should be processed. This is determined in conjunction with
646     * {@link #setCartridgeFilter(String)}. If the <code>cartridgeFilter</code> is not defined and the namespace is
647     * present within the configuration, then this method will <strong>ALWAYS </strong> return true.
648     *
649     * @param namespace the name of the namespace to check whether or not it should be processed.
650     * @return true/false on whether or not it should be processed.
651     */
652    protected boolean shouldProcess(final String namespace)
653    {
654        boolean shouldProcess = this.namespaces.namespacePresent(namespace);
655        if (shouldProcess)
656        {
657            shouldProcess = this.cartridgeFilter == null || this.cartridgeFilter.isEmpty();
658            if (!shouldProcess)
659            {
660                shouldProcess =
661                    this.negateCartridgeFilter ^ this.cartridgeFilter.contains(StringUtils.trimToEmpty(namespace));
662            }
663        }
664        return shouldProcess;
665    }
666
667    /**
668     * The prefix used for cartridge filter negation.
669     */
670    private static final String CARTRIDGE_FILTER_NEGATOR = "~";
671
672    /**
673     * <p>
674     * Sets the current cartridge filter. This is a comma separated list of namespaces (matching cartridges names) that
675     * should be processed. </p>
676     * <p>
677     * If this filter is defined, then any cartridge names found in this list <strong>will be processed </strong>, while
678     * any other discovered cartridges <strong>will not be processed </strong>. </p>
679     *
680     * @param namespaces a comma separated list of the cartridge namespaces to be processed.
681     */
682    public void setCartridgeFilter(String namespaces)
683    {
684        if (namespaces != null)
685        {
686            namespaces = StringUtils.deleteWhitespace(namespaces);
687            if (namespaces.startsWith(CARTRIDGE_FILTER_NEGATOR))
688            {
689                this.negateCartridgeFilter = true;
690                namespaces = namespaces.substring(1);
691            }
692            else
693            {
694                this.negateCartridgeFilter = false;
695            }
696            if (StringUtils.isNotBlank(namespaces))
697            {
698                this.cartridgeFilter = Arrays.asList(namespaces.split(","));
699            }
700        }
701    }
702
703    /**
704     * Sets the encoding (UTF-8, ISO-8859-1, etc) for all output
705     * produced during model processing.
706     *
707     * @param outputEncoding the encoding.
708     */
709    public void setOutputEncoding(final String outputEncoding)
710    {
711        ResourceWriter.instance().setEncoding(outputEncoding);
712    }
713
714    /**
715     * Sets <code>xmlValidation</code> to be true/false. This defines whether XML resources loaded by AndroMDA (such as
716     * plugin descriptors) should be validated. Sometimes underlying parsers don't support XML Schema validation and in
717     * that case, we want to be able to turn it off.
718     *
719     * @param xmlValidation true/false on whether we should validate XML resources used by AndroMDA
720     */
721    public void setXmlValidation(final boolean xmlValidation)
722    {
723        XmlObjectFactory.setDefaultValidating(xmlValidation);
724    }
725
726    /**
727     * <p>
728     * Sets the <code>loggingConfigurationUri</code> for AndroMDA. This is the URI to an external logging configuration
729     * file. This is useful when you want to override the default logging configuration of AndroMDA. </p>
730     * <p>
731     * You can retrieve the default log4j.xml contained within the {@link org.andromda.core.common}package, customize
732     * it, and then specify the location of this logging file with this operation. </p>
733     *
734     * @param loggingConfigurationUri the URI to the external logging configuration file.
735     */
736    public void setLoggingConfigurationUri(final String loggingConfigurationUri)
737    {
738        AndroMDALogger.setLoggingConfigurationUri(loggingConfigurationUri);
739    }
740
741    /**
742     * Filters out any <em>invalid</em> models. This means models that either are null within the specified
743     * <code>models</code> array or those that don't have URLs set.
744     *
745     * @param models the models to filter.
746     * @return the array of valid models
747     */
748    private Model[] filterInvalidModels(final Model[] models)
749    {
750        final Collection<Model> validModels = new ArrayList<Model>(Arrays.asList(models));
751        CollectionUtils.filter(validModels, new Predicate() {
752            public boolean evaluate(Object o) {
753                final Model model = (Model)o;
754                return model != null && model.getUris() != null && model.getUris().length > 0;
755            }
756        });
757
758        return validModels.toArray(new Model[validModels.size()]);
759    }
760
761    /**
762     * Shuts down the model processor (reclaims any
763     * resources).
764     */
765    public void shutdown()
766    {
767        // - shutdown the metafacade factory instance
768        this.factory.shutdown();
769
770        // - shutdown the configuration namespaces instance
771        this.namespaces.clear();
772
773        // - shutdown the container instance
774        ComponentContainer.instance().shutdown();
775
776        // - shutdown the namespace components registry
777        NamespaceComponents.instance().shutdown();
778
779        // - shutdown the introspector
780        Introspector.instance().shutdown();
781
782        // - clear out any caches used by the configuration
783        Configuration.clearCaches();
784
785        // - clear out any repositories
786        this.repositories.clear();
787    }
788
789    /**
790     * Reinitializes the model processor's resources.
791     */
792    private void reset()
793    {
794        this.factory.reset();
795        this.cartridgeFilter = null;
796        this.setXmlValidation(true);
797        this.setOutputEncoding(null);
798        this.setModelValidation(true);
799        this.setFailOnValidationErrors(true);
800    }
801
802    /**
803     * Filters out any messages that should not be applied according to the AndroMDA configuration's
804     * constraints and sorts the resulting <code>messages</code> first by type (i.e. the metafacade class)
805     * and then by the <code>name</code> of the model element to which the validation message applies.
806     *
807     * @param messages the collection of messages to sort.
808     * @param constraints any constraint filters to apply to the validation messages.
809     */
810    protected void filterAndSortValidationMessages(
811        final List<ModelValidationMessage> messages,
812        final Filters constraints)
813    {
814        if (constraints != null)
815        {
816            // - perform constraint filtering (if any applies)
817            CollectionUtils.filter(messages, new Predicate() {
818                public boolean evaluate(Object o) {
819                    ModelValidationMessage message = (ModelValidationMessage)o;
820                    return constraints.isApply(message.getName());
821                }
822            });
823        }
824
825        if (messages != null && !messages.isEmpty())
826        {
827            final ComparatorChain chain = new ComparatorChain();
828            chain.addComparator(new ValidationMessageTypeComparator());
829            chain.addComparator(new ValidationMessageNameComparator());
830            Collections.sort(
831                messages,
832                chain);
833        }
834    }
835
836    /**
837     * Used to sort validation messages by <code>metafacadeClass</code>.
838     */
839    private static final class ValidationMessageTypeComparator
840        implements Comparator<ModelValidationMessage>
841    {
842        private final Collator collator = Collator.getInstance();
843
844        ValidationMessageTypeComparator()
845        {
846            collator.setStrength(Collator.PRIMARY);
847        }
848
849        public int compare(
850            final ModelValidationMessage objectA,
851            final ModelValidationMessage objectB)
852        {
853            return collator.compare(
854                objectA.getMetafacadeClass().getName(),
855                objectB.getMetafacadeClass().getName());
856        }
857    }
858
859    /**
860     * Used to sort validation messages by <code>modelElementName</code>.
861     */
862    private static final class ValidationMessageNameComparator
863        implements Comparator<ModelValidationMessage>
864    {
865        private final Collator collator = Collator.getInstance();
866
867        ValidationMessageNameComparator()
868        {
869            collator.setStrength(Collator.PRIMARY);
870        }
871
872        public int compare(
873            final ModelValidationMessage objectA,
874            final ModelValidationMessage objectB)
875        {
876            return collator.compare(
877                StringUtils.trimToEmpty(objectA.getMetafacadeName()),
878                StringUtils.trimToEmpty(objectB.getMetafacadeName()));
879        }
880    }
881}