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