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}