1 package org.andromda.core.engine;
2
3 import java.text.Collator;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.Collection;
7 import java.util.Collections;
8 import java.util.Comparator;
9 import java.util.Iterator;
10 import java.util.LinkedHashMap;
11 import java.util.List;
12 import java.util.Map;
13
14 import org.andromda.core.ModelValidationException;
15 import org.andromda.core.cartridge.Cartridge;
16 import org.andromda.core.common.AndroMDALogger;
17 import org.andromda.core.common.BuildInformation;
18 import org.andromda.core.common.ComponentContainer;
19 import org.andromda.core.common.ExceptionRecorder;
20 import org.andromda.core.common.Introspector;
21 import org.andromda.core.common.ResourceWriter;
22 import org.andromda.core.common.XmlObjectFactory;
23 import org.andromda.core.configuration.Configuration;
24 import org.andromda.core.configuration.Filters;
25 import org.andromda.core.configuration.Model;
26 import org.andromda.core.configuration.Namespace;
27 import org.andromda.core.configuration.Namespaces;
28 import org.andromda.core.configuration.Property;
29 import org.andromda.core.metafacade.MetafacadeFactory;
30 import org.andromda.core.metafacade.ModelAccessFacade;
31 import org.andromda.core.metafacade.ModelValidationMessage;
32 import org.andromda.core.namespace.NamespaceComponents;
33 import org.andromda.core.repository.Repositories;
34 import org.apache.commons.collections.comparators.ComparatorChain;
35 import org.apache.commons.lang.StringUtils;
36 import org.apache.log4j.Logger;
37
38
39 /***
40 * <p>
41 * Handles the processing of models. Facilitates Model Driven
42 * Architecture by enabling the generation of source code, configuration files, and other such artifacts from a single
43 * or multiple models. </p>
44 *
45 * @author Chad Brandon
46 */
47 public class ModelProcessor
48 {
49 /***
50 * The logger instance.
51 */
52 private static final Logger logger = Logger.getLogger(ModelProcessor.class);
53
54 /***
55 * Creates a new instance the ModelProcessor.
56 *
57 * @return the shared ModelProcessor instance.
58 */
59 public static ModelProcessor newInstance()
60 {
61 return new ModelProcessor();
62 }
63
64 private ModelProcessor()
65 {
66
67 }
68
69 /***
70 * Re-configures this model processor from the given <code>configuration</code>
71 * instance (if different from that of the one passed in during the call to
72 * {@link #initialize(Configuration)}), and runs the model processor.
73 *
74 * @param configuration the configuration from which to configure this model
75 * processor instance.
76 * @return any model validation messages collected during model processing (if
77 * model validation is enabled).
78 */
79 public ModelValidationMessage[] process(final Configuration configuration)
80 {
81 this.configure(configuration);
82 final List messages = this.process(configuration.getRepositories());
83 return messages != null ? (ModelValidationMessage[])messages.toArray(new ModelValidationMessage[0])
84 : new ModelValidationMessage[0];
85 }
86
87 /***
88 * Configures (or re-configures) the model processor if configuration
89 * is required (the configuration has changed since the previous, or has
90 * yet to be used).
91 *
92 * @param configuration the AndroMDA configuration instance.
93 */
94 private void configure(final Configuration configuration)
95 {
96 if (this.requiresConfiguration(configuration))
97 {
98 configuration.initialize();
99 this.reset();
100 final Property[] properties = configuration.getProperties();
101 final int propertyNumber = properties.length;
102 final Introspector introspector = Introspector.instance();
103 for (int ctr = 0; ctr < propertyNumber; ctr++)
104 {
105 final Property property = properties[ctr];
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 process(final org.andromda.core.configuration.Repository[] repositories)
131 {
132 List messages = null;
133 final long startTime = System.currentTimeMillis();
134 final int repositoryNumber = repositories.length;
135 for (int ctr = 0; ctr < repositoryNumber; ctr++)
136 {
137 final org.andromda.core.configuration.Repository repository = repositories[ctr];
138 if (repository != null)
139 {
140 final String repositoryName = repository.getName();
141
142
143 final Model[] models = this.filterInvalidModels(repository.getModels());
144 if (models.length > 0)
145 {
146 messages = this.processModels(
147 repositoryName,
148 models);
149 AndroMDALogger.info(
150 "completed model processing --> TIME: " + this.getDurationInSeconds(startTime) +
151 "[s], RESOURCES WRITTEN: " + ResourceWriter.instance().getWrittenCount());
152 }
153 else
154 {
155 AndroMDALogger.warn("No model(s) found to process for repository '" + repositoryName + "'");
156 }
157 }
158 }
159 return messages == null ? Collections.EMPTY_LIST : messages;
160 }
161
162 /***
163 * The shared metafacade factory instance.
164 */
165 private final MetafacadeFactory factory = MetafacadeFactory.getInstance();
166
167 /***
168 * The shared namespaces instance.
169 */
170 private final Namespaces namespaces = Namespaces.instance();
171
172 /***
173 * The shared repositories instance.
174 */
175 private final Repositories repositories = Repositories.instance();
176
177 /***
178 * Processes multiple <code>models</code>.
179 *
180 * @param repositoryName the name of the repository that loads/reads the model.
181 * @param models the Model(s) to process.
182 * @return any model validation messages that may have been collected during validation/loading of
183 * the <code>models</code>.
184 */
185 private List processModels(
186 final String repositoryName,
187 final Model[] models)
188 {
189 List messages = null;
190 String cartridgeName = null;
191 try
192 {
193 boolean lastModifiedCheck = true;
194 long lastModified = 0;
195 final ResourceWriter writer = ResourceWriter.instance();
196
197
198 for (int ctr = 0; ctr < models.length; ctr++)
199 {
200 final Model model = models[ctr];
201 writer.resetHistory(model.getUris()[0]);
202 lastModifiedCheck = model.isLastModifiedCheck() && lastModifiedCheck;
203
204
205 if (model.getLastModified() > lastModified)
206 {
207 lastModified = model.getLastModified();
208 }
209 }
210
211 if (!lastModifiedCheck || writer.isHistoryBefore(lastModified))
212 {
213 final Collection cartridges = ComponentContainer.instance().findComponentsOfType(Cartridge.class);
214 if (cartridges.isEmpty())
215 {
216 AndroMDALogger.warn("WARNING! No cartridges found, check your classpath!");
217 }
218
219 final Map cartridgesByNamespace = this.loadCartridgesByNamespace(cartridges);
220
221
222 final Collection namespaces = this.namespaces.getNamespaces();
223
224
225 messages = this.loadIfNecessary(models);
226 for (final Iterator iterator = namespaces.iterator(); iterator.hasNext();)
227 {
228 final Namespace namespace = (Namespace)iterator.next();
229 final Cartridge cartridge = (Cartridge)cartridgesByNamespace.get(namespace.getName());
230 if (cartridge != null)
231 {
232 cartridgeName = cartridge.getNamespace();
233 if (this.shouldProcess(cartridgeName))
234 {
235
236 this.factory.setNamespace(cartridgeName);
237 cartridge.initialize();
238
239
240 for (int ctr = 0; ctr < models.length; ctr++)
241 {
242 final Model model = models[ctr];
243
244
245
246 this.factory.setModel(
247 this.repositories.getImplementation(repositoryName).getModel(),
248 model.getType());
249 cartridge.processModelElements(this.factory);
250 writer.writeHistory();
251 }
252 cartridge.shutdown();
253 }
254 }
255 }
256 }
257 }
258 catch (final ModelValidationException exception)
259 {
260
261 throw exception;
262 }
263 catch (final Throwable throwable)
264 {
265 final String messsage =
266 "Error performing ModelProcessor.process with model(s) --> '" + StringUtils.join(
267 models,
268 ",") + "'";
269 logger.error(messsage);
270 ExceptionRecorder.instance().record(
271 messsage,
272 throwable,
273 cartridgeName);
274 throw new ModelProcessorException(messsage, throwable);
275 }
276 return messages == null ? Collections.EMPTY_LIST : messages;
277 }
278
279 /***
280 * Loads the given list of <code>cartridges</code> into a map keyed by namespace.
281 *
282 * @param cartridges the cartridges loaded.
283 * @return the loaded cartridge map.
284 */
285 private Map loadCartridgesByNamespace(final Collection cartridges)
286 {
287 final Map cartridgesByNamespace = new LinkedHashMap();
288 for (final Iterator iterator = cartridges.iterator(); iterator.hasNext();)
289 {
290 final Cartridge cartridge = (Cartridge)iterator.next();
291 cartridgesByNamespace.put(cartridge.getNamespace(), cartridge);
292 }
293 return cartridgesByNamespace;
294 }
295
296 /***
297 * Initializes this model processor instance with the given
298 * configuration. This configuration information is overridden (if changed)
299 * when calling {@link #process(Configuration)}
300 *
301 * @param configuration the configuration instance by which to initialize this
302 * model processor instance.
303 */
304 public void initialize(final Configuration configuration)
305 {
306 final long startTime = System.currentTimeMillis();
307
308
309 this.printConsoleHeader();
310
311
312
313
314
315
316 this.configure(configuration);
317
318
319 AndroMDALogger.initialize();
320
321
322 NamespaceComponents.instance().discover();
323
324
325 repositories.initialize();
326
327
328 this.factory.initialize();
329 this.printWorkCompleteMessage(
330 "core initialization",
331 startTime);
332 }
333
334 /***
335 * Loads the model into the repository only when necessary (the model has a timestamp
336 * later than the last timestamp of the loaded model).
337 *
338 * @param model the model to be loaded.
339 */
340 protected final List loadModelIfNecessary(final Model model)
341 {
342 final List validationMessages = new ArrayList();
343 final long startTime = System.currentTimeMillis();
344 if (this.repositories.loadModel(model))
345 {
346 this.printWorkCompleteMessage(
347 "loading",
348 startTime);
349
350
351 final org.andromda.core.configuration.Repository repository = model.getRepository();
352 final String repositoryName = repository != null ? repository.getName() : null;
353 validationMessages.addAll(this.validateModel(
354 repositoryName,
355 model));
356 }
357 return validationMessages;
358 }
359
360 /***
361 * Validates the entire model with each cartridge namespace,
362 * and returns any validation messages that occurred during validation
363 * (also logs any validation failures).
364 *
365 * @param repositoryName the name of the repository storing the model to validate.
366 * @return any {@link ModelValidationMessage} instances that may have been collected
367 * during validation.
368 */
369 private List validateModel(
370 final String repositoryName,
371 final Model model)
372 {
373 final Filters constraints = model != null ? model.getConstraints() : null;
374 final List validationMessages = new ArrayList();
375 if (this.modelValidation)
376 {
377 final long startTime = System.currentTimeMillis();
378 AndroMDALogger.info("- validating model -");
379 final Collection cartridges = ComponentContainer.instance().findComponentsOfType(Cartridge.class);
380 final ModelAccessFacade modelAccessFacade =
381 this.repositories.getImplementation(repositoryName).getModel();
382
383
384 this.factory.clearCaches();
385 this.factory.setModel(
386 modelAccessFacade,
387 model.getType());
388 for (final Iterator iterator = cartridges.iterator(); iterator.hasNext();)
389 {
390 final Cartridge cartridge = (Cartridge)iterator.next();
391 final String cartridgeName = cartridge.getNamespace();
392 if (this.shouldProcess(cartridgeName))
393 {
394
395 this.factory.setNamespace(cartridgeName);
396 this.factory.validateAllMetafacades();
397 }
398 }
399 final List messages = this.factory.getValidationMessages();
400 this.filterAndSortValidationMessages(
401 messages,
402 constraints);
403 this.printValidationMessages(messages);
404 this.printWorkCompleteMessage(
405 "validation",
406 startTime);
407 if (messages != null && !messages.isEmpty())
408 {
409 validationMessages.addAll(messages);
410 }
411 }
412 return validationMessages;
413 }
414
415 /***
416 * Prints a work complete message using the type of <code>unitOfWork</code> and
417 * <code>startTime</code> as input.
418 * @param unitOfWork the type of unit of work that was completed
419 * @param startTime the time the unit of work was started.
420 */
421 private void printWorkCompleteMessage(
422 final String unitOfWork,
423 final long startTime)
424 {
425 AndroMDALogger.info("- " + unitOfWork + " complete: " + this.getDurationInSeconds(startTime) + "[s] -");
426 }
427
428 /***
429 * Calcuates the duration in seconds between the
430 * given <code>startTime</code> and the current time.
431 * @param startTime the time to compare against.
432 * @return the duration of time in seconds.
433 */
434 private double getDurationInSeconds(final long startTime)
435 {
436 return ((System.currentTimeMillis() - startTime) / 1000.0);
437 }
438
439 /***
440 * Prints any model validation errors stored within the <code>factory</code>.
441 */
442 private void printValidationMessages(final List messages)
443 {
444
445 if (messages != null && !messages.isEmpty())
446 {
447 final StringBuffer header =
448 new StringBuffer("Model Validation Failed - " + messages.size() + " VALIDATION ERROR");
449 if (messages.size() > 1)
450 {
451 header.append("S");
452 }
453 AndroMDALogger.error(header);
454 final Iterator iterator = messages.iterator();
455 for (int ctr = 1; iterator.hasNext(); ctr++)
456 {
457 final ModelValidationMessage message = (ModelValidationMessage)iterator.next();
458 AndroMDALogger.error(ctr + ") " + message);
459 }
460 AndroMDALogger.reset();
461 if (this.failOnValidationErrors)
462 {
463 throw new ModelValidationException("Model validation failed!");
464 }
465 }
466 }
467
468 /***
469 * The current configuration of this model processor.
470 */
471 private Configuration currentConfiguration = null;
472
473 /***
474 * Determines whether or not this model processor needs to be reconfigured.
475 * This is based on whether or not the new configuration is different
476 * than the <code>currentConfiguration</code>. We determine this checking
477 * if their contents are equal or not, if not equal this method will
478 * return true, otherwise false.
479 *
480 * @param configuration the configuration to compare to the lastConfiguration.
481 * @return true/false
482 */
483 private boolean requiresConfiguration(final Configuration configuration)
484 {
485 boolean requiresConfiguration =
486 this.currentConfiguration == null || this.currentConfiguration.getContents() == null ||
487 configuration.getContents() == null;
488 if (!requiresConfiguration)
489 {
490 requiresConfiguration = !this.currentConfiguration.getContents().equals(configuration.getContents());
491 }
492 return requiresConfiguration;
493 }
494
495 /***
496 * Checks to see if <em>any</em> of the repositories contain models
497 * that need to be reloaded, and if so, re-loads them.
498 *
499 * @param repositories the repositories from which to load the model(s).
500 */
501 final List loadIfNecessary(final org.andromda.core.configuration.Repository[] repositories)
502 {
503 final List messages = new ArrayList();
504 if (repositories != null && repositories.length > 0)
505 {
506 final int repositoryNumber = repositories.length;
507 for (int repositoryCtr = 0; repositoryCtr < repositoryNumber; repositoryCtr++)
508 {
509 final org.andromda.core.configuration.Repository repository = repositories[repositoryCtr];
510 if (repository != null)
511 {
512 messages.addAll(this.loadIfNecessary(repository.getModels()));
513 }
514 }
515 }
516 return messages;
517 }
518
519 /***
520 * Checks to see if <em>any</em> of the models need to be reloaded, and if so, re-loads them.
521 *
522 * @param models that will be loaded (if necessary).
523 * @return any validation messages collected during loading.
524 */
525 private List loadIfNecessary(final Model[] models)
526 {
527 final List messages = new ArrayList();
528 if (models != null && models.length > 0)
529 {
530 final int modelNumber = models.length;
531 for (int modelCtr = 0; modelCtr < modelNumber; modelCtr++)
532 {
533 messages.addAll(this.loadModelIfNecessary(models[modelCtr]));
534 }
535 }
536 return messages;
537 }
538
539 /***
540 * Stores the current version of AndroMDA.
541 */
542 private static final String VERSION = BuildInformation.instance().getBuildVersion();
543
544 /***
545 * Prints the console header.
546 */
547 protected void printConsoleHeader()
548 {
549 AndroMDALogger.info("");
550 AndroMDALogger.info("A n d r o M D A - " + VERSION);
551 AndroMDALogger.info("");
552 }
553
554 /***
555 * Whether or not model validation should be performed.
556 */
557 private boolean modelValidation = true;
558
559 /***
560 * Sets whether or not model validation should occur. This is useful for
561 * performance reasons (i.e. if you have a large model it can significatly descrease the amount of time it takes for
562 * AndroMDA to process a model). By default this is set to <code>true</code>.
563 *
564 * @param modelValidation true/false on whether model validation should be performed or not.
565 */
566 public void setModelValidation(final boolean modelValidation)
567 {
568 this.modelValidation = modelValidation;
569 }
570
571 /***
572 * A flag indicating whether or not failure should occur
573 * when model validation errors are present.
574 */
575 private boolean failOnValidationErrors = true;
576
577 /***
578 * Sets whether or not processing should fail when validation errors occur, default is <code>true</code>.
579 *
580 * @param failOnValidationErrors whether or not processing should fail if any validation errors are present.
581 */
582 public void setFailOnValidationErrors(final boolean failOnValidationErrors)
583 {
584 this.failOnValidationErrors = failOnValidationErrors;
585 }
586
587 /***
588 * Stores the cartridge filter.
589 */
590 private List cartridgeFilter = null;
591
592 /***
593 * Denotes whether or not the complement of filtered cartridges should be processed
594 */
595 private boolean negateCartridgeFilter = false;
596
597 /***
598 * Indicates whether or not the <code>namespace</code> should be processed. This is determined in conjunction with
599 * {@link #setCartridgeFilter(String)}. If the <code>cartridgeFilter</code> is not defined and the namespace is
600 * present within the configuration, then this method will <strong>ALWAYS </strong> return true.
601 *
602 * @param namespace the name of the namespace to check whether or not it should be processed.
603 * @return true/false on whether or not it should be processed.
604 */
605 protected boolean shouldProcess(final String namespace)
606 {
607 boolean shouldProcess = this.namespaces.namespacePresent(namespace);
608 if (shouldProcess)
609 {
610 shouldProcess = this.cartridgeFilter == null || this.cartridgeFilter.isEmpty();
611 if (!shouldProcess)
612 {
613 shouldProcess =
614 this.negateCartridgeFilter ^ this.cartridgeFilter.contains(StringUtils.trimToEmpty(namespace));
615 }
616 }
617 return shouldProcess;
618 }
619
620 /***
621 * The prefix used for cartridge filter negation.
622 */
623 private static final String CARTRIDGE_FILTER_NEGATOR = "~";
624
625 /***
626 * <p/>
627 * Sets the current cartridge filter. This is a comma seperated list of namespaces (matching cartridges names) that
628 * should be processed. </p>
629 * <p/>
630 * If this filter is defined, then any cartridge names found in this list <strong>will be processed </strong>, while
631 * any other discovered cartridges <strong>will not be processed </strong>. </p>
632 *
633 * @param namespaces a comma seperated list of the cartridge namespaces to be processed.
634 */
635 public void setCartridgeFilter(String namespaces)
636 {
637 if (namespaces != null)
638 {
639 namespaces = StringUtils.deleteWhitespace(namespaces);
640 if (namespaces.startsWith(CARTRIDGE_FILTER_NEGATOR))
641 {
642 this.negateCartridgeFilter = true;
643 namespaces = namespaces.substring(1);
644 }
645 else
646 {
647 this.negateCartridgeFilter = false;
648 }
649 if (StringUtils.isNotBlank(namespaces))
650 {
651 this.cartridgeFilter = Arrays.asList(namespaces.split(","));
652 }
653 }
654 }
655
656 /***
657 * Sets the encoding (UTF-8, ISO-8859-1, etc) for all output
658 * produced during model processing.
659 *
660 * @param outputEncoding the encoding.
661 */
662 public void setOutputEncoding(final String outputEncoding)
663 {
664 ResourceWriter.instance().setEncoding(outputEncoding);
665 }
666
667 /***
668 * Sets <code>xmlValidation</code> to be true/false. This defines whether XML resources loaded by AndroMDA (such as
669 * plugin descriptors) should be validated. Sometimes underlying parsers don't support XML Schema validation and in
670 * that case, we want to be able to turn it off.
671 *
672 * @param xmlValidation true/false on whether we should validate XML resources used by AndroMDA
673 */
674 public void setXmlValidation(final boolean xmlValidation)
675 {
676 XmlObjectFactory.setDefaultValidating(xmlValidation);
677 }
678
679 /***
680 * <p/>
681 * Sets the <code>loggingConfigurationUri</code> for AndroMDA. This is the URI to an external logging configuration
682 * file. This is useful when you want to override the default logging configuration of AndroMDA. </p>
683 * <p/>
684 * You can retrieve the default log4j.xml contained within the {@link org.andromda.core.common}package, customize
685 * it, and then specify the location of this logging file with this operation. </p>
686 *
687 * @param loggingConfigurationUri the URI to the external logging configuation file.
688 */
689 public void setLoggingConfigurationUri(final String loggingConfigurationUri)
690 {
691 AndroMDALogger.setLoggingConfigurationUri(loggingConfigurationUri);
692 }
693
694 /***
695 * Filters out any <em>invalid</em> models. This means models that either are null within the specified
696 * <code>models</code> array or those that don't have URLs set.
697 *
698 * @param models the models to filter.
699 * @return the array of valid models
700 */
701 private Model[] filterInvalidModels(final Model[] models)
702 {
703 final Collection validModels = new ArrayList(Arrays.asList(models));
704 for (final Iterator iterator = validModels.iterator(); iterator.hasNext();)
705 {
706 final Model model = (Model)iterator.next();
707 if (!(model != null && model.getUris() != null && model.getUris().length > 0))
708 {
709 iterator.remove();
710 }
711 }
712 return (Model[])validModels.toArray(new Model[0]);
713 }
714
715 /***
716 * Shuts down the model processor (reclaims any
717 * resources).
718 */
719 public void shutdown()
720 {
721
722 this.factory.shutdown();
723
724
725 this.namespaces.clear();
726
727
728 ComponentContainer.instance().shutdown();
729
730
731 NamespaceComponents.instance().shutdown();
732
733
734 Introspector.instance().shutdown();
735
736
737 Configuration.clearCaches();
738
739
740 this.repositories.clear();
741 }
742
743 /***
744 * Reinitializes the model processor's resources.
745 */
746 private void reset()
747 {
748 this.factory.reset();
749 this.cartridgeFilter = null;
750 this.setXmlValidation(true);
751 this.setOutputEncoding(null);
752 this.setModelValidation(true);
753 this.setLoggingConfigurationUri(null);
754 this.setFailOnValidationErrors(true);
755 }
756
757 /***
758 * Filters out any messages that should not be applied according to the AndroMDA configuration's
759 * constraints and sorts the resulting <code>messages</code> first by type (i.e. the metafacade class)
760 * and then by the <code>name</code> of the model element to which the validation message applies.
761 *
762 * @param messages the collection of messages to sort.
763 * @param constraints any constraint filters to apply to the validation messages.
764 */
765 protected void filterAndSortValidationMessages(
766 final List messages,
767 final Filters constraints)
768 {
769 if (constraints != null)
770 {
771
772 for (final Iterator iterator = messages.iterator(); iterator.hasNext();)
773 {
774 final ModelValidationMessage message = (ModelValidationMessage)iterator.next();
775 if (message != null && !constraints.isApply(message.getName()))
776 {
777 iterator.remove();
778 }
779 }
780 }
781
782 if (messages != null && !messages.isEmpty())
783 {
784 final ComparatorChain chain = new ComparatorChain();
785 chain.addComparator(new ValidationMessageTypeComparator());
786 chain.addComparator(new ValidationMessageNameComparator());
787 Collections.sort(
788 messages,
789 chain);
790 }
791 }
792
793 /***
794 * Used to sort validation messages by <code>metafacadeClass</code>.
795 */
796 private final static class ValidationMessageTypeComparator
797 implements Comparator
798 {
799 private final Collator collator = Collator.getInstance();
800
801 ValidationMessageTypeComparator()
802 {
803 collator.setStrength(Collator.PRIMARY);
804 }
805
806 public int compare(
807 final Object objectA,
808 final Object objectB)
809 {
810 final ModelValidationMessage a = (ModelValidationMessage)objectA;
811 final ModelValidationMessage b = (ModelValidationMessage)objectB;
812 return collator.compare(
813 a.getMetafacadeClass().getName(),
814 b.getMetafacadeClass().getName());
815 }
816 }
817
818 /***
819 * Used to sort validation messages by <code>modelElementName</code>.
820 */
821 private final static class ValidationMessageNameComparator
822 implements Comparator
823 {
824 private final Collator collator = Collator.getInstance();
825
826 ValidationMessageNameComparator()
827 {
828 collator.setStrength(Collator.PRIMARY);
829 }
830
831 public int compare(
832 final Object objectA,
833 final Object objectB)
834 {
835 final ModelValidationMessage a = (ModelValidationMessage)objectA;
836 final ModelValidationMessage b = (ModelValidationMessage)objectB;
837 return collator.compare(
838 StringUtils.trimToEmpty(a.getMetafacadeName()),
839 StringUtils.trimToEmpty(b.getMetafacadeName()));
840 }
841 }
842 }