View Javadoc

1   package org.apache.maven.plugins.help;
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 com.thoughtworks.xstream.XStream;
23  import com.thoughtworks.xstream.converters.MarshallingContext;
24  import com.thoughtworks.xstream.converters.collections.PropertiesConverter;
25  import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
26  import org.apache.commons.lang.ClassUtils;
27  import org.apache.maven.artifact.Artifact;
28  import org.apache.maven.artifact.ArtifactUtils;
29  import org.apache.maven.artifact.factory.ArtifactFactory;
30  import org.apache.maven.artifact.repository.ArtifactRepository;
31  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
32  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
33  import org.apache.maven.artifact.resolver.ArtifactResolver;
34  import org.apache.maven.execution.MavenSession;
35  import org.apache.maven.model.Dependency;
36  import org.apache.maven.model.Model;
37  import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
38  import org.apache.maven.plugin.AbstractMojo;
39  import org.apache.maven.plugin.MojoExecution;
40  import org.apache.maven.plugin.MojoExecutionException;
41  import org.apache.maven.plugin.MojoFailureException;
42  import org.apache.maven.plugin.PluginParameterExpressionEvaluator;
43  import org.apache.maven.plugin.descriptor.MojoDescriptor;
44  import org.apache.maven.plugins.annotations.Component;
45  import org.apache.maven.plugins.annotations.Mojo;
46  import org.apache.maven.plugins.annotations.Parameter;
47  import org.apache.maven.project.MavenProject;
48  import org.apache.maven.project.MavenProjectBuilder;
49  import org.apache.maven.project.ProjectBuildingException;
50  import org.apache.maven.project.path.PathTranslator;
51  import org.apache.maven.settings.Settings;
52  import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
53  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
54  import org.codehaus.plexus.components.interactivity.InputHandler;
55  import org.codehaus.plexus.util.IOUtil;
56  import org.codehaus.plexus.util.StringUtils;
57  
58  import java.io.File;
59  import java.io.FileInputStream;
60  import java.io.IOException;
61  import java.io.InputStream;
62  import java.io.StringWriter;
63  import java.util.List;
64  import java.util.Locale;
65  import java.util.Map;
66  import java.util.Properties;
67  import java.util.TreeMap;
68  import java.util.jar.JarEntry;
69  import java.util.jar.JarInputStream;
70  
71  /**
72   * Evaluates Maven expressions given by the user in an interactive mode.
73   *
74   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
75   * @version $Id: EvaluateMojo.java 1446806 2013-02-15 23:07:28Z rfscholte $
76   * @since 2.1
77   */
78  @Mojo( name = "evaluate", requiresProject = false )
79  public class EvaluateMojo
80      extends AbstractMojo
81  {
82      // ----------------------------------------------------------------------
83      // Mojo components
84      // ----------------------------------------------------------------------
85  
86      /**
87       * Maven Artifact Factory component.
88       */
89      @Component
90      private ArtifactFactory artifactFactory;
91  
92      /**
93       * Input handler, needed for command line handling.
94       */
95      @Component
96      private InputHandler inputHandler;
97  
98      /**
99       * Maven Project Builder component.
100      */
101     @Component
102     private MavenProjectBuilder mavenProjectBuilder;
103 
104     /**
105      */
106     @Component
107     private PathTranslator pathTranslator;
108 
109     /**
110      * Artifact Resolver component.
111      */
112     @Component
113     private ArtifactResolver resolver;
114 
115     /**
116      */
117     @Component
118     private LoggerRetriever loggerRetriever;
119 
120     // ----------------------------------------------------------------------
121     // Mojo parameters
122     // ----------------------------------------------------------------------
123 
124     /**
125      * An artifact for evaluating Maven expressions.
126      * <br/>
127      * <b>Note</b>: Should respect the Maven format, i.e. <code>groupId:artifactId[:version][:classifier]</code>.
128      */
129     @Parameter( property = "artifact" )
130     private String artifact;
131 
132     /**
133      * An expression to evaluate instead of prompting. Note that this <i>must not</i> include the surrounding ${...}.
134      */
135     @Parameter( property = "expression" )
136     private String expression;
137 
138     /**
139      * Local Repository.
140      */
141     @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
142     protected ArtifactRepository localRepository;
143 
144     /**
145      * The current Maven project or the super pom.
146      */
147     @Component
148     protected MavenProject project;
149 
150     /**
151      * Remote repositories used for the project.
152      */
153     @Parameter( defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true )
154     private List<ArtifactRepository> remoteRepositories;
155 
156     /**
157      * The system settings for Maven.
158      */
159     @Component
160     protected Settings settings;
161 
162     /**
163      * The current Maven session.
164      */
165     @Component
166     private MavenSession session;
167 
168     // ----------------------------------------------------------------------
169     // Instance variables
170     // ----------------------------------------------------------------------
171 
172     /** lazy loading evaluator variable */
173     private PluginParameterExpressionEvaluator evaluator;
174 
175     /** lazy loading xstream variable */
176     private XStream xstream;
177 
178     // ----------------------------------------------------------------------
179     // Public methods
180     // ----------------------------------------------------------------------
181 
182     /** {@inheritDoc} */
183     public void execute()
184         throws MojoExecutionException, MojoFailureException
185     {
186         if ( expression == null && !settings.isInteractiveMode() )
187         {
188             StringBuilder msg = new StringBuilder();
189             msg.append( "Maven is configured to NOT interact with the user for input. " );
190             msg.append( "This Mojo requires that 'interactiveMode' in your settings file is flag to 'true'." );
191 
192             getLog().error( msg.toString() );
193             return;
194         }
195 
196         validateParameters();
197 
198         if ( StringUtils.isNotEmpty( artifact ) )
199         {
200             Artifact artifactObj = getArtifact( artifact );
201 
202             try
203             {
204                 project = getMavenProject( artifactObj );
205             }
206             catch ( ProjectBuildingException e )
207             {
208                 throw new MojoExecutionException( "Unable to get the POM for the artifact '" + artifact
209                     + "'. Verify the artifact parameter." );
210             }
211         }
212 
213         if ( expression == null )
214         {
215             while ( true )
216             {
217                 getLog().info( "Enter the Maven expression i.e. ${project.groupId} or 0 to exit?:" );
218 
219                 try
220                 {
221                     String userExpression = inputHandler.readLine();
222                     if ( userExpression == null || userExpression.toLowerCase( Locale.ENGLISH ).equals( "0" ) )
223                     {
224                         break;
225                     }
226 
227                     handleResponse( userExpression );
228                 }
229                 catch ( IOException e )
230                 {
231                     throw new MojoExecutionException( "Unable to read from standard input.", e );
232                 }
233             }
234         }
235         else
236         {
237             handleResponse( "${" + expression + "}" );
238         }
239     }
240 
241     // ----------------------------------------------------------------------
242     // Private methods
243     // ----------------------------------------------------------------------
244 
245     /**
246      * Validate Mojo parameters.
247      */
248     private void validateParameters()
249     {
250         if ( artifact == null )
251         {
252             // using project if found or super-pom
253             getLog().info( "No artifact parameter specified, using '" + project.getId() + "' as project." );
254         }
255     }
256 
257     /**
258      * @param artifactString should respect the format <code>groupId:artifactId[:version][:classifier]</code>
259      * @return the <code>Artifact</code> object for the <code>artifactString</code> parameter.
260      * @throws MojoExecutionException if the <code>artifactString</code> doesn't respect the format.
261      */
262     private Artifact getArtifact( String artifactString )
263         throws MojoExecutionException
264     {
265         if ( StringUtils.isEmpty( artifactString ) )
266         {
267             throw new IllegalArgumentException( "artifact parameter could not be empty" );
268         }
269 
270         String groupId = null; // required
271         String artifactId = null; // required
272         String version = null; // optional
273         String classifier = null; // optional
274 
275         String[] artifactParts = artifactString.split( ":" );
276 
277         switch ( artifactParts.length )
278         {
279             case 2:
280                 groupId = artifactParts[0];
281                 artifactId = artifactParts[1];
282                 version = Artifact.LATEST_VERSION;
283                 break;
284             case 3:
285                 groupId = artifactParts[0];
286                 artifactId = artifactParts[1];
287                 version = artifactParts[2];
288                 break;
289             case 4:
290                 groupId = artifactParts[0];
291                 artifactId = artifactParts[1];
292                 version = artifactParts[2];
293                 classifier = artifactParts[3];
294                 break;
295             default:
296                 throw new MojoExecutionException( "The artifact parameter '" + artifactString
297                     + "' should be conform to: " + "'groupId:artifactId[:version][:classifier]'." );
298         }
299 
300         if ( StringUtils.isNotEmpty( classifier ) )
301         {
302             return artifactFactory.createArtifactWithClassifier( groupId, artifactId, version, "jar", classifier );
303         }
304 
305         return artifactFactory.createArtifact( groupId, artifactId, version, Artifact.SCOPE_COMPILE, "jar" );
306     }
307 
308     /**
309      * @param artifactObj not null
310      * @return the POM for the given artifact.
311      * @throws MojoExecutionException if the artifact has a system scope.
312      * @throws ProjectBuildingException when building pom.
313      */
314     private MavenProject getMavenProject( Artifact artifactObj )
315         throws MojoExecutionException, ProjectBuildingException
316     {
317         if ( Artifact.SCOPE_SYSTEM.equals( artifactObj.getScope() ) )
318         {
319             throw new MojoExecutionException( "System artifact is not be handled." );
320         }
321 
322         Artifact copyArtifact = ArtifactUtils.copyArtifact( artifactObj );
323         if ( !"pom".equals( copyArtifact.getType() ) )
324         {
325             copyArtifact =
326                 artifactFactory.createProjectArtifact( copyArtifact.getGroupId(), copyArtifact.getArtifactId(),
327                                                        copyArtifact.getVersion(), copyArtifact.getScope() );
328         }
329 
330         return mavenProjectBuilder.buildFromRepository( copyArtifact, remoteRepositories, localRepository );
331     }
332 
333     /**
334      * @return a lazy loading evaluator object.
335      * @throws MojoExecutionException if any
336      * @throws MojoFailureException if any reflection exceptions occur or missing components.
337      * @see #getMojoDescriptor(String, MavenSession, MavenProject, String, boolean, boolean)
338      */
339     private PluginParameterExpressionEvaluator getEvaluator()
340         throws MojoExecutionException, MojoFailureException
341     {
342         if ( evaluator == null )
343         {
344             MojoDescriptor mojoDescriptor =
345                 HelpUtil.getMojoDescriptor( "help:evaluate", session, project, "help:evaluate", true, false );
346             MojoExecution mojoExecution = new MojoExecution( mojoDescriptor );
347             evaluator =
348                 new PluginParameterExpressionEvaluator( session, mojoExecution, pathTranslator,
349                                                         loggerRetriever.getLogger(), project,
350                                                         session.getExecutionProperties() );
351         }
352 
353         return evaluator;
354     }
355 
356     /**
357      * @param expr the user expression asked.
358      * @throws MojoExecutionException if any
359      * @throws MojoFailureException if any reflection exceptions occur or missing components.
360      */
361     private void handleResponse( String expr )
362         throws MojoExecutionException, MojoFailureException
363     {
364         StringBuilder response = new StringBuilder();
365 
366         Object obj;
367         try
368         {
369             obj = getEvaluator().evaluate( expr );
370         }
371         catch ( ExpressionEvaluationException e )
372         {
373             throw new MojoExecutionException( "Error when evaluating the Maven expression", e );
374         }
375 
376         if ( obj != null && expr.equals( obj.toString() ) )
377         {
378             getLog().warn( "The Maven expression was invalid. Please use a valid expression." );
379             return;
380         }
381 
382         // handle null
383         if ( obj == null )
384         {
385             response.append( "null object or invalid expression" );
386         }
387         // handle primitives objects
388         else if ( obj instanceof String )
389         {
390             response.append( obj.toString() );
391         }
392         else if ( obj instanceof Boolean )
393         {
394             response.append( obj.toString() );
395         }
396         else if ( obj instanceof Byte )
397         {
398             response.append( obj.toString() );
399         }
400         else if ( obj instanceof Character )
401         {
402             response.append( obj.toString() );
403         }
404         else if ( obj instanceof Double )
405         {
406             response.append( obj.toString() );
407         }
408         else if ( obj instanceof Float )
409         {
410             response.append( obj.toString() );
411         }
412         else if ( obj instanceof Integer )
413         {
414             response.append( obj.toString() );
415         }
416         else if ( obj instanceof Long )
417         {
418             response.append( obj.toString() );
419         }
420         else if ( obj instanceof Short )
421         {
422             response.append( obj.toString() );
423         }
424         // handle specific objects
425         else if ( obj instanceof File )
426         {
427             File f = (File) obj;
428             response.append( f.getAbsolutePath() );
429         }
430         // handle Maven pom object
431         else if ( obj instanceof MavenProject )
432         {
433             MavenProject projectAsked = (MavenProject) obj;
434             StringWriter sWriter = new StringWriter();
435             MavenXpp3Writer pomWriter = new MavenXpp3Writer();
436             try
437             {
438                 pomWriter.write( sWriter, projectAsked.getModel() );
439             }
440             catch ( IOException e )
441             {
442                 throw new MojoExecutionException( "Error when writing pom", e );
443             }
444 
445             response.append( sWriter.toString() );
446         }
447         // handle Maven Settings object
448         else if ( obj instanceof Settings )
449         {
450             Settings settingsAsked = (Settings) obj;
451             StringWriter sWriter = new StringWriter();
452             SettingsXpp3Writer settingsWriter = new SettingsXpp3Writer();
453             try
454             {
455                 settingsWriter.write( sWriter, settingsAsked );
456             }
457             catch ( IOException e )
458             {
459                 throw new MojoExecutionException( "Error when writing settings", e );
460             }
461 
462             response.append( sWriter.toString() );
463         }
464         else
465         {
466             // others Maven objects
467             response.append( toXML( expr, obj ) );
468         }
469 
470         getLog().info( "\n" + response.toString() );
471     }
472 
473     /**
474      * @param expr the user expression.
475      * @param obj a not null.
476      * @return the XML for the given object.
477      */
478     private String toXML( String expr, Object obj )
479     {
480         XStream currentXStream = getXStream();
481 
482         // beautify list
483         if ( obj instanceof List )
484         {
485             List<?> list = (List<?>) obj;
486             if ( list.size() > 0 )
487             {
488                 Object elt = list.iterator().next();
489 
490                 String name = StringUtils.lowercaseFirstLetter( ClassUtils.getShortClassName( elt.getClass() ) );
491                 currentXStream.alias( pluralize( name ), List.class );
492             }
493             else
494             {
495                 // try to detect the alias from question
496                 if ( expr.indexOf( '.' ) != -1 )
497                 {
498                     String name = expr.substring( expr.indexOf( '.' ) + 1, expr.indexOf( '}' ) );
499                     currentXStream.alias( name, List.class );
500                 }
501             }
502         }
503 
504         return currentXStream.toXML( obj );
505     }
506 
507     /**
508      * @return lazy loading xstream object.
509      */
510     private XStream getXStream()
511     {
512         if ( xstream == null )
513         {
514             xstream = new XStream();
515             addAlias( xstream );
516 
517             // handle Properties a la Maven
518             xstream.registerConverter( new PropertiesConverter()
519             {
520                 /** {@inheritDoc} */
521                 public boolean canConvert( @SuppressWarnings( "rawtypes" ) Class type )
522                 {
523                     return Properties.class == type;
524                 }
525 
526                 /** {@inheritDoc} */
527                 public void marshal( Object source, HierarchicalStreamWriter writer, MarshallingContext context )
528                 {
529                     Properties properties = (Properties) source;
530                     Map<?, ?> map = new TreeMap<Object, Object>( properties ); // sort
531                     for ( Map.Entry<?, ?> entry : map.entrySet() )
532                     {
533                         writer.startNode( entry.getKey().toString() );
534                         writer.setValue( entry.getValue().toString() );
535                         writer.endNode();
536                     }
537                 }
538             } );
539         }
540 
541         return xstream;
542     }
543 
544     /**
545      * @param xstreamObject not null
546      */
547     private void addAlias( XStream xstreamObject )
548     {
549         try
550         {
551             addAlias( xstreamObject, getMavenModelJarFile(), "org.apache.maven.model" );
552             addAlias( xstreamObject, getMavenSettingsJarFile(), "org.apache.maven.settings" );
553         }
554         catch ( MojoExecutionException e )
555         {
556             if ( getLog().isDebugEnabled() )
557             {
558                 getLog().debug( "MojoExecutionException: " + e.getMessage(), e );
559             }
560         }
561         catch ( ArtifactResolutionException e )
562         {
563             if ( getLog().isDebugEnabled() )
564             {
565                 getLog().debug( "ArtifactResolutionException: " + e.getMessage(), e );
566             }
567         }
568         catch ( ArtifactNotFoundException e )
569         {
570             if ( getLog().isDebugEnabled() )
571             {
572                 getLog().debug( "ArtifactNotFoundException: " + e.getMessage(), e );
573             }
574         }
575         catch ( ProjectBuildingException e )
576         {
577             if ( getLog().isDebugEnabled() )
578             {
579                 getLog().debug( "ProjectBuildingException: " + e.getMessage(), e );
580             }
581         }
582 
583         // TODO need to handle specific Maven objects like DefaultArtifact?
584     }
585 
586     /**
587      * @param xstreamObject not null
588      * @param jarFile not null
589      * @param packageFilter a package name to filter.
590      */
591     private void addAlias( XStream xstreamObject, File jarFile, String packageFilter )
592     {
593         JarInputStream jarStream = null;
594         try
595         {
596             jarStream = new JarInputStream( new FileInputStream( jarFile ) );
597             JarEntry jarEntry = jarStream.getNextJarEntry();
598             while ( jarEntry != null )
599             {
600                 if ( jarEntry.getName().toLowerCase( Locale.ENGLISH ).endsWith( ".class" ) )
601                 {
602                     String name = jarEntry.getName().substring( 0, jarEntry.getName().indexOf( "." ) );
603                     name = name.replaceAll( "/", "\\." );
604 
605                     if ( name.indexOf( packageFilter ) != -1 )
606                     {
607                         try
608                         {
609                             Class<?> clazz = ClassUtils.getClass( name );
610                             String alias = StringUtils.lowercaseFirstLetter( ClassUtils.getShortClassName( clazz ) );
611                             xstreamObject.alias( alias, clazz );
612                             if ( !clazz.equals( Model.class ) )
613                             {
614                                 xstreamObject.omitField( clazz, "modelEncoding" ); // unnecessary field
615                             }
616                         }
617                         catch ( ClassNotFoundException e )
618                         {
619                             getLog().error( e );
620                         }
621                     }
622                 }
623 
624                 jarStream.closeEntry();
625                 jarEntry = jarStream.getNextJarEntry();
626             }
627         }
628         catch ( IOException e )
629         {
630             if ( getLog().isDebugEnabled() )
631             {
632                 getLog().debug( "IOException: " + e.getMessage(), e );
633             }
634         }
635         finally
636         {
637             IOUtil.close( jarStream );
638         }
639     }
640 
641     /**
642      * @return the <code>org.apache.maven:maven-model</code> artifact jar file in the local repository.
643      * @throws MojoExecutionException if any
644      * @throws ProjectBuildingException if any
645      * @throws ArtifactResolutionException if any
646      * @throws ArtifactNotFoundException if any
647      */
648     private File getMavenModelJarFile()
649         throws MojoExecutionException, ProjectBuildingException, ArtifactResolutionException,
650         ArtifactNotFoundException
651     {
652         return getArtifactFile( true );
653     }
654 
655     /**
656      * @return the <code>org.apache.maven:maven-settings</code> artifact jar file in the local repository.
657      * @throws MojoExecutionException if any
658      * @throws ProjectBuildingException if any
659      * @throws ArtifactResolutionException if any
660      * @throws ArtifactNotFoundException if any
661      */
662     private File getMavenSettingsJarFile()
663         throws MojoExecutionException, ProjectBuildingException, ArtifactResolutionException,
664         ArtifactNotFoundException
665     {
666         return getArtifactFile( false );
667     }
668 
669     /**
670      *
671      * @param isPom <code>true</code> to lookup the <code>maven-model</code> artifact jar, <code>false</code> to
672      * lookup the <code>maven-settings</code> artifact jar.
673      * @return the <code>org.apache.maven:maven-model|maven-settings</code> artifact jar file for this current
674      * HelpPlugin pom.
675      * @throws MojoExecutionException if any
676      * @throws ProjectBuildingException if any
677      * @throws ArtifactResolutionException if any
678      * @throws ArtifactNotFoundException if any
679      */
680     private File getArtifactFile( boolean isPom )
681         throws MojoExecutionException, ProjectBuildingException, ArtifactResolutionException,
682         ArtifactNotFoundException
683     {
684         @SuppressWarnings( "unchecked" )
685         List<Dependency> dependencies = getHelpPluginPom().getDependencies();
686         for ( Dependency depependency : dependencies )
687         {
688             if ( !( depependency.getGroupId().equals( "org.apache.maven" ) ) )
689             {
690                 continue;
691             }
692 
693             if ( isPom )
694             {
695                 if ( !( depependency.getArtifactId().equals( "maven-model" ) ) )
696                 {
697                     continue;
698                 }
699             }
700             else
701             {
702                 if ( !( depependency.getArtifactId().equals( "maven-settings" ) ) )
703                 {
704                     continue;
705                 }
706             }
707 
708             Artifact mavenArtifact =
709                 getArtifact( depependency.getGroupId() + ":" + depependency.getArtifactId() + ":"
710                     + depependency.getVersion() );
711             resolver.resolveAlways( mavenArtifact, remoteRepositories, localRepository );
712 
713             return mavenArtifact.getFile();
714         }
715 
716         throw new MojoExecutionException( "Unable to find the 'org.apache.maven:"
717             + ( isPom ? "maven-model" : "maven-settings" ) + "' artifact" );
718     }
719 
720     /**
721      * @return the Maven POM for the current help plugin
722      * @throws MojoExecutionException if any
723      * @throws ProjectBuildingException if any
724      */
725     private MavenProject getHelpPluginPom()
726         throws MojoExecutionException, ProjectBuildingException
727     {
728         String resource = "META-INF/maven/org.apache.maven.plugins/maven-help-plugin/pom.properties";
729 
730         InputStream resourceAsStream = EvaluateMojo.class.getClassLoader().getResourceAsStream( resource );
731         Artifact helpPluginArtifact = null;
732         if ( resourceAsStream != null )
733         {
734             Properties properties = new Properties();
735             try
736             {
737                 properties.load( resourceAsStream );
738             }
739             catch ( IOException e )
740             {
741                 if ( getLog().isDebugEnabled() )
742                 {
743                     getLog().debug( "IOException: " + e.getMessage(), e );
744                 }
745             }
746 
747             String artifactString =
748                 properties.getProperty( "groupId", "unknown" ) + ":"
749                     + properties.getProperty( "artifactId", "unknown" ) + ":"
750                     + properties.getProperty( "version", "unknown" );
751 
752             helpPluginArtifact = getArtifact( artifactString );
753         }
754 
755         if ( helpPluginArtifact == null )
756         {
757             throw new MojoExecutionException( "The help plugin artifact was not found." );
758         }
759 
760         return getMavenProject( helpPluginArtifact );
761     }
762 
763     /**
764      * @param name not null
765      * @return the plural of the name
766      */
767     private static String pluralize( String name )
768     {
769         if ( StringUtils.isEmpty( name ) )
770         {
771             throw new IllegalArgumentException( "name is required" );
772         }
773 
774         if ( name.endsWith( "y" ) )
775         {
776             return name.substring( 0, name.length() - 1 ) + "ies";
777         }
778         else if ( name.endsWith( "s" ) )
779         {
780             return name;
781         }
782         else
783         {
784             return name + "s";
785         }
786     }
787 }