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