Coverage Report - org.apache.maven.plugin.changes.ChangesMojo
 
Classes in this File Line Coverage Branch Coverage Complexity
ChangesMojo
0%
0/134
0%
0/38
3.5
 
 1  
 package org.apache.maven.plugin.changes;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *   http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 20  
  */
 21  
 
 22  
 import java.io.File;
 23  
 import java.io.FileWriter;
 24  
 import java.io.IOException;
 25  
 import java.io.Writer;
 26  
 
 27  
 import java.net.URL;
 28  
 
 29  
 import java.text.SimpleDateFormat;
 30  
 
 31  
 import java.util.Collections;
 32  
 import java.util.Date;
 33  
 import java.util.Iterator;
 34  
 import java.util.List;
 35  
 import java.util.Locale;
 36  
 import java.util.Map;
 37  
 import java.util.Properties;
 38  
 import java.util.ResourceBundle;
 39  
 
 40  
 import org.apache.commons.collections.map.CaseInsensitiveMap;
 41  
 import org.apache.maven.execution.MavenSession;
 42  
 import org.apache.maven.plugins.annotations.Component;
 43  
 import org.apache.maven.plugins.annotations.Mojo;
 44  
 import org.apache.maven.plugins.annotations.Parameter;
 45  
 import org.apache.maven.project.MavenProject;
 46  
 import org.apache.maven.reporting.MavenReportException;
 47  
 import org.apache.maven.shared.filtering.MavenFileFilter;
 48  
 import org.apache.maven.shared.filtering.MavenFileFilterRequest;
 49  
 import org.apache.maven.shared.filtering.MavenFilteringException;
 50  
 
 51  
 import org.codehaus.plexus.util.FileUtils;
 52  
 import org.codehaus.plexus.util.IOUtil;
 53  
 import org.codehaus.plexus.util.StringUtils;
 54  
 import org.apache.commons.io.input.XmlStreamReader;
 55  
 
 56  
 /**
 57  
  * Goal which creates a nicely formatted Changes Report in html format from a changes.xml file.
 58  
  *
 59  
  * @author <a href="mailto:jruiz@exist.com">Johnny R. Ruiz III</a>
 60  
  * @version $Id: ChangesMojo.java 1423355 2012-12-18 09:03:16Z ltheussl $
 61  
  */
 62  
 @Mojo( name = "changes-report", threadSafe = true )
 63  0
 public class ChangesMojo
 64  
     extends AbstractChangesReport
 65  
 {
 66  
     /**
 67  
      * A flag whether the report should also include changes from child modules. If set to <code>false</code>, only
 68  
      * the changes from current project will be written to the report.
 69  
      *
 70  
      * @since 2.5
 71  
      */
 72  
     @Parameter( defaultValue = "false" )
 73  
     private boolean aggregated;
 74  
 
 75  
     /**
 76  
      * A flag whether the report should also include the dates of individual actions. If set to <code>false</code>, only
 77  
      * the dates of releases will be written to the report.
 78  
      *
 79  
      * @since 2.1
 80  
      */
 81  
     @Parameter( property = "changes.addActionDate", defaultValue = "false" )
 82  
     private boolean addActionDate;
 83  
 
 84  
     /**
 85  
      * Whether HTML code within an action should be escaped. By changing this to
 86  
      * <code>false</code> you can restore the behavior that was in version 2.2
 87  
      * of this plugin, allowing you to use HTML code to format the content of an
 88  
      * action.
 89  
      * <p>
 90  
      * <strong>Note:</strong> If you use HTML code in an action you need to
 91  
      * place it inside a CDATA section.
 92  
      * </p>
 93  
      * <strong>Note:</strong> Putting any kind of markup inside a CDATA section
 94  
      * might mess up the Changes Report or other generated documents, such as
 95  
      * PDFs, that are based on your <code>changes.xml</code> file if you are not
 96  
      * careful.
 97  
      *
 98  
      * @since 2.4
 99  
      * @deprecated using markup inside CDATA sections does not work for all output formats!
 100  
      */
 101  
     @Parameter( defaultValue = "true" )
 102  
     private boolean escapeHTML;
 103  
 
 104  
     /**
 105  
      * The directory for interpolated changes.xml.
 106  
      *
 107  
      * @since 2.2
 108  
      */
 109  
     @Parameter( defaultValue = "${project.build.directory}/changes", required = true, readonly = true )
 110  
     private File filteredOutputDirectory;
 111  
 
 112  
     /**
 113  
      * applying filtering filtering "a la" resources plugin
 114  
      *
 115  
      * @since 2.2
 116  
      */
 117  
     @Parameter( defaultValue = "false" )
 118  
     private boolean filteringChanges;
 119  
 
 120  
     /**
 121  
      * Template string that is used to discover the URL to use to display an issue report.
 122  
      * There are 2 template tokens you can use. <code>%URL%</code>: this is computed by getting the
 123  
      * <code>&lt;issueManagement&gt;/&lt;url&gt;</code> value from the POM, and removing the last '/'
 124  
      * and everything that comes after it. <code>%ISSUE%</code>: this is the issue number.
 125  
      * <p>
 126  
      * <strong>Note:</strong> In versions of this plugin prior to 2.0-beta-2 this parameter was called
 127  
      * <code>link_template</code>.
 128  
      * </p>
 129  
      *
 130  
      * @since 2.0-beta-2
 131  
      * @deprecated As of 2.1 use issueLinkTemplatePerSystem : this one will be with system default
 132  
      */
 133  
     @Parameter( property = "changes.issueLinkTemplate", defaultValue = "%URL%/ViewIssue.jspa?key=%ISSUE%" )
 134  
     private String issueLinkTemplate;
 135  
 
 136  
     /**
 137  
      * Template strings per system that is used to discover the URL to use to display an issue report. Each key in this
 138  
      * map denotes the (case-insensitive) identifier of the issue tracking system and its value gives the URL template.
 139  
      * <p>
 140  
      * There are 2 template tokens you can use. <code>%URL%</code>: this is computed by getting the
 141  
      * <code>&lt;issueManagement&gt;/&lt;url&gt;</code> value from the POM, and removing the last '/'
 142  
      * and everything that comes after it. <code>%ISSUE%</code>: this is the issue number.
 143  
      * </p>
 144  
      * <p>
 145  
      * <strong>Note:</strong> The deprecated issueLinkTemplate will be used for a system called "default".
 146  
      * </p>
 147  
      * <p>
 148  
      * <strong>Note:</strong> Starting with version 2.4 you usually don't need
 149  
      * to specify this, unless you need to link to an issue management system in
 150  
      * your Changes report that isn't supported out of the box. See the
 151  
      * <a href="./usage.html">Usage page</a> for more
 152  
      * information.
 153  
      * </p>
 154  
      *
 155  
      * @since 2.1
 156  
      */
 157  
     @Parameter
 158  
     private Map issueLinkTemplatePerSystem;
 159  
 
 160  
     /**
 161  
      * @since 2.2
 162  
      */
 163  
     @Component
 164  
     private MavenFileFilter mavenFileFilter;
 165  
 
 166  
     /**
 167  
      * Format to use for publishDate. The value will be available with the following expression ${publishDate}
 168  
      *
 169  
      * @see java.text.SimpleDateFormat
 170  
      * @since 2.2
 171  
      */
 172  
     @Parameter( defaultValue = "yyyy-MM-dd" )
 173  
     private String publishDateFormat;
 174  
 
 175  
     /**
 176  
      * Locale to use for publishDate when formatting
 177  
      *
 178  
      * @see java.util.Locale
 179  
      * @since 2.2
 180  
      */
 181  
     @Parameter( defaultValue = "en" )
 182  
     private String publishDateLocale;
 183  
 
 184  
     /**
 185  
      * @since 2.2
 186  
      */
 187  
     @Component
 188  
     protected MavenSession session;
 189  
 
 190  
     /**
 191  
      * @since 2.4
 192  
      */
 193  
     @Parameter( defaultValue = "${project.issueManagement.system}", readonly = true )
 194  
     private String system;
 195  
 
 196  
     /**
 197  
      * The URI of a file containing all the team members. If this is set to the
 198  
      * special value "none", no links will be generated for the team members.
 199  
      *
 200  
      * @since 2.4
 201  
      */
 202  
     @Parameter( defaultValue = "team-list.html" )
 203  
     private String teamlist;
 204  
 
 205  
     /**
 206  
      */
 207  
     @Parameter( defaultValue = "${project.issueManagement.url}", readonly = true )
 208  
     private String url;
 209  
 
 210  
     /**
 211  
      * The type of the feed to generate.
 212  
      *
 213  
      * <p>
 214  
      * Supported values are:
 215  
      * <code>"rss_0.9", "rss_0.91N" (RSS 0.91 Netscape), "rss_0.91U" (RSS 0.91 Userland),
 216  
      * "rss_0.92", "rss_0.93", "rss_0.94", "rss_1.0", "rss_2.0", "atom_0.3", "atom_1.0"</code>.
 217  
      * </p>
 218  
      *
 219  
      * <p>If not specified, no feed is generated.</p>
 220  
      *
 221  
      * @since 2.9
 222  
      */
 223  
     @Parameter
 224  
     private String feedType;
 225  
 
 226  
     /**
 227  
      * The path of the <code>changes.xml</code> file that will be converted into an HTML report.
 228  
      */
 229  
     @Parameter( property = "changes.xmlPath", defaultValue = "src/changes/changes.xml" )
 230  
     private File xmlPath;
 231  
 
 232  0
     private ReleaseUtils releaseUtils = new ReleaseUtils( getLog() );
 233  
 
 234  
     private CaseInsensitiveMap caseInsensitiveIssueLinkTemplatePerSystem;
 235  
 
 236  
     /* --------------------------------------------------------------------- */
 237  
     /* Public methods                                                        */
 238  
     /* --------------------------------------------------------------------- */
 239  
 
 240  
     public boolean canGenerateReport()
 241  
     {
 242  0
         return xmlPath.isFile();
 243  
     }
 244  
 
 245  
     public void executeReport( Locale locale )
 246  
         throws MavenReportException
 247  
     {
 248  0
         Date now = new Date();
 249  0
         SimpleDateFormat simpleDateFormat =
 250  
                 new SimpleDateFormat(publishDateFormat, new Locale(publishDateLocale));
 251  0
         Properties additionalProperties = new Properties();
 252  0
         additionalProperties.put("publishDate", simpleDateFormat.format(now));
 253  
 
 254  0
         ChangesXML changesXml = getChangesFromFile( xmlPath, project, additionalProperties);
 255  0
         if ( changesXml == null ) return;
 256  
 
 257  0
         if ( aggregated )
 258  
         {
 259  0
             final String basePath = project.getBasedir().getAbsolutePath();
 260  0
             final String absolutePath = xmlPath.getAbsolutePath();
 261  0
             if ( !absolutePath.startsWith( basePath ) )
 262  
             {
 263  0
                 getLog().warn( "xmlPath should be within the project dir for aggregated changes report." );
 264  0
                 return;
 265  
             }
 266  0
             final String relativePath = absolutePath.substring( basePath.length() );
 267  
 
 268  0
             List releaseList = changesXml.getReleaseList();
 269  0
             for ( Iterator iterator = project.getCollectedProjects().iterator(); iterator.hasNext(); )
 270  
             {
 271  0
                 final MavenProject childProject = (MavenProject) iterator.next();
 272  0
                 final File changesFile = new File( childProject.getBasedir(), relativePath );
 273  0
                 final ChangesXML childXml = getChangesFromFile( changesFile, childProject, additionalProperties );
 274  0
                 if ( childXml != null )
 275  
                 {
 276  0
                     releaseList = releaseUtils.mergeReleases( releaseList, childProject.getName(), childXml.getReleaseList() );
 277  
                 }
 278  0
             }
 279  0
             changesXml.setReleaseList( releaseList );
 280  
         }
 281  
 
 282  0
         ChangesReportGenerator report = new ChangesReportGenerator( changesXml.getReleaseList() );
 283  
 
 284  0
         report.setAuthor( changesXml.getAuthor() );
 285  0
         report.setTitle( changesXml.getTitle() );
 286  
 
 287  0
         report.setEscapeHTML ( escapeHTML );
 288  
 
 289  
         // Create a case insensitive version of issueLinkTemplatePerSystem
 290  
         // We need something case insensitive to maintain backward compatibility
 291  0
         if ( issueLinkTemplatePerSystem == null )
 292  
         {
 293  0
             caseInsensitiveIssueLinkTemplatePerSystem = new CaseInsensitiveMap();
 294  
         }
 295  
         else
 296  
         {
 297  0
             caseInsensitiveIssueLinkTemplatePerSystem = new CaseInsensitiveMap( issueLinkTemplatePerSystem );
 298  
         }
 299  
 
 300  
         // Set good default values for issue management systems here, but only
 301  
         // if they have not been configured already by the user
 302  0
         addIssueLinkTemplate( ChangesReportGenerator.DEFAULT_ISSUE_SYSTEM_KEY, issueLinkTemplate );
 303  0
         addIssueLinkTemplate( "Bitbucket", "%URL%/issue/%ISSUE%" );
 304  0
         addIssueLinkTemplate( "Bugzilla", "%URL%/show_bug.cgi?id=%ISSUE%" );
 305  0
         addIssueLinkTemplate( "GitHub", "%URL%/%ISSUE%" );
 306  0
         addIssueLinkTemplate( "GoogleCode", "%URL%/detail?id=%ISSUE%" );
 307  0
         addIssueLinkTemplate( "JIRA", "%URL%/%ISSUE%" );
 308  0
         addIssueLinkTemplate( "Mantis", "%URL%/view.php?id=%ISSUE%" );
 309  0
         addIssueLinkTemplate( "MKS", "%URL%/viewissue?selection=%ISSUE%" );
 310  0
         addIssueLinkTemplate( "Redmine", "%URL%/issues/show/%ISSUE%" );
 311  0
         addIssueLinkTemplate( "Scarab", "%URL%/issues/id/%ISSUE%" );
 312  0
         addIssueLinkTemplate( "SourceForge", "http://sourceforge.net/support/tracker.php?aid=%ISSUE%" );
 313  0
         addIssueLinkTemplate( "SourceForge2", "%URL%/%ISSUE%" );
 314  0
         addIssueLinkTemplate( "Trac", "%URL%/ticket/%ISSUE%" );
 315  0
         addIssueLinkTemplate( "Trackplus", "%URL%/printItem.action?key=%ISSUE%" );
 316  0
         addIssueLinkTemplate( "YouTrack", "%URL%/issue/%ISSUE%" );
 317  
         // @todo Add more issue management systems here
 318  
         // Remember to also add documentation in usage.apt.vm
 319  
 
 320  
         // Show the current issueLinkTemplatePerSystem configuration
 321  0
         logIssueLinkTemplatePerSystem( caseInsensitiveIssueLinkTemplatePerSystem );
 322  
 
 323  0
         report.setIssueLinksPerSystem( caseInsensitiveIssueLinkTemplatePerSystem );
 324  
 
 325  0
         report.setSystem( system );
 326  
 
 327  0
         report.setTeamlist ( teamlist );
 328  
 
 329  0
         report.setUrl( url );
 330  
 
 331  0
         report.setAddActionDate( addActionDate );
 332  
 
 333  0
         if ( StringUtils.isEmpty( url ) )
 334  
         {
 335  0
             getLog().warn( "No issue management URL defined in POM. Links to your issues will not work correctly." );
 336  
         }
 337  
 
 338  0
         boolean feedGenerated = false;
 339  
 
 340  0
         if ( StringUtils.isNotEmpty( feedType ) )
 341  
         {
 342  0
             feedGenerated = generateFeed( changesXml, locale );
 343  
         }
 344  
 
 345  0
         report.setLinkToFeed( feedGenerated );
 346  
 
 347  0
         report.doGenerateReport( getBundle( locale ), getSink() );
 348  
 
 349  
         // Copy the images
 350  0
         copyStaticResources();
 351  0
     }
 352  
 
 353  
     public String getDescription( Locale locale )
 354  
     {
 355  0
         return getBundle( locale ).getString( "report.issues.description" );
 356  
     }
 357  
 
 358  
     public String getName( Locale locale )
 359  
     {
 360  0
         return getBundle( locale ).getString( "report.issues.name" );
 361  
     }
 362  
 
 363  
     public String getOutputName()
 364  
     {
 365  0
         return "changes-report";
 366  
     }
 367  
 
 368  
     /* --------------------------------------------------------------------- */
 369  
     /* Private methods                                                       */
 370  
     /* --------------------------------------------------------------------- */
 371  
 
 372  
     /**
 373  
      * Parses specified changes.xml file. It also makes filtering if needed. If specified file doesn't exist
 374  
      * it will log warning and return <code>null</code>.
 375  
      *
 376  
      * @param changesXml changes xml file to parse
 377  
      * @param project maven project to parse changes for
 378  
      * @param additionalProperties additional properties used for filtering
 379  
      * @return parsed <code>ChangesXML</code> instance or null if file doesn't exist
 380  
      * @throws MavenReportException if any errors occurs while parsing
 381  
      */
 382  
     private ChangesXML getChangesFromFile( File changesXml, MavenProject project, Properties additionalProperties )
 383  
         throws MavenReportException
 384  
     {
 385  0
         if ( !changesXml.exists() )
 386  
         {
 387  0
             getLog().warn( "changes.xml file " + changesXml.getAbsolutePath() + " does not exist." );
 388  0
             return null;
 389  
         }
 390  
 
 391  0
         if ( filteringChanges )
 392  
         {
 393  0
             if ( !filteredOutputDirectory.exists() )
 394  
             {
 395  0
                 filteredOutputDirectory.mkdirs();
 396  
             }
 397  0
             XmlStreamReader xmlStreamReader = null;
 398  
             try
 399  
             {
 400  
                 // so we get encoding from the file itself
 401  0
                 xmlStreamReader = new XmlStreamReader( changesXml );
 402  0
                 String encoding = xmlStreamReader.getEncoding();
 403  0
                 File resultFile = new File( filteredOutputDirectory, project.getGroupId() + "." + project.getArtifactId() + "-changes.xml" );
 404  
 
 405  0
                 final MavenFileFilterRequest mavenFileFilterRequest =
 406  
                         new MavenFileFilterRequest( changesXml, resultFile, true, project, Collections.EMPTY_LIST, false,
 407  
                                 encoding, session, additionalProperties );
 408  0
                 mavenFileFilter.copyFile( mavenFileFilterRequest );
 409  0
                 changesXml = resultFile;
 410  
             }
 411  0
             catch ( IOException e )
 412  
             {
 413  0
                 throw new MavenReportException( "Exception during filtering changes file : " + e.getMessage(), e );
 414  
             }
 415  0
             catch ( MavenFilteringException e )
 416  
             {
 417  0
                 throw new MavenReportException( "Exception during filtering changes file : " + e.getMessage(), e );
 418  
             }
 419  
             finally
 420  
             {
 421  0
                 if ( xmlStreamReader != null )
 422  
                 {
 423  0
                     IOUtil.close( xmlStreamReader );
 424  
                 }
 425  
             }
 426  
 
 427  
         }
 428  0
         return new ChangesXML( changesXml, getLog() );
 429  
     }
 430  
 
 431  
     /**
 432  
      * Add the issue link template for the given issue management system,
 433  
      * but only if it has not already been configured.
 434  
      *
 435  
      * @param system The issue management system
 436  
      * @param issueLinkTemplate The issue link template to use
 437  
      * @since 2.4
 438  
      */
 439  
     private void addIssueLinkTemplate( String system, String issueLinkTemplate )
 440  
     {
 441  0
         if ( caseInsensitiveIssueLinkTemplatePerSystem == null )
 442  
         {
 443  0
             caseInsensitiveIssueLinkTemplatePerSystem = new CaseInsensitiveMap();
 444  
         }
 445  0
         if ( !caseInsensitiveIssueLinkTemplatePerSystem.containsKey( system ) )
 446  
         {
 447  0
             caseInsensitiveIssueLinkTemplatePerSystem.put( system, issueLinkTemplate );
 448  
         }
 449  0
     }
 450  
 
 451  
     private void copyStaticResources()
 452  
         throws MavenReportException
 453  
     {
 454  0
         final String pluginResourcesBase = "org/apache/maven/plugin/changes";
 455  0
         String resourceNames[] = {
 456  
             "images/add.gif",
 457  
             "images/fix.gif",
 458  
             "images/icon_help_sml.gif",
 459  
             "images/remove.gif",
 460  
             "images/rss.png",
 461  
             "images/update.gif" };
 462  
         try
 463  
         {
 464  0
             getLog().debug( "Copying static resources." );
 465  0
             for ( int i = 0; i < resourceNames.length; i++ )
 466  
             {
 467  0
                 URL url = this.getClass().getClassLoader().getResource( pluginResourcesBase + "/" + resourceNames[i] );
 468  0
                 FileUtils.copyURLToFile( url, new File( getReportOutputDirectory(), resourceNames[i] ) );
 469  
             }
 470  
         }
 471  0
         catch ( IOException e )
 472  
         {
 473  0
             throw new MavenReportException( "Unable to copy static resources." );
 474  0
         }
 475  0
     }
 476  
 
 477  
     private ResourceBundle getBundle( Locale locale )
 478  
     {
 479  0
         return ResourceBundle.getBundle( "changes-report", locale, this.getClass().getClassLoader() );
 480  
     }
 481  
 
 482  
     protected String getTeamlist()
 483  
     {
 484  0
         return teamlist;
 485  
     }
 486  
 
 487  
     private void logIssueLinkTemplatePerSystem( Map issueLinkTemplatePerSystem )
 488  
     {
 489  0
         if ( getLog().isDebugEnabled() )
 490  
         {
 491  0
             if ( issueLinkTemplatePerSystem == null )
 492  
             {
 493  0
                 getLog().debug( "No issueLinkTemplatePerSystem configuration was found" );
 494  
             }
 495  
             else
 496  
             {
 497  0
                 Iterator iterator = issueLinkTemplatePerSystem.entrySet().iterator();
 498  0
                 while ( iterator.hasNext() )
 499  
                 {
 500  0
                     Map.Entry entry = (Map.Entry) iterator.next();
 501  0
                     getLog().debug( "issueLinkTemplatePerSystem[" + entry.getKey() + "] = " + entry.getValue() );
 502  0
                 }
 503  
             }
 504  
         }
 505  0
     }
 506  
 
 507  
     private boolean generateFeed( final ChangesXML changesXml, final Locale locale )
 508  
     {
 509  0
         getLog().debug( "Generating " + feedType + " feed." );
 510  
 
 511  0
         boolean success = true;
 512  
 
 513  0
         final FeedGenerator feed = new FeedGenerator( locale );
 514  0
         feed.setLink( project.getUrl() + "/changes-report.html" ); // TODO: better way?
 515  0
         feed.setTitle( project.getName() + ": " + changesXml.getTitle() );
 516  0
         feed.setAuthor( changesXml.getAuthor() );
 517  0
         feed.setDateFormat( new SimpleDateFormat( publishDateFormat, new Locale( publishDateLocale ) ) );
 518  
 
 519  0
         Writer writer = null;
 520  
 
 521  
         try
 522  
         {
 523  0
             writer = new FileWriter( new File( getReportOutputDirectory(), "changes.rss" ) );
 524  0
             feed.export( changesXml.getReleaseList(), feedType, writer );
 525  
         }
 526  0
         catch ( IOException ex )
 527  
         {
 528  0
             success = false;
 529  0
             getLog().warn( "Failed to create rss feed: " + ex.getMessage() );
 530  0
             getLog().debug( ex );
 531  
         }
 532  
         finally
 533  
         {
 534  0
             try
 535  
             {
 536  0
                 if ( writer != null )
 537  
                 {
 538  0
                     writer.close();
 539  
                 }
 540  
             }
 541  0
             catch ( IOException ex )
 542  
             {
 543  0
                 getLog().warn( "Failed to close writer: " + ex.getMessage() );
 544  0
                 getLog().debug( ex );
 545  0
             }
 546  0
         }
 547  
 
 548  0
         return success;
 549  
     }
 550  
 }