blob: df58567b7b89dbddb8eb2cf62d58f543ae6d5f30 [file] [log] [blame]
package org.apache.maven.plugin.doap;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.WeakHashMap;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Set;
import java.util.Properties;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.maven.model.Contributor;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Settings;
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.apache.maven.wagon.proxy.ProxyUtils;
import org.codehaus.plexus.i18n.I18N;
import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
import org.codehaus.plexus.interpolation.InterpolationException;
import org.codehaus.plexus.interpolation.ObjectBasedValueSource;
import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.introspection.ClassMap;
import org.codehaus.plexus.util.xml.XMLWriter;
import org.codehaus.plexus.util.xml.XmlWriterUtil;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.RDFReader;
import com.hp.hpl.jena.rdf.model.impl.RDFDefaultErrorHandler;
/**
* Utility class for {@link DoapMojo} class.
*
* @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
* @version $Id$
* @since 1.0
*/
public class DoapUtil
{
/** Email regex */
private static final String EMAIL_REGEX =
"^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
/** Email pattern */
private static final Pattern EMAIL_PATTERN = Pattern.compile( EMAIL_REGEX );
/** Magic number to repeat '=' */
private static final int REPEAT_EQUALS = 21;
/** The default timeout used when fetching url, i.e. 2000. */
public static final int DEFAULT_TIMEOUT = 2000;
/** RDF resource attribute */
protected static final String RDF_RESOURCE = "rdf:resource";
/** RDF nodeID attribute */
protected static final String RDF_NODE_ID = "rdf:nodeID";
/** DoaP Organizations stored by name */
private static Map<String, DoapUtil.Organization> organizations = new HashMap<String, DoapUtil.Organization>();
/**
* Write comments in the DOAP file header
*
* @param writer not null
*/
public static void writeHeader( XMLWriter writer )
{
XmlWriterUtil.writeLineBreak( writer );
XmlWriterUtil.writeCommentLineBreak( writer );
XmlWriterUtil.writeComment( writer, StringUtils.repeat( "=", REPEAT_EQUALS ) + " - DO NOT EDIT THIS FILE! - "
+ StringUtils.repeat( "=", REPEAT_EQUALS ) );
XmlWriterUtil.writeCommentLineBreak( writer );
XmlWriterUtil.writeComment( writer, " " );
XmlWriterUtil.writeComment( writer, "Any modifications will be overwritten." );
XmlWriterUtil.writeComment( writer, " " );
DateFormat dateFormat = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT, Locale.US );
XmlWriterUtil.writeComment( writer, "Generated by Maven Doap Plugin " + getPluginVersion() + " on "
+ dateFormat.format( new Date( System.currentTimeMillis() ) ) );
XmlWriterUtil.writeComment( writer, "See: http://maven.apache.org/plugins/maven-doap-plugin/" );
XmlWriterUtil.writeComment( writer, " " );
XmlWriterUtil.writeCommentLineBreak( writer );
XmlWriterUtil.writeLineBreak( writer );
}
/**
* Write comment.
*
* @param writer not null
* @param comment not null
* @throws IllegalArgumentException if comment is null or empty
* @since 1.1
*/
public static void writeComment( XMLWriter writer, String comment )
throws IllegalArgumentException
{
if ( StringUtils.isEmpty( comment ) )
{
throw new IllegalArgumentException( "comment should be defined" );
}
XmlWriterUtil.writeLineBreak( writer );
XmlWriterUtil.writeCommentText( writer, comment, 2 );
}
/**
* @param writer not null
* @param xmlnsPrefix could be null
* @param name not null
* @param value could be null. In this case, the element is not written.
* @throws IllegalArgumentException if name is null or empty
*/
public static void writeElement( XMLWriter writer, String xmlnsPrefix, String name, String value )
throws IllegalArgumentException
{
if ( StringUtils.isEmpty( name ) )
{
throw new IllegalArgumentException( "name should be defined" );
}
if ( value != null )
{
writeStartElement( writer, xmlnsPrefix, name );
writer.writeText( value );
writer.endElement();
}
}
/**
* @param writer not null
* @param xmlnsPrefix could be null
* @param name not null
* @param lang not null
* @param value could be null. In this case, the element is not written.
* @throws IllegalArgumentException if name is null or empty
*/
public static void writeElement( XMLWriter writer, String xmlnsPrefix, String name, String value, String lang )
throws IllegalArgumentException
{
if ( StringUtils.isEmpty( lang ) )
{
writeElement( writer, xmlnsPrefix, name, value );
return;
}
if ( StringUtils.isEmpty( name ) )
{
throw new IllegalArgumentException( "name should be defined" );
}
if ( value != null )
{
writeStartElement( writer, xmlnsPrefix, name );
writer.addAttribute( "xml:lang", lang );
writer.writeText( value );
writer.endElement();
}
}
/**
* @param writer not null
* @param xmlnsPrefix could be null
* @param name not null
* @throws IllegalArgumentException if name is null or empty
* @since 1.1
*/
public static void writeStartElement( XMLWriter writer, String xmlnsPrefix, String name )
throws IllegalArgumentException
{
if ( StringUtils.isEmpty( name ) )
{
throw new IllegalArgumentException( "name should be defined" );
}
if ( StringUtils.isNotEmpty( xmlnsPrefix ) )
{
writer.startElement( xmlnsPrefix + ":" + name );
}
else
{
writer.startElement( name );
}
}
/**
* @param writer not null
* @param xmlnsPrefix could be null
* @param name not null
* @param value could be null. In this case, the element is not written.
* @throws IllegalArgumentException if name is null or empty
*/
public static void writeRdfResourceElement( XMLWriter writer, String xmlnsPrefix, String name, String value )
throws IllegalArgumentException
{
if ( StringUtils.isEmpty( name ) )
{
throw new IllegalArgumentException( "name should be defined" );
}
if ( value != null )
{
writeStartElement( writer, xmlnsPrefix, name );
writer.addAttribute( RDF_RESOURCE, value );
writer.endElement();
}
}
/**
* @param writer not null
* @param name not null
* @param value could be null. In this case, the element is not written.
* @throws IllegalArgumentException if name is null or empty
*/
public static void writeRdfNodeIdElement( XMLWriter writer, String xmlnsPrefix, String name, String value )
throws IllegalArgumentException
{
if ( StringUtils.isEmpty( name ) )
{
throw new IllegalArgumentException( "name should be defined" );
}
if ( value != null )
{
writeStartElement( writer, xmlnsPrefix, name );
writer.addAttribute( RDF_NODE_ID, value );
writer.endElement();
}
}
/**
* @param i18n the internationalization component
* @param developersOrContributors list of <code>{@link Contributor}</code>
* @return a none null list of <code>{@link Contributor}</code> which have a <code>developer</code> DOAP role.
*/
public static List<Contributor> getContributorsWithDeveloperRole( I18N i18n,
List<Contributor> developersOrContributors )
{
return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "developers" );
}
/**
* @param i18n the internationalization component
* @param developersOrContributors list of <code>{@link Contributor}</code>
* @return a none null list of <code>{@link Contributor}</code> which have a <code>documenter</code> DOAP role.
*/
public static List<Contributor> getContributorsWithDocumenterRole( I18N i18n,
List<Contributor> developersOrContributors )
{
return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "documenters" );
}
/**
* @param i18n the internationalization component
* @param developersOrContributors list of <code>{@link Contributor}</code>
* @return a none null list of <code>{@link Contributor}</code> which have an <code>helper</code> DOAP role.
*/
public static List<Contributor> getContributorsWithHelperRole( I18N i18n,
List<Contributor> developersOrContributors )
{
return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "helpers" );
}
/**
* @param i18n the internationalization component
* @param developersOrContributors list of <code>{@link Contributor}</code>
* @return a none null list of <code>{@link Contributor}</code> which have a <code>maintainer</code> DOAP role.
*/
public static List<Contributor> getContributorsWithMaintainerRole( I18N i18n,
List<Contributor> developersOrContributors )
{
return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "maintainers" );
}
/**
* @param i18n the internationalization component
* @param developersOrContributors list of <code>{@link Contributor}</code>
* @return a none null list of <code>{@link Contributor}</code> which have a <code>tester</code> DOAP role.
*/
public static List<Contributor> getContributorsWithTesterRole( I18N i18n,
List<Contributor> developersOrContributors )
{
return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "testers" );
}
/**
* @param i18n the internationalization component
* @param developersOrContributors list of <code>{@link Contributor}</code>
* @return a none null list of <code>{@link Contributor}</code> which have a <code>translator</code> DOAP role.
*/
public static List<Contributor> getContributorsWithTranslatorRole( I18N i18n,
List<Contributor> developersOrContributors )
{
return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "translators" );
}
/**
* @param i18n the internationalization component
* @param developersOrContributors list of <code>{@link Contributor}</code>
* @return a none null list of <code>{@link Contributor}</code> which have an <code>unknown</code> DOAP role.
*/
public static List<Contributor> getContributorsWithUnknownRole( I18N i18n,
List<Contributor> developersOrContributors )
{
return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "unknowns" );
}
/**
* Utility class for keeping track of DOAP organizations in the DoaP mojo.
*
* @author <a href="mailto:t.fliss@gmail.com">Tim Fliss</a>
* @version $Id$
* @since 1.1
*/
public static class Organization
{
private String name;
private String url;
private List<String> members = new LinkedList<String>();
public Organization( String name, String url )
{
this.name = name;
this.url = url;
}
public void setName( String name )
{
this.name = name;
}
public String getName()
{
return name;
}
public void setUrl( String url )
{
this.url = url;
}
public String getUrl()
{
return url;
}
public void addMember( String nodeId )
{
members.add( nodeId );
}
public List<String> getMembers()
{
return members;
}
}
/**
* put an organization from the pom file in the organization list.
*
* @param name from the pom file (e.g. Yoyodyne)
* @param url from the pom file (e.g. http://yoyodyne.example.org/about)
* @return the existing organization if a duplicate, or a new one.
*/
public static DoapUtil.Organization addOrganization( String name, String url )
{
Organization organization = organizations.get( name );
if ( organization == null )
{
organization = new DoapUtil.Organization( name, url );
}
organizations.put( name, organization );
return organization;
}
// unique RDF blank node index scoped internal to the DOAP file
private static int nodeNumber = 1;
/**
* get a unique (within the DoaP file) RDF blank node ID
*
* @return the nodeID
* @see <a href="http://www.w3.org/TR/rdf-syntax-grammar/#section-Syntax-blank-nodes">
* http://www.w3.org/TR/rdf-syntax-grammar/#section-Syntax-blank-nodes</a>
*/
public static String getNodeId()
{
return "b" + nodeNumber++;
}
/**
* get the set of Organizations that people are members of
*
* @return Map.EntrySet of DoapUtil.Organization
*/
public static Set<Entry<String, DoapUtil.Organization>> getOrganizations()
{
return organizations.entrySet();
}
/**
* Validate the given DOAP file.
*
* @param doapFile not null and should exists.
* @return an empty list if the DOAP file is valid, otherwise a list of errors.
* @since 1.1
*/
public static List<String> validate( File doapFile )
{
if ( doapFile == null || !doapFile.isFile() )
{
throw new IllegalArgumentException( "The DOAP file should exist" );
}
Model model = ModelFactory.createDefaultModel();
RDFReader r = model.getReader( "RDF/XML" );
r.setProperty( "error-mode", "strict-error" );
final List<String> errors = new ArrayList<String>();
r.setErrorHandler( new RDFDefaultErrorHandler()
{
@Override
public void error( Exception e )
{
errors.add( e.getMessage() );
}
} );
try
{
r.read( model, doapFile.toURI().toURL().toString() );
}
catch ( MalformedURLException e )
{
// ignored
}
return errors;
}
/**
* @param str not null
* @return <code>true</code> if the str parameter is a valid email, <code>false</code> otherwise.
* @since 1.1
*/
public static boolean isValidEmail( String str )
{
if ( StringUtils.isEmpty( str ) )
{
return false;
}
Matcher matcher = EMAIL_PATTERN.matcher( str );
return matcher.matches();
}
/**
* Fetch an URL
*
* @param settings the user settings used to fetch the url with an active proxy, if defined.
* @param url the url to fetch
* @throws IOException if any
* @see #DEFAULT_TIMEOUT
* @since 1.1
*/
public static void fetchURL( Settings settings, URL url )
throws IOException
{
if ( url == null )
{
throw new IllegalArgumentException( "The url is null" );
}
if ( "file".equals( url.getProtocol() ) )
{
InputStream in = null;
try
{
in = url.openStream();
in.close();
in = null;
}
finally
{
IOUtil.close( in );
}
return;
}
// http, https...
HttpClient httpClient = new HttpClient( new MultiThreadedHttpConnectionManager() );
httpClient.getHttpConnectionManager().getParams().setConnectionTimeout( DEFAULT_TIMEOUT );
httpClient.getHttpConnectionManager().getParams().setSoTimeout( DEFAULT_TIMEOUT );
httpClient.getParams().setBooleanParameter( HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, true );
// Some web servers don't allow the default user-agent sent by httpClient
httpClient.getParams().setParameter( HttpMethodParams.USER_AGENT,
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)" );
if ( settings != null && settings.getActiveProxy() != null )
{
Proxy activeProxy = settings.getActiveProxy();
ProxyInfo proxyInfo = new ProxyInfo();
proxyInfo.setNonProxyHosts( activeProxy.getNonProxyHosts() );
if ( StringUtils.isNotEmpty( activeProxy.getHost() )
&& !ProxyUtils.validateNonProxyHosts( proxyInfo, url.getHost() ) )
{
httpClient.getHostConfiguration().setProxy( activeProxy.getHost(), activeProxy.getPort() );
if ( StringUtils.isNotEmpty( activeProxy.getUsername() ) && activeProxy.getPassword() != null )
{
Credentials credentials =
new UsernamePasswordCredentials( activeProxy.getUsername(), activeProxy.getPassword() );
httpClient.getState().setProxyCredentials( AuthScope.ANY, credentials );
}
}
}
GetMethod getMethod = new GetMethod( url.toString() );
try
{
int status;
try
{
status = httpClient.executeMethod( getMethod );
}
catch ( SocketTimeoutException e )
{
// could be a sporadic failure, one more retry before we give up
status = httpClient.executeMethod( getMethod );
}
if ( status != HttpStatus.SC_OK )
{
throw new FileNotFoundException( url.toString() );
}
}
finally
{
getMethod.releaseConnection();
}
}
/**
* Interpolate a string with project and settings.
*
* @param value could be null
* @param project not null
* @param settings could be null
* @return the value trimmed and interpolated or null if the interpolation doesn't work.
* @since 1.1
*/
public static String interpolate( String value, final MavenProject project, Settings settings )
{
if ( project == null )
{
throw new IllegalArgumentException( "project is required" );
}
if ( value == null )
{
return value;
}
if ( !value.contains( "${" ) )
{
return value.trim();
}
RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
try
{
interpolator.addValueSource( new EnvarBasedValueSource() );
}
catch ( IOException e )
{
// ignore
}
interpolator.addValueSource( new PropertiesBasedValueSource( System.getProperties() ) );
interpolator.addValueSource( new PropertiesBasedValueSource( project.getProperties() ) );
interpolator.addValueSource( new PrefixedObjectValueSource( "project", project ) );
interpolator.addValueSource( new PrefixedObjectValueSource( "pom", project ) );
interpolator.addValueSource( new ObjectBasedValueSource( project )
{
@Override
public Object getValue( String expression )
{
try
{
return ReflectionValueExtractor.evaluate( expression, project, true );
}
catch ( Exception e )
{
addFeedback( "Failed to extract \'" + expression + "\' from: " + project, e );
}
return null;
}
} );
if ( settings != null )
{
interpolator.addValueSource( new PrefixedObjectValueSource( "settings", settings ) );
}
String interpolatedValue = value;
try
{
interpolatedValue = interpolator.interpolate( value ).trim();
}
catch ( InterpolationException e )
{
// ignore
}
if ( interpolatedValue.startsWith( "${" ) )
{
return null;
}
return interpolatedValue;
}
// ----------------------------------------------------------------------
// Private methods
// ----------------------------------------------------------------------
/**
* Filter the developers/contributors roles by the keys from {@link I18N#getBundle()}. <br/>
* I18N roles supported in DOAP, i.e. <code>maintainer</code>, <code>developer</code>, <code>documenter</code>,
* <code>translator</code>, <code>tester</code>, <code>helper</code>. <br/>
* <b>Note:</b> Actually, only English keys are used.
*
* @param i18n i18n component
* @param developersOrContributors list of <code>{@link Contributor}</code>
* @return a none null map with <code>maintainers</code>, <code>developers</code>, <code>documenters</code>,
* <code>translators</code>, <code>testers</code>, <code>helpers</code>, <code>unknowns</code> as keys and
* list of <code>{@link Contributor}</code> as value.
*/
private static Map<String, List<Contributor>> filterContributorsByDoapRoles( I18N i18n,
List<Contributor> developersOrContributors )
{
Map<String, List<Contributor>> returnMap = new HashMap<String, List<Contributor>>( 7 );
returnMap.put( "maintainers", new ArrayList<Contributor>() );
returnMap.put( "developers", new ArrayList<Contributor>() );
returnMap.put( "documenters", new ArrayList<Contributor>() );
returnMap.put( "translators", new ArrayList<Contributor>() );
returnMap.put( "testers", new ArrayList<Contributor>() );
returnMap.put( "helpers", new ArrayList<Contributor>() );
returnMap.put( "unknowns", new ArrayList<Contributor>() );
if ( developersOrContributors == null || developersOrContributors.isEmpty() )
{
return returnMap;
}
for ( Contributor contributor : developersOrContributors )
{
List<String> roles = contributor.getRoles();
if ( roles != null && roles.size() != 0 )
{
for ( String role : roles )
{
role = role.toLowerCase( Locale.ENGLISH );
if ( role.contains( getLowerCaseString( i18n, "doap.maintainer" ) ) )
{
if ( !returnMap.get( "maintainers" ).contains( contributor ) )
{
returnMap.get( "maintainers" ).add( contributor );
}
}
else if ( role.contains( getLowerCaseString( i18n, "doap.developer" ) ) )
{
if ( !returnMap.get( "developers" ).contains( contributor ) )
{
returnMap.get( "developers" ).add( contributor );
}
}
else if ( role.contains( getLowerCaseString( i18n, "doap.documenter" ) ) )
{
if ( !returnMap.get( "documenters" ).contains( contributor ) )
{
returnMap.get( "documenters" ).add( contributor );
}
}
else if ( role.contains( getLowerCaseString( i18n, "doap.translator" ) ) )
{
if ( !returnMap.get( "translators" ).contains( contributor ) )
{
returnMap.get( "translators" ).add( contributor );
}
}
else if ( role.contains( getLowerCaseString( i18n, "doap.tester" ) ) )
{
if ( !returnMap.get( "testers" ).contains( contributor ) )
{
returnMap.get( "testers" ).add( contributor );
}
}
else if ( role.contains( getLowerCaseString( i18n, "doap.helper" ) ) )
{
if ( !returnMap.get( "helpers" ).contains( contributor ) )
{
returnMap.get( "helpers" ).add( contributor );
}
}
else if ( role.contains( getLowerCaseString( i18n, "doap.emeritus" ) ) )
{
// Don't add as developer nor as contributor as the person is no longer involved
}
else
{
if ( !returnMap.get( "unknowns" ).contains( contributor ) )
{
returnMap.get( "unknowns" ).add( contributor );
}
}
}
}
else
{
if ( !returnMap.get( "unknowns" ).contains( contributor ) )
{
returnMap.get( "unknowns" ).add( contributor );
}
}
}
return returnMap;
}
/**
* @param i18n not null
* @param key not null
* @return lower case value for the key in the i18n bundle.
*/
private static String getLowerCaseString( I18N i18n, String key )
{
return i18n.getString( "doap-person", Locale.ENGLISH, key ).toLowerCase( Locale.ENGLISH );
}
/**
* @return the Maven artefact version.
*/
private static String getPluginVersion()
{
Properties pomProperties = new Properties();
InputStream is = null;
try
{
is =
DoapUtil.class.getResourceAsStream( "/META-INF/maven/org.apache.maven.plugins/"
+ "maven-doap-plugin/pom.properties" );
if ( is == null )
{
return "<unknown>";
}
pomProperties.load( is );
is.close();
is = null;
return pomProperties.getProperty( "version", "<unknown>" );
}
catch ( IOException e )
{
return "<unknown>";
}
finally
{
IOUtil.close( is );
}
}
/**
* Fork of {@link org.codehaus.plexus.interpolation.reflection.ReflectionValueExtractor} to care of list or arrays.
*/
static class ReflectionValueExtractor
{
@SuppressWarnings( "rawtypes" )
private static final Class[] CLASS_ARGS = new Class[0];
private static final Object[] OBJECT_ARGS = new Object[0];
/**
* Use a WeakHashMap here, so the keys (Class objects) can be garbage collected. This approach prevents permgen
* space overflows due to retention of discarded classloaders.
*/
@SuppressWarnings( "rawtypes" )
private static final Map<Class, ClassMap> CLASS_MAPS = new WeakHashMap<Class, ClassMap>();
private ReflectionValueExtractor()
{
}
public static Object evaluate( String expression, Object root )
throws Exception
{
return evaluate( expression, root, true );
}
// TODO: don't throw Exception
public static Object evaluate( String expression, Object root, boolean trimRootToken )
throws Exception
{
// if the root token refers to the supplied root object parameter, remove it.
if ( trimRootToken )
{
expression = expression.substring( expression.indexOf( '.' ) + 1 );
}
Object value = root;
// ----------------------------------------------------------------------
// Walk the dots and retrieve the ultimate value desired from the
// MavenProject instance.
// ----------------------------------------------------------------------
StringTokenizer parser = new StringTokenizer( expression, "." );
while ( parser.hasMoreTokens() )
{
String token = parser.nextToken();
if ( value == null )
{
return null;
}
StringTokenizer parser2 = new StringTokenizer( token, "[]" );
int index = -1;
if ( parser2.countTokens() > 1 )
{
token = parser2.nextToken();
try
{
index = Integer.valueOf( parser2.nextToken() ).intValue();
}
catch ( NumberFormatException e )
{
// ignore
}
}
final ClassMap classMap = getClassMap( value.getClass() );
final String methodBase = StringUtils.capitalizeFirstLetter( token );
String methodName = "get" + methodBase;
Method method = classMap.findMethod( methodName, CLASS_ARGS );
if ( method == null )
{
// perhaps this is a boolean property??
methodName = "is" + methodBase;
method = classMap.findMethod( methodName, CLASS_ARGS );
}
if ( method == null )
{
return null;
}
value = method.invoke( value, OBJECT_ARGS );
if ( value == null )
{
return null;
}
if ( Collection.class.isAssignableFrom( value.getClass() ) )
{
ClassMap classMap2 = getClassMap( value.getClass() );
Method method2 = classMap2.findMethod( "toArray", CLASS_ARGS );
value = method2.invoke( value, OBJECT_ARGS );
}
if ( value.getClass().isArray() )
{
value = ( (Object[]) value )[index];
}
}
return value;
}
private static ClassMap getClassMap( Class<? extends Object> clazz )
{
ClassMap classMap = CLASS_MAPS.get( clazz );
if ( classMap == null )
{
classMap = new ClassMap( clazz );
CLASS_MAPS.put( clazz, classMap );
}
return classMap;
}
}
}