001package org.apache.maven.tools.plugin.extractor.javadoc;
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 javax.inject.Named;
023import javax.inject.Singleton;
024
025import java.io.File;
026import java.net.MalformedURLException;
027import java.net.URL;
028import java.net.URLClassLoader;
029import java.util.ArrayList;
030import java.util.Collection;
031import java.util.List;
032import java.util.Map;
033import java.util.TreeMap;
034
035import org.apache.maven.artifact.Artifact;
036import org.apache.maven.plugin.descriptor.InvalidParameterException;
037import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
038import org.apache.maven.plugin.descriptor.MojoDescriptor;
039import org.apache.maven.plugin.descriptor.Parameter;
040import org.apache.maven.plugin.descriptor.Requirement;
041import org.apache.maven.project.MavenProject;
042import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
043import org.apache.maven.tools.plugin.PluginToolsRequest;
044import org.apache.maven.tools.plugin.extractor.ExtractionException;
045import org.apache.maven.tools.plugin.extractor.GroupKey;
046import org.apache.maven.tools.plugin.extractor.MojoDescriptorExtractor;
047import org.apache.maven.tools.plugin.util.PluginUtils;
048import org.codehaus.plexus.logging.AbstractLogEnabled;
049import org.codehaus.plexus.util.StringUtils;
050
051import com.thoughtworks.qdox.JavaProjectBuilder;
052import com.thoughtworks.qdox.library.SortedClassLibraryBuilder;
053import com.thoughtworks.qdox.model.DocletTag;
054import com.thoughtworks.qdox.model.JavaClass;
055import com.thoughtworks.qdox.model.JavaField;
056import com.thoughtworks.qdox.model.JavaType;
057
058/**
059 * <p>
060 * Extracts Mojo descriptors from <a href="https://www.oracle.com/java/technologies//">Java</a> source
061 * javadoc comments only. New mojos should rather rely on annotations and comments which are evaluated
062 * by extractor named {@code java}.
063 * </p>
064 * For more information about the usage tag, have a look to:
065 * <a href="https://maven.apache.org/developers/mojo-api-specification.html">
066 * https://maven.apache.org/developers/mojo-api-specification.html</a>
067 *
068 * @see org.apache.maven.plugin.descriptor.MojoDescriptor
069 */
070@Named( JavaJavadocMojoDescriptorExtractor.NAME )
071@Singleton
072public class JavaJavadocMojoDescriptorExtractor
073    extends AbstractLogEnabled
074    implements MojoDescriptorExtractor, JavadocMojoAnnotation
075{
076    public static final String NAME = "java-javadoc";
077
078    private static final GroupKey GROUP_KEY = new GroupKey( GroupKey.JAVA_GROUP, 200 );
079
080    @Override
081    public String getName()
082    {
083        return NAME;
084    }
085
086    @Override
087    public boolean isDeprecated()
088    {
089        return true; // one should use Java5 annotations instead
090    }
091
092    @Override
093    public GroupKey getGroupKey()
094    {
095        return GROUP_KEY;
096    }
097
098    /**
099     * @param parameter not null
100     * @param i positive number
101     * @throws InvalidParameterException if any
102     */
103    protected void validateParameter( Parameter parameter, int i )
104        throws InvalidParameterException
105    {
106        // TODO: remove when backward compatibility is no longer an issue.
107        String name = parameter.getName();
108
109        if ( name == null )
110        {
111            throw new InvalidParameterException( "name", i );
112        }
113
114        // TODO: remove when backward compatibility is no longer an issue.
115        String type = parameter.getType();
116
117        if ( type == null )
118        {
119            throw new InvalidParameterException( "type", i );
120        }
121
122        // TODO: remove when backward compatibility is no longer an issue.
123        String description = parameter.getDescription();
124
125        if ( description == null )
126        {
127            throw new InvalidParameterException( "description", i );
128        }
129    }
130
131    // ----------------------------------------------------------------------
132    // Mojo descriptor creation from @tags
133    // ----------------------------------------------------------------------
134
135    /**
136     * @param javaClass not null
137     * @return a mojo descriptor
138     * @throws InvalidPluginDescriptorException if any
139     */
140    protected MojoDescriptor createMojoDescriptor( JavaClass javaClass )
141        throws InvalidPluginDescriptorException
142    {
143        ExtendedMojoDescriptor mojoDescriptor = new ExtendedMojoDescriptor();
144        mojoDescriptor.setLanguage( "java" );
145        mojoDescriptor.setImplementation( javaClass.getFullyQualifiedName() );
146        mojoDescriptor.setDescription( javaClass.getComment() );
147
148        // ----------------------------------------------------------------------
149        // Mojo annotations in alphabetical order
150        // ----------------------------------------------------------------------
151
152        // Aggregator flag
153        DocletTag aggregator = findInClassHierarchy( javaClass, JavadocMojoAnnotation.AGGREGATOR );
154        if ( aggregator != null )
155        {
156            mojoDescriptor.setAggregator( true );
157        }
158
159        // Configurator hint
160        DocletTag configurator = findInClassHierarchy( javaClass, JavadocMojoAnnotation.CONFIGURATOR );
161        if ( configurator != null )
162        {
163            mojoDescriptor.setComponentConfigurator( configurator.getValue() );
164        }
165
166        // Additional phase to execute first
167        DocletTag execute = findInClassHierarchy( javaClass, JavadocMojoAnnotation.EXECUTE );
168        if ( execute != null )
169        {
170            String executePhase = execute.getNamedParameter( JavadocMojoAnnotation.EXECUTE_PHASE );
171            String executeGoal = execute.getNamedParameter( JavadocMojoAnnotation.EXECUTE_GOAL );
172
173            if ( executePhase == null && executeGoal == null )
174            {
175                throw new InvalidPluginDescriptorException( javaClass.getFullyQualifiedName()
176                    + ": @execute tag requires either a 'phase' or 'goal' parameter" );
177            }
178            else if ( executePhase != null && executeGoal != null )
179            {
180                throw new InvalidPluginDescriptorException( javaClass.getFullyQualifiedName()
181                    + ": @execute tag can have only one of a 'phase' or 'goal' parameter" );
182            }
183            mojoDescriptor.setExecutePhase( executePhase );
184            mojoDescriptor.setExecuteGoal( executeGoal );
185
186            String lifecycle = execute.getNamedParameter( JavadocMojoAnnotation.EXECUTE_LIFECYCLE );
187            if ( lifecycle != null )
188            {
189                mojoDescriptor.setExecuteLifecycle( lifecycle );
190                if ( mojoDescriptor.getExecuteGoal() != null )
191                {
192                    throw new InvalidPluginDescriptorException( javaClass.getFullyQualifiedName()
193                        + ": @execute lifecycle requires a phase instead of a goal" );
194                }
195            }
196        }
197
198        // Goal name
199        DocletTag goal = findInClassHierarchy( javaClass, JavadocMojoAnnotation.GOAL );
200        if ( goal != null )
201        {
202            mojoDescriptor.setGoal( goal.getValue() );
203        }
204
205        // inheritByDefault flag
206        boolean value =
207            getBooleanTagValue( javaClass, JavadocMojoAnnotation.INHERIT_BY_DEFAULT,
208                                mojoDescriptor.isInheritedByDefault() );
209        mojoDescriptor.setInheritedByDefault( value );
210
211        // instantiationStrategy
212        DocletTag tag = findInClassHierarchy( javaClass, JavadocMojoAnnotation.INSTANTIATION_STRATEGY );
213        if ( tag != null )
214        {
215            mojoDescriptor.setInstantiationStrategy( tag.getValue() );
216        }
217
218        // executionStrategy (and deprecated @attainAlways)
219        tag = findInClassHierarchy( javaClass, JavadocMojoAnnotation.MULTI_EXECUTION_STRATEGY );
220        if ( tag != null )
221        {
222            getLogger().warn( "@" + JavadocMojoAnnotation.MULTI_EXECUTION_STRATEGY + " in "
223                                  + javaClass.getFullyQualifiedName() + " is deprecated: please use '@"
224                                  + JavadocMojoAnnotation.EXECUTION_STATEGY + " always' instead." );
225            mojoDescriptor.setExecutionStrategy( MojoDescriptor.MULTI_PASS_EXEC_STRATEGY );
226        }
227        else
228        {
229            mojoDescriptor.setExecutionStrategy( MojoDescriptor.SINGLE_PASS_EXEC_STRATEGY );
230        }
231        tag = findInClassHierarchy( javaClass, JavadocMojoAnnotation.EXECUTION_STATEGY );
232        if ( tag != null )
233        {
234            mojoDescriptor.setExecutionStrategy( tag.getValue() );
235        }
236
237        // Phase name
238        DocletTag phase = findInClassHierarchy( javaClass, JavadocMojoAnnotation.PHASE );
239        if ( phase != null )
240        {
241            mojoDescriptor.setPhase( phase.getValue() );
242        }
243
244        // Dependency resolution flag
245        DocletTag requiresDependencyResolution =
246            findInClassHierarchy( javaClass, JavadocMojoAnnotation.REQUIRES_DEPENDENCY_RESOLUTION );
247        if ( requiresDependencyResolution != null )
248        {
249            String v = requiresDependencyResolution.getValue();
250
251            if ( StringUtils.isEmpty( v ) )
252            {
253                v = "runtime";
254            }
255
256            mojoDescriptor.setDependencyResolutionRequired( v );
257        }
258
259        // Dependency collection flag
260        DocletTag requiresDependencyCollection =
261            findInClassHierarchy( javaClass, JavadocMojoAnnotation.REQUIRES_DEPENDENCY_COLLECTION );
262        if ( requiresDependencyCollection != null )
263        {
264            String v = requiresDependencyCollection.getValue();
265
266            if ( StringUtils.isEmpty( v ) )
267            {
268                v = "runtime";
269            }
270
271            mojoDescriptor.setDependencyCollectionRequired( v );
272        }
273
274        // requiresDirectInvocation flag
275        value =
276            getBooleanTagValue( javaClass, JavadocMojoAnnotation.REQUIRES_DIRECT_INVOCATION,
277                                mojoDescriptor.isDirectInvocationOnly() );
278        mojoDescriptor.setDirectInvocationOnly( value );
279
280        // Online flag
281        value =
282            getBooleanTagValue( javaClass, JavadocMojoAnnotation.REQUIRES_ONLINE, mojoDescriptor.isOnlineRequired() );
283        mojoDescriptor.setOnlineRequired( value );
284
285        // Project flag
286        value =
287            getBooleanTagValue( javaClass, JavadocMojoAnnotation.REQUIRES_PROJECT, mojoDescriptor.isProjectRequired() );
288        mojoDescriptor.setProjectRequired( value );
289
290        // requiresReports flag
291        value =
292            getBooleanTagValue( javaClass, JavadocMojoAnnotation.REQUIRES_REPORTS, mojoDescriptor.isRequiresReports() );
293        mojoDescriptor.setRequiresReports( value );
294
295        // ----------------------------------------------------------------------
296        // Javadoc annotations in alphabetical order
297        // ----------------------------------------------------------------------
298
299        // Deprecation hint
300        DocletTag deprecated = javaClass.getTagByName( JavadocMojoAnnotation.DEPRECATED );
301        if ( deprecated != null )
302        {
303            mojoDescriptor.setDeprecated( deprecated.getValue() );
304        }
305
306        // What version it was introduced in
307        DocletTag since = findInClassHierarchy( javaClass, JavadocMojoAnnotation.SINCE );
308        if ( since != null )
309        {
310            mojoDescriptor.setSince( since.getValue() );
311        }
312
313        // Thread-safe mojo 
314
315        value = getBooleanTagValue( javaClass, JavadocMojoAnnotation.THREAD_SAFE, true, mojoDescriptor.isThreadSafe() );
316        mojoDescriptor.setThreadSafe( value );
317
318        extractParameters( mojoDescriptor, javaClass );
319
320        return mojoDescriptor;
321    }
322
323    /**
324     * @param javaClass not null
325     * @param tagName not null
326     * @param defaultValue the wanted default value
327     * @return the boolean value of the given tagName
328     * @see #findInClassHierarchy(JavaClass, String)
329     */
330    private static boolean getBooleanTagValue( JavaClass javaClass, String tagName, boolean defaultValue )
331    {
332        DocletTag tag = findInClassHierarchy( javaClass, tagName );
333
334        if ( tag != null )
335        {
336            String value = tag.getValue();
337
338            if ( StringUtils.isNotEmpty( value ) )
339            {
340                defaultValue = Boolean.valueOf( value ).booleanValue();
341            }
342        }
343        return defaultValue;
344    }
345
346    /**
347     * @param javaClass     not null
348     * @param tagName       not null
349     * @param defaultForTag The wanted default value when only the tagname is present
350     * @param defaultValue  the wanted default value when the tag is not specified
351     * @return the boolean value of the given tagName
352     * @see #findInClassHierarchy(JavaClass, String)
353     */
354    private static boolean getBooleanTagValue( JavaClass javaClass, String tagName, boolean defaultForTag,
355                                               boolean defaultValue )
356    {
357        DocletTag tag = findInClassHierarchy( javaClass, tagName );
358
359        if ( tag != null )
360        {
361            String value = tag.getValue();
362
363            if ( StringUtils.isNotEmpty( value ) )
364            {
365                return Boolean.valueOf( value ).booleanValue();
366            }
367            else
368            {
369                return defaultForTag;
370            }
371        }
372        return defaultValue;
373    }
374
375    /**
376     * @param javaClass not null
377     * @param tagName not null
378     * @return docletTag instance
379     */
380    private static DocletTag findInClassHierarchy( JavaClass javaClass, String tagName )
381    {
382        DocletTag tag = javaClass.getTagByName( tagName );
383
384        if ( tag == null )
385        {
386            JavaClass superClass = javaClass.getSuperJavaClass();
387
388            if ( superClass != null )
389            {
390                tag = findInClassHierarchy( superClass, tagName );
391            }
392        }
393
394        return tag;
395    }
396
397    /**
398     * @param mojoDescriptor not null
399     * @param javaClass not null
400     * @throws InvalidPluginDescriptorException if any
401     */
402    private void extractParameters( MojoDescriptor mojoDescriptor, JavaClass javaClass )
403        throws InvalidPluginDescriptorException
404    {
405        // ---------------------------------------------------------------------------------
406        // We're resolving class-level, ancestor-class-field, local-class-field order here.
407        // ---------------------------------------------------------------------------------
408
409        Map<String, JavaField> rawParams = extractFieldParameterTags( javaClass );
410
411        for ( Map.Entry<String, JavaField> entry : rawParams.entrySet() )
412        {
413            JavaField field = entry.getValue();
414
415            JavaType type = field.getType();
416
417            Parameter pd = new Parameter();
418
419            pd.setName( entry.getKey() );
420
421            pd.setType( type.getFullyQualifiedName() );
422
423            pd.setDescription( field.getComment() );
424
425            DocletTag deprecationTag = field.getTagByName( JavadocMojoAnnotation.DEPRECATED );
426
427            if ( deprecationTag != null )
428            {
429                pd.setDeprecated( deprecationTag.getValue() );
430            }
431
432            DocletTag sinceTag = field.getTagByName( JavadocMojoAnnotation.SINCE );
433            if ( sinceTag != null )
434            {
435                pd.setSince( sinceTag.getValue() );
436            }
437
438            DocletTag componentTag = field.getTagByName( JavadocMojoAnnotation.COMPONENT );
439
440            if ( componentTag != null )
441            {
442                // Component tag
443                String role = componentTag.getNamedParameter( JavadocMojoAnnotation.COMPONENT_ROLE );
444
445                if ( role == null )
446                {
447                    role = field.getType().toString();
448                }
449
450                String roleHint = componentTag.getNamedParameter( JavadocMojoAnnotation.COMPONENT_ROLEHINT );
451
452                if ( roleHint == null )
453                {
454                    // support alternate syntax for better compatibility with the Plexus CDC.
455                    roleHint = componentTag.getNamedParameter( "role-hint" );
456                }
457
458                // recognize Maven-injected objects as components annotations instead of parameters
459                // Note: the expressions we are looking for, i.e. "${project}", are in the values of the Map,
460                // so the lookup mechanism is different here than in maven-plugin-tools-annotations
461                boolean isDeprecated = PluginUtils.MAVEN_COMPONENTS.containsValue( role );
462
463                if ( !isDeprecated )
464                {
465                    // normal component
466                    pd.setRequirement( new Requirement( role, roleHint ) );
467                }
468                else
469                {
470                    // not a component but a Maven object to be transformed into an expression/property
471                    getLogger().warn( "Deprecated @component Javadoc tag for '" + pd.getName() + "' field in "
472                                          + javaClass.getFullyQualifiedName()
473                                          + ": replace with @Parameter( defaultValue = \"" + role
474                                          + "\", readonly = true )" );
475                    pd.setDefaultValue( role );
476                    pd.setRequired( true );
477                }
478
479                pd.setEditable( false );
480                /* TODO: or better like this? Need @component fields be editable for the user?
481                pd.setEditable( field.getTagByName( READONLY ) == null );
482                */
483            }
484            else
485            {
486                // Parameter tag
487                DocletTag parameter = field.getTagByName( JavadocMojoAnnotation.PARAMETER );
488
489                pd.setRequired( field.getTagByName( JavadocMojoAnnotation.REQUIRED ) != null );
490
491                pd.setEditable( field.getTagByName( JavadocMojoAnnotation.READONLY ) == null );
492
493                String name = parameter.getNamedParameter( JavadocMojoAnnotation.PARAMETER_NAME );
494
495                if ( !StringUtils.isEmpty( name ) )
496                {
497                    pd.setName( name );
498                }
499
500                String alias = parameter.getNamedParameter( JavadocMojoAnnotation.PARAMETER_ALIAS );
501
502                if ( !StringUtils.isEmpty( alias ) )
503                {
504                    pd.setAlias( alias );
505                }
506
507                String expression = parameter.getNamedParameter( JavadocMojoAnnotation.PARAMETER_EXPRESSION );
508                String property = parameter.getNamedParameter( JavadocMojoAnnotation.PARAMETER_PROPERTY );
509
510                if ( StringUtils.isNotEmpty( expression ) && StringUtils.isNotEmpty( property ) )
511                {
512                    getLogger().error( javaClass.getFullyQualifiedName() + "#" + field.getName() + ":" );
513                    getLogger().error( "  Cannot use both:" );
514                    getLogger().error( "    @parameter expression=\"${property}\"" );
515                    getLogger().error( "  and" );
516                    getLogger().error( "    @parameter property=\"property\"" );
517                    getLogger().error( "  Second syntax is preferred." );
518                    throw new InvalidParameterException( javaClass.getFullyQualifiedName() + "#" + field.getName()
519                        + ": cannot" + " use both @parameter expression and property", null );
520                }
521
522                if ( StringUtils.isNotEmpty( expression ) )
523                {
524                    getLogger().warn( javaClass.getFullyQualifiedName() + "#" + field.getName() + ":" );
525                    getLogger().warn( "  The syntax" );
526                    getLogger().warn( "    @parameter expression=\"${property}\"" );
527                    getLogger().warn( "  is deprecated, please use" );
528                    getLogger().warn( "    @parameter property=\"property\"" );
529                    getLogger().warn( "  instead." );
530
531                }
532                else if ( StringUtils.isNotEmpty( property ) )
533                {
534                    expression = "${" + property + "}";
535                }
536
537                pd.setExpression( expression );
538
539                if ( StringUtils.isNotEmpty( expression ) && expression.startsWith( "${component." ) )
540                {
541                    getLogger().warn( javaClass.getFullyQualifiedName() + "#" + field.getName() + ":" );
542                    getLogger().warn( "  The syntax" );
543                    getLogger().warn( "    @parameter expression=\"${component.<role>#<roleHint>}\"" );
544                    getLogger().warn( "  is deprecated, please use" );
545                    getLogger().warn( "    @component role=\"<role>\" roleHint=\"<roleHint>\"" );
546                    getLogger().warn( "  instead." );
547                }
548
549                if ( "${reports}".equals( pd.getExpression() ) )
550                {
551                    mojoDescriptor.setRequiresReports( true );
552                }
553
554                pd.setDefaultValue( parameter.getNamedParameter( JavadocMojoAnnotation.PARAMETER_DEFAULT_VALUE ) );
555
556                pd.setImplementation( parameter.getNamedParameter( JavadocMojoAnnotation.PARAMETER_IMPLEMENTATION ) );
557            }
558
559            mojoDescriptor.addParameter( pd );
560        }
561    }
562
563    /**
564     * extract fields that are either parameters or components.
565     * 
566     * @param javaClass not null
567     * @return map with Mojo parameters names as keys
568     */
569    private Map<String, JavaField> extractFieldParameterTags( JavaClass javaClass )
570    {
571        Map<String, JavaField> rawParams;
572
573        // we have to add the parent fields first, so that they will be overwritten by the local fields if
574        // that actually happens...
575        JavaClass superClass = javaClass.getSuperJavaClass();
576
577        if ( superClass != null )
578        {
579            rawParams = extractFieldParameterTags( superClass );
580        }
581        else
582        {
583            rawParams = new TreeMap<String, JavaField>();
584        }
585
586        for ( JavaField field : javaClass.getFields() )
587        {
588            if ( field.getTagByName( JavadocMojoAnnotation.PARAMETER ) != null
589                || field.getTagByName( JavadocMojoAnnotation.COMPONENT ) != null )
590            {
591                rawParams.put( field.getName(), field );
592            }
593        }
594        return rawParams;
595    }
596
597
598    @Override
599    public List<MojoDescriptor> execute( PluginToolsRequest request )
600        throws ExtractionException, InvalidPluginDescriptorException
601    {
602        Collection<JavaClass> javaClasses = discoverClasses( request );
603
604        List<MojoDescriptor> descriptors = new ArrayList<>();
605
606        for ( JavaClass javaClass : javaClasses )
607        {
608            DocletTag tag = javaClass.getTagByName( GOAL );
609
610            if ( tag != null )
611            {
612                MojoDescriptor mojoDescriptor = createMojoDescriptor( javaClass );
613                mojoDescriptor.setPluginDescriptor( request.getPluginDescriptor() );
614
615                // Validate the descriptor as best we can before allowing it to be processed.
616                validate( mojoDescriptor );
617
618                descriptors.add( mojoDescriptor );
619            }
620        }
621
622        return descriptors;
623    }
624
625    /**
626     * @param request The plugin request.
627     * @return an array of java class
628     */
629    protected Collection<JavaClass> discoverClasses( final PluginToolsRequest request )
630    {
631        JavaProjectBuilder builder = new JavaProjectBuilder( new SortedClassLibraryBuilder() );
632        builder.setEncoding( request.getEncoding() );
633        
634         // Build isolated Classloader with only the artifacts of the project (none of this plugin) 
635        List<URL> urls = new ArrayList<>( request.getDependencies().size() );
636        for ( Artifact artifact : request.getDependencies() )
637        {
638            try
639            {
640                urls.add( artifact.getFile().toURI().toURL() );
641            }
642            catch ( MalformedURLException e )
643            {
644                // noop
645            }
646        }
647        builder.addClassLoader( new URLClassLoader( urls.toArray( new URL[0] ), ClassLoader.getSystemClassLoader() ) );
648        
649        MavenProject project = request.getProject();
650
651        for ( String source : project.getCompileSourceRoots() )
652        {
653            builder.addSourceTree( new File( source ) );
654        }
655
656        // TODO be more dynamic
657        File generatedPlugin = new File( project.getBasedir(), "target/generated-sources/plugin" );
658        if ( !project.getCompileSourceRoots().contains( generatedPlugin.getAbsolutePath() ) )
659        {
660            builder.addSourceTree( generatedPlugin );
661        }
662
663        return builder.getClasses();
664    }
665
666    /**
667     * @param mojoDescriptor not null
668     * @throws InvalidParameterException if any
669     */
670    protected void validate( MojoDescriptor mojoDescriptor )
671        throws InvalidParameterException
672    {
673        List<Parameter> parameters = mojoDescriptor.getParameters();
674
675        if ( parameters != null )
676        {
677            for ( int j = 0; j < parameters.size(); j++ )
678            {
679                validateParameter( parameters.get( j ), j );
680            }
681        }
682    }
683}