001package org.apache.maven.tools.plugin.generator;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.File;
023import java.io.IOException;
024import java.io.OutputStreamWriter;
025import java.io.Writer;
026import java.net.URI;
027import java.util.LinkedHashMap;
028import java.util.LinkedHashSet;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032
033import org.apache.maven.plugin.descriptor.MojoDescriptor;
034import org.apache.maven.plugin.descriptor.Parameter;
035import org.apache.maven.plugin.descriptor.PluginDescriptor;
036import org.apache.maven.plugin.descriptor.Requirement;
037import org.apache.maven.project.MavenProject;
038import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
039import org.apache.maven.tools.plugin.PluginToolsRequest;
040import org.apache.maven.tools.plugin.javadoc.JavadocLinkGenerator;
041import org.apache.maven.tools.plugin.util.PluginUtils;
042import org.codehaus.plexus.util.StringUtils;
043import org.codehaus.plexus.util.io.CachingOutputStream;
044import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
045import org.codehaus.plexus.util.xml.XMLWriter;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049import static java.nio.charset.StandardCharsets.UTF_8;
050
051/**
052 * Serializes
053 * <ol>
054 * <li>a standard <a href="/ref/current/maven-plugin-api/plugin.html">Maven Plugin Descriptor XML file</a></li>
055 * <li>a descriptor containing a limited set of attributes for {@link PluginHelpGenerator}</li>
056 * <li>an enhanced descriptor containing HTML values for some elements (instead of plain text as for the other two)
057 * for {@link PluginXdocGenerator}</li>
058 * </ol>
059 * from a given in-memory descriptor. The in-memory descriptor acting as source is supposed to contain XHTML values
060 * for description elements.
061 *
062 */
063public class PluginDescriptorFilesGenerator
064    implements Generator
065{
066    private static final Logger LOG = LoggerFactory.getLogger( PluginDescriptorFilesGenerator.class );
067
068    /**
069     * The type of the plugin descriptor file
070     */
071    enum DescriptorType
072    {
073        STANDARD,
074        LIMITED_FOR_HELP_MOJO,
075        XHTML
076    }
077
078    @Override
079    public void execute( File destinationDirectory, PluginToolsRequest request )
080        throws GeneratorException
081    {
082        try
083        {
084            // write standard plugin.xml descriptor
085            File f = new File( destinationDirectory, "plugin.xml" );
086            writeDescriptor( f, request, DescriptorType.STANDARD );
087
088            // write plugin-help.xml help-descriptor (containing only a limited set of attributes)
089            MavenProject mavenProject = request.getProject();
090            f = new File( destinationDirectory,
091                          PluginHelpGenerator.getPluginHelpPath( mavenProject ) );
092            writeDescriptor( f, request, DescriptorType.LIMITED_FOR_HELP_MOJO );
093
094            // write enhanced plugin-enhanced.xml descriptor (containing some XHTML values)
095            f = getEnhancedDescriptorFilePath( mavenProject );
096            writeDescriptor( f, request, DescriptorType.XHTML );
097        }
098        catch ( IOException e )
099        {
100            throw new GeneratorException( e.getMessage(), e );
101        }
102    }
103
104    public static File getEnhancedDescriptorFilePath( MavenProject project )
105    {
106        return new File( project.getBuild().getDirectory(), "plugin-enhanced.xml" );
107    }
108
109    private String getVersion()
110    {
111        Package p = this.getClass().getPackage();
112        String version = ( p == null ) ? null : p.getSpecificationVersion();
113        return ( version == null ) ? "SNAPSHOT" : version;
114    }
115
116    public void writeDescriptor( File destinationFile, PluginToolsRequest request, DescriptorType type )
117        throws IOException
118    {
119        PluginDescriptor pluginDescriptor = request.getPluginDescriptor();
120
121        if ( !destinationFile.getParentFile().exists() )
122        {
123            destinationFile.getParentFile().mkdirs();
124        }
125
126        try ( Writer writer = new OutputStreamWriter( new CachingOutputStream( destinationFile ), UTF_8 ) )
127        {
128            XMLWriter w = new PrettyPrintXMLWriter( writer, UTF_8.name(), null );
129            
130            final String additionalInfo;
131            switch ( type )
132            {
133                case LIMITED_FOR_HELP_MOJO:
134                    additionalInfo = " (for help'mojo with additional elements)";
135                    break;
136                case XHTML:
137                    additionalInfo = " (enhanced XHTML version with additional elements (used for plugin:report))";
138                    break;
139                default:
140                    additionalInfo = "";
141                    break;
142            }
143            w.writeMarkup( "\n<!-- Generated by maven-plugin-tools " + getVersion() 
144                           + additionalInfo + "-->\n\n" );
145
146            w.startElement( "plugin" );
147
148            GeneratorUtils.element( w, "name", pluginDescriptor.getName() );
149
150            GeneratorUtils.element( w, "description", pluginDescriptor.getDescription() );
151
152            GeneratorUtils.element( w, "groupId", pluginDescriptor.getGroupId() );
153
154            GeneratorUtils.element( w, "artifactId", pluginDescriptor.getArtifactId() );
155
156            GeneratorUtils.element( w, "version", pluginDescriptor.getVersion() );
157
158            GeneratorUtils.element( w, "goalPrefix", pluginDescriptor.getGoalPrefix() );
159
160            if ( type != DescriptorType.LIMITED_FOR_HELP_MOJO )
161            {
162                GeneratorUtils.element( w, "isolatedRealm", String.valueOf( pluginDescriptor.isIsolatedRealm() ) );
163
164                GeneratorUtils.element( w, "inheritedByDefault",
165                                        String.valueOf( pluginDescriptor.isInheritedByDefault() ) );
166            }
167
168            w.startElement( "mojos" );
169
170            final JavadocLinkGenerator javadocLinkGenerator;
171            if ( request.getInternalJavadocBaseUrl() != null 
172                 || ( request.getExternalJavadocBaseUrls() != null 
173                      && !request.getExternalJavadocBaseUrls().isEmpty() ) )
174            {
175                javadocLinkGenerator =  new JavadocLinkGenerator( request.getInternalJavadocBaseUrl(),
176                                                                  request.getInternalJavadocVersion(),
177                                                                  request.getExternalJavadocBaseUrls(),
178                                                                  request.getSettings() );
179            }
180            else
181            {
182                javadocLinkGenerator = null;
183            }
184            if ( pluginDescriptor.getMojos() != null )
185            {
186                List<MojoDescriptor> descriptors = pluginDescriptor.getMojos();
187
188                PluginUtils.sortMojos( descriptors );
189
190                for ( MojoDescriptor descriptor : descriptors )
191                {
192                    processMojoDescriptor( descriptor, w, type, javadocLinkGenerator );
193                }
194            }
195
196            w.endElement();
197
198            if ( type != DescriptorType.LIMITED_FOR_HELP_MOJO )
199            {
200                GeneratorUtils.writeDependencies( w, pluginDescriptor );
201            }
202
203            w.endElement();
204
205            writer.flush();
206
207        }
208    }
209
210    /**
211     * 
212     * @param type
213     * @param containsXhtmlValue
214     * @param text
215     * @return the normalized text value (i.e. potentially converted to XHTML)
216     */
217    private static String getTextValue( DescriptorType type, boolean containsXhtmlValue, String text )
218    {
219        final String xhtmlText;
220        if ( !containsXhtmlValue ) // text comes from legacy extractor
221        {
222            xhtmlText = GeneratorUtils.makeHtmlValid( text );
223        }
224        else
225        {
226            xhtmlText = text;
227        }
228        if ( type != DescriptorType.XHTML )
229        {
230            return new HtmlToPlainTextConverter().convert( text );
231        }
232        else
233        {
234            return xhtmlText;
235        }
236    }
237
238    @SuppressWarnings( "deprecation" )
239    protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w, DescriptorType type,
240                                          JavadocLinkGenerator javadocLinkGenerator )
241    {
242        boolean containsXhtmlTextValues = mojoDescriptor instanceof ExtendedMojoDescriptor
243                        && ( (ExtendedMojoDescriptor) mojoDescriptor ).containsXhtmlTextValues();
244        
245        w.startElement( "mojo" );
246
247        // ----------------------------------------------------------------------
248        //
249        // ----------------------------------------------------------------------
250
251        w.startElement( "goal" );
252        w.writeText( mojoDescriptor.getGoal() );
253        w.endElement();
254
255        // ----------------------------------------------------------------------
256        //
257        // ----------------------------------------------------------------------
258
259        String description = mojoDescriptor.getDescription();
260
261        if ( StringUtils.isNotEmpty( description ) )
262        {
263            w.startElement( "description" );
264            w.writeText( getTextValue( type, containsXhtmlTextValues, mojoDescriptor.getDescription() ) );
265            w.endElement();
266        }
267
268        // ----------------------------------------------------------------------
269        //
270        // ----------------------------------------------------------------------
271
272        if ( StringUtils.isNotEmpty( mojoDescriptor.isDependencyResolutionRequired() ) )
273        {
274            GeneratorUtils.element( w, "requiresDependencyResolution",
275                                    mojoDescriptor.isDependencyResolutionRequired() );
276        }
277
278        // ----------------------------------------------------------------------
279        //
280        // ----------------------------------------------------------------------
281
282        GeneratorUtils.element( w, "requiresDirectInvocation",
283                                String.valueOf( mojoDescriptor.isDirectInvocationOnly() ) );
284
285        // ----------------------------------------------------------------------
286        //
287        // ----------------------------------------------------------------------
288
289        GeneratorUtils.element( w, "requiresProject", String.valueOf( mojoDescriptor.isProjectRequired() ) );
290
291        // ----------------------------------------------------------------------
292        //
293        // ----------------------------------------------------------------------
294
295        GeneratorUtils.element( w, "requiresReports", String.valueOf( mojoDescriptor.isRequiresReports() ) );
296
297        // ----------------------------------------------------------------------
298        //
299        // ----------------------------------------------------------------------
300
301        GeneratorUtils.element( w, "aggregator", String.valueOf( mojoDescriptor.isAggregator() ) );
302
303        // ----------------------------------------------------------------------
304        //
305        // ----------------------------------------------------------------------
306
307        GeneratorUtils.element( w, "requiresOnline", String.valueOf( mojoDescriptor.isOnlineRequired() ) );
308
309        // ----------------------------------------------------------------------
310        //
311        // ----------------------------------------------------------------------
312
313        GeneratorUtils.element( w, "inheritedByDefault", String.valueOf( mojoDescriptor.isInheritedByDefault() ) );
314
315        // ----------------------------------------------------------------------
316        //
317        // ----------------------------------------------------------------------
318
319        if ( StringUtils.isNotEmpty( mojoDescriptor.getPhase() ) )
320        {
321            GeneratorUtils.element( w, "phase", mojoDescriptor.getPhase() );
322        }
323
324        // ----------------------------------------------------------------------
325        //
326        // ----------------------------------------------------------------------
327
328        if ( StringUtils.isNotEmpty( mojoDescriptor.getExecutePhase() ) )
329        {
330            GeneratorUtils.element( w, "executePhase", mojoDescriptor.getExecutePhase() );
331        }
332
333        if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteGoal() ) )
334        {
335            GeneratorUtils.element( w, "executeGoal", mojoDescriptor.getExecuteGoal() );
336        }
337
338        if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteLifecycle() ) )
339        {
340            GeneratorUtils.element( w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle() );
341        }
342
343        // ----------------------------------------------------------------------
344        //
345        // ----------------------------------------------------------------------
346
347        w.startElement( "implementation" );
348        w.writeText( mojoDescriptor.getImplementation() );
349        w.endElement();
350
351        // ----------------------------------------------------------------------
352        //
353        // ----------------------------------------------------------------------
354
355        w.startElement( "language" );
356        w.writeText( mojoDescriptor.getLanguage() );
357        w.endElement();
358
359        // ----------------------------------------------------------------------
360        //
361        // ----------------------------------------------------------------------
362
363        if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentConfigurator() ) )
364        {
365            w.startElement( "configurator" );
366            w.writeText( mojoDescriptor.getComponentConfigurator() );
367            w.endElement();
368        }
369
370        // ----------------------------------------------------------------------
371        //
372        // ----------------------------------------------------------------------
373
374        if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentComposer() ) )
375        {
376            w.startElement( "composer" );
377            w.writeText( mojoDescriptor.getComponentComposer() );
378            w.endElement();
379        }
380
381        // ----------------------------------------------------------------------
382        //
383        // ----------------------------------------------------------------------
384
385        w.startElement( "instantiationStrategy" );
386        w.writeText( mojoDescriptor.getInstantiationStrategy() );
387        w.endElement();
388
389        // ----------------------------------------------------------------------
390        // Strategy for handling repeated reference to mojo in
391        // the calculated (decorated, resolved) execution stack
392        // ----------------------------------------------------------------------
393        w.startElement( "executionStrategy" );
394        w.writeText( mojoDescriptor.getExecutionStrategy() );
395        w.endElement();
396
397        // ----------------------------------------------------------------------
398        //
399        // ----------------------------------------------------------------------
400
401        if ( mojoDescriptor.getSince() != null )
402        {
403            w.startElement( "since" );
404
405            if ( StringUtils.isEmpty( mojoDescriptor.getSince() ) )
406            {
407                w.writeText( "No version given" );
408            }
409            else
410            {
411                w.writeText( mojoDescriptor.getSince() );
412            }
413
414            w.endElement();
415        }
416
417        // ----------------------------------------------------------------------
418        //
419        // ----------------------------------------------------------------------
420
421        if ( mojoDescriptor.getDeprecated() != null )
422        {
423            w.startElement( "deprecated" );
424
425            if ( StringUtils.isEmpty( mojoDescriptor.getDeprecated() ) )
426            {
427                w.writeText( "No reason given" );
428            }
429            else
430            {
431                w.writeText( getTextValue( type, containsXhtmlTextValues, mojoDescriptor.getDeprecated() ) );
432            }
433
434            w.endElement();
435        }
436
437        // ----------------------------------------------------------------------
438        // Extended (3.0) descriptor
439        // ----------------------------------------------------------------------
440
441        if ( mojoDescriptor instanceof ExtendedMojoDescriptor )
442        {
443            ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor;
444            if ( extendedMojoDescriptor.getDependencyCollectionRequired() != null )
445            {
446                GeneratorUtils.element( w, "requiresDependencyCollection",
447                                        extendedMojoDescriptor.getDependencyCollectionRequired() );
448            }
449
450            GeneratorUtils.element( w, "threadSafe", String.valueOf( extendedMojoDescriptor.isThreadSafe() ) );
451        }
452
453        // ----------------------------------------------------------------------
454        // Parameters
455        // ----------------------------------------------------------------------
456
457        List<Parameter> parameters = mojoDescriptor.getParameters();
458
459        w.startElement( "parameters" );
460
461        Map<String, Requirement> requirements = new LinkedHashMap<>();
462
463        Set<Parameter> configuration = new LinkedHashSet<>();
464
465        if ( parameters != null )
466        {
467            if ( type == DescriptorType.LIMITED_FOR_HELP_MOJO )
468            {
469                PluginUtils.sortMojoParameters( parameters );
470            }
471
472            for ( Parameter parameter : parameters )
473            {
474                String expression = getExpression( parameter );
475
476                if ( StringUtils.isNotEmpty( expression ) && expression.startsWith( "${component." ) )
477                {
478                    // treat it as a component...a requirement, in other words.
479
480                    // remove "component." plus expression delimiters
481                    String role = expression.substring( "${component.".length(), expression.length() - 1 );
482
483                    String roleHint = null;
484
485                    int posRoleHintSeparator = role.indexOf( '#' );
486                    if ( posRoleHintSeparator > 0 )
487                    {
488                        roleHint = role.substring( posRoleHintSeparator + 1 );
489
490                        role = role.substring( 0, posRoleHintSeparator );
491                    }
492
493                    // TODO: remove deprecated expression
494                    requirements.put( parameter.getName(), new Requirement( role, roleHint ) );
495                }
496                else if ( parameter.getRequirement() != null )
497                {
498                    requirements.put( parameter.getName(), parameter.getRequirement() );
499                }
500                // don't show readonly parameters in help
501                else if ( type != DescriptorType.LIMITED_FOR_HELP_MOJO || parameter.isEditable() )
502                {
503                    // treat it as a normal parameter.
504
505                    w.startElement( "parameter" );
506
507                    GeneratorUtils.element( w, "name", parameter.getName() );
508
509                    if ( parameter.getAlias() != null )
510                    {
511                        GeneratorUtils.element( w, "alias", parameter.getAlias() );
512                    }
513
514                    writeParameterType( w, type, javadocLinkGenerator, parameter, mojoDescriptor.getGoal() );
515
516                    if ( parameter.getSince() != null )
517                    {
518                        w.startElement( "since" );
519
520                        if ( StringUtils.isEmpty( parameter.getSince() ) )
521                        {
522                            w.writeText( "No version given" );
523                        }
524                        else
525                        {
526                            w.writeText( parameter.getSince() );
527                        }
528
529                        w.endElement();
530                    }
531
532                    if ( parameter.getDeprecated() != null )
533                    {
534                        if ( StringUtils.isEmpty( parameter.getDeprecated() ) )
535                        {
536                            GeneratorUtils.element( w, "deprecated", "No reason given" );
537                        }
538                        else
539                        {
540                            GeneratorUtils.element( w, "deprecated", 
541                                                    getTextValue( type, containsXhtmlTextValues,
542                                                                  parameter.getDeprecated() ) );
543                        }
544                    }
545
546                    if ( parameter.getImplementation() != null )
547                    {
548                        GeneratorUtils.element( w, "implementation", parameter.getImplementation() );
549                    }
550
551                    GeneratorUtils.element( w, "required", Boolean.toString( parameter.isRequired() ) );
552
553                    GeneratorUtils.element( w, "editable", Boolean.toString( parameter.isEditable() ) );
554
555                    GeneratorUtils.element( w, "description", getTextValue( type, containsXhtmlTextValues,
556                                                                            parameter.getDescription() ) );
557
558                    if ( StringUtils.isNotEmpty( parameter.getDefaultValue() ) || StringUtils.isNotEmpty(
559                        parameter.getExpression() ) )
560                    {
561                        configuration.add( parameter );
562                    }
563
564                    w.endElement();
565                }
566
567            }
568        }
569
570        w.endElement();
571
572        // ----------------------------------------------------------------------
573        // Configuration
574        // ----------------------------------------------------------------------
575
576        if ( !configuration.isEmpty() )
577        {
578            w.startElement( "configuration" );
579
580            for ( Parameter parameter : configuration )
581            {
582                if ( type == DescriptorType.LIMITED_FOR_HELP_MOJO && !parameter.isEditable() )
583                {
584                    // don't show readonly parameters in help
585                    continue;
586                }
587
588                w.startElement( parameter.getName() );
589
590                // strip type by parameter type (generics) information
591                String parameterType = StringUtils.chomp( parameter.getType(), "<" );
592                if ( StringUtils.isNotEmpty( parameterType ) )
593                {
594                    w.addAttribute( "implementation", parameterType );
595                }
596
597                if ( parameter.getDefaultValue() != null )
598                {
599                    w.addAttribute( "default-value", parameter.getDefaultValue() );
600                }
601
602                if ( StringUtils.isNotEmpty( parameter.getExpression() ) )
603                {
604                    w.writeText( parameter.getExpression() );
605                }
606
607                w.endElement();
608            }
609
610            w.endElement();
611        }
612
613        // ----------------------------------------------------------------------
614        // Requirements
615        // ----------------------------------------------------------------------
616
617        if ( !requirements.isEmpty() && type != DescriptorType.LIMITED_FOR_HELP_MOJO )
618        {
619            w.startElement( "requirements" );
620
621            for ( Map.Entry<String, Requirement> entry : requirements.entrySet() )
622            {
623                String key = entry.getKey();
624                Requirement requirement = entry.getValue();
625
626                w.startElement( "requirement" );
627
628                GeneratorUtils.element( w, "role", requirement.getRole() );
629
630                if ( StringUtils.isNotEmpty( requirement.getRoleHint() ) )
631                {
632                    GeneratorUtils.element( w, "role-hint", requirement.getRoleHint() );
633                }
634
635                GeneratorUtils.element( w, "field-name", key );
636
637                w.endElement();
638            }
639
640            w.endElement();
641        }
642
643        w.endElement();
644    }
645
646    /**
647     * Writes parameter type information and potentially also the related javadoc URL.
648     * @param w
649     * @param type
650     * @param javadocLinkGenerator
651     * @param parameter
652     * @param goal
653     */
654    protected void writeParameterType( XMLWriter w, DescriptorType type, JavadocLinkGenerator javadocLinkGenerator,
655                                       Parameter parameter, String goal )
656    {
657        String parameterType = parameter.getType();
658        
659        if ( type == DescriptorType.STANDARD )
660        {
661            // strip type by parameter type (generics) information for standard plugin descriptor
662            parameterType = StringUtils.chomp( parameterType, "<" );
663        }
664        GeneratorUtils.element( w, "type", parameterType );
665
666        if ( type == DescriptorType.XHTML && javadocLinkGenerator != null )
667        {
668            // skip primitives which never has javadoc
669            if ( parameter.getType().indexOf( '.' ) == -1 )
670            {
671                LOG.debug( "Javadoc URLs are not available for primitive types like {}",
672                           parameter.getType() );
673            }
674            else
675            {
676                try
677                {
678                    URI javadocUrl = getJavadocUrlForType( javadocLinkGenerator, parameterType );
679                    GeneratorUtils.element( w, "typeJavadocUrl", javadocUrl.toString() );
680                } 
681                catch ( IllegalArgumentException e )
682                {
683                    LOG.warn( "Could not get javadoc URL for type {} of parameter {} from goal {}: {}",
684                              parameter.getType(), parameter.getName(), goal,
685                              e.getMessage() );
686                }
687            }
688        }
689    }
690
691    static URI getJavadocUrlForType( JavadocLinkGenerator javadocLinkGenerator, String type )
692    {
693        final String binaryName;
694        int startOfParameterType = type.indexOf( "<" );
695        if ( startOfParameterType != -1 )
696        {
697            // parse parameter type
698            String mainType = type.substring( 0, startOfParameterType );
699            
700            // some heuristics here
701            String[] parameterTypes = type.substring( startOfParameterType + 1, type.lastIndexOf( ">" ) )
702                            .split( ",\\s*" );
703            switch ( parameterTypes.length )
704            {
705                case 1: // if only one parameter type, assume collection, first parameter type is most interesting
706                    binaryName = parameterTypes[0];
707                    break;
708                case 2: // if two parameter types assume map, second parameter type is most interesting
709                    binaryName = parameterTypes[1];
710                    break;
711                default:
712                    // all other cases link to main type
713                    binaryName = mainType;
714            }
715        }
716        else
717        {
718            binaryName = type;
719        }
720        return javadocLinkGenerator.createLink( binaryName );
721    }
722
723    /**
724     * Get the expression value, eventually surrounding it with <code>${ }</code>.
725     *
726     * @param parameter the parameter
727     * @return the expression value
728     */
729    private String getExpression( Parameter parameter )
730    {
731        String expression = parameter.getExpression();
732        if ( StringUtils.isNotBlank( expression ) && !expression.contains( "${" ) )
733        {
734            expression = "${" + expression.trim() + "}";
735            parameter.setExpression( expression );
736        }
737        return expression;
738    }
739}