package org.apache.maven.plugin;
/* ====================================================================
* 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.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.maven.util.InsertionOrderedSet;
import org.apache.maven.werkz.CyclicGoalChainException;
import org.apache.maven.werkz.Goal;
import org.apache.maven.werkz.NoSuchGoalException;
import org.apache.maven.werkz.WerkzProject;
import org.codehaus.plexus.util.StringUtils;
/**
* This is the process that we use:
*
* - Start with goal X
* - Gather prereqs for X
*
* We want to find all goals that will be executed due to X being executed
* we don't care about ordering here because when werkz executes it will sort
* that out for us. We just need to make sure all the appropriate plugins have
* been loaded so that werkz can find the goals it needs.
*
* For a single goal we will collect the set of goals that will be executed with
* it.
*/
public class GoalToJellyScriptHousingMapper
implements PluginDefinitionHandler
{
/** Logger */
private static final Log LOGGER = LogFactory.getLog( GoalToJellyScriptHousingMapper.class );
/** dyna tag -> plugin map. Where dyna tags live. */
private HashMap dynaTagPluginMap = new HashMap();
/** A plugins dyna tag dependencies. */
private HashMap pluginDynaTagDepsMap = new HashMap();
/** Goal -> plugin map. */
private HashMap goalPluginMap = new HashMap();
/** Goals with a pointer to plugins with callbacks. */
private HashMap preGoalDecoratorsMap = new HashMap();
/** Goals with a pointer to plugins with callbacks. */
private HashMap postGoalDecoratorsMap = new HashMap();
/** */
private final WerkzProject goalProject = new WerkzProject();
/** */
private String defaultGoalName;
private final HashSet resolvedPlugins = new HashSet();
/**
* Default constructor.
*/
public GoalToJellyScriptHousingMapper()
{
}
/**
* Merge parent mapper. Used to setup a transient submapper.
*
* @param mapper mapper to copy from
* @todo figure out which are actually needed, change redundant initialisers by removing here,
* or adding others to default constructor (eg goalProject init)
* @todo We should have a way to inherit entire werkz project instead of hokey goal copying
*/
void merge( GoalToJellyScriptHousingMapper mapper )
throws CyclicGoalChainException
{
mergeMap( dynaTagPluginMap, mapper.dynaTagPluginMap );
mergeMap( pluginDynaTagDepsMap, mapper.pluginDynaTagDepsMap );
mergeMap( goalPluginMap, mapper.goalPluginMap );
for ( Iterator i = mapper.preGoalDecoratorsMap.keySet().iterator(); i.hasNext(); )
{
String goalName = (String) i.next();
Set s = (Set) mapper.preGoalDecoratorsMap.get( goalName );
Set sExist = (Set) preGoalDecoratorsMap.get( goalName );
if ( sExist == null )
{
preGoalDecoratorsMap.put( goalName, s );
}
else
{
sExist.addAll( s );
}
}
for ( Iterator i = mapper.postGoalDecoratorsMap.keySet().iterator(); i.hasNext(); )
{
String goalName = (String) i.next();
Set s = (Set) mapper.postGoalDecoratorsMap.get( goalName );
Set sExist = (Set) postGoalDecoratorsMap.get( goalName );
if ( sExist == null )
{
postGoalDecoratorsMap.put( goalName, s );
}
else
{
sExist.addAll( s );
}
}
if ( defaultGoalName == null )
{
defaultGoalName = mapper.defaultGoalName;
}
for ( Iterator i = mapper.goalProject.getGoals().iterator(); i.hasNext(); )
{
Goal goal = (Goal) i.next();
Goal existingGoal = goalProject.getGoal( goal.getName() );
if ( existingGoal == null )
{
goalProject.addGoal( goal );
}
else
{
for ( Iterator j = goal.getPrecursors().iterator(); j.hasNext(); )
{
existingGoal.addPrecursor( (Goal) j.next() );
}
for ( Iterator j = goal.getPostcursors().iterator(); j.hasNext(); )
{
existingGoal.addPostcursor( (Goal) j.next() );
}
}
}
resolvedPlugins.addAll( mapper.resolvedPlugins );
}
/**
* merge source into target, but don't override anything. Kind of the
* opposite of putAll to some extent.
*
* @todo Surely can be more efficient.
* @todo if there isn't already a util function for this there should be.
*/
private static void mergeMap( Map target, Map source )
{
Map map = new HashMap( target );
target.putAll( source );
target.putAll( map );
}
// ----------------------------------------------------------------------
// Accessors
// ----------------------------------------------------------------------
public String getDefaultGoalName()
{
return defaultGoalName;
}
public void setDefaultGoalName( String defaultGoalName )
{
// first definition wins
if ( this.defaultGoalName == null )
{
this.defaultGoalName = defaultGoalName;
}
}
// ----------------------------------------------------------------------
// Implementation
// ----------------------------------------------------------------------
public JellyScriptHousing getPluginHousing( String goal )
{
return (JellyScriptHousing) goalPluginMap.get( goal );
}
private HashSet getPluginDynaTagDeps( Object pluginHousing )
{
HashSet pluginDynaTagDeps = (HashSet) pluginDynaTagDepsMap.get( pluginHousing );
if ( pluginDynaTagDeps == null )
{
pluginDynaTagDeps = new HashSet();
pluginDynaTagDepsMap.put( pluginHousing, pluginDynaTagDeps );
}
return pluginDynaTagDeps;
}
HashSet getPostGoalDecorators( String goalName )
{
HashSet decorators = (HashSet) postGoalDecoratorsMap.get( goalName );
if ( decorators == null )
{
decorators = new HashSet();
postGoalDecoratorsMap.put( goalName, decorators );
}
return decorators;
}
HashSet getPreGoalDecorators( String goalName )
{
HashSet decorators = (HashSet) preGoalDecoratorsMap.get( goalName );
if ( decorators == null )
{
decorators = new HashSet();
preGoalDecoratorsMap.put( goalName, decorators );
}
return decorators;
}
/**
* Find the appropriate plugins that provide the give goal and its precursors.
* Goals such as ${report}:register will need to be resolved lazily.
*
* @param goal the goal to find
* @return the set of plugins
* @throws NoSuchGoalException if the given goal is in no plugins
* @see org.apache.maven.jelly.tags.werkz.MavenAttainGoalTag
*/
Set resolveJellyScriptHousings( String goal )
throws NoSuchGoalException
{
LOGGER.debug( "preparing goal: " + goal );
Set pluginSet = new InsertionOrderedSet();
Goal[] chain = getExecutionChain( goal, goalProject );
LOGGER.debug( "execution chain: " + Arrays.asList( chain ).toString() );
for ( int i = 0; i < chain.length; i++ )
{
Goal g = chain[i];
Object plugin = goalPluginMap.get( g.getName() );
if ( plugin == null )
{
throw new NoSuchGoalException( g.getName() );
}
pluginSet.add( plugin );
Collection decorators = getPostGoalDecorators( g.getName() );
if ( LOGGER.isDebugEnabled() && !decorators.isEmpty() )
{
LOGGER.debug( "goal " + g.getName() + " has postGoal decorators " + decorators );
}
pluginSet.addAll( decorators );
decorators = getPreGoalDecorators( g.getName() );
if ( LOGGER.isDebugEnabled() && !decorators.isEmpty() )
{
LOGGER.debug( "goal " + g.getName() + " has preGoal decorators " + decorators );
}
pluginSet.addAll( decorators );
}
// Done like this as the dynatag plugin dependencies must be first in the list
InsertionOrderedSet newPluginSet = resolveDynaTagPlugins( pluginSet );
if ( LOGGER.isDebugEnabled() && !newPluginSet.isEmpty() )
{
LOGGER.debug( "dynatag dependencies: " + newPluginSet );
}
newPluginSet.addAll( pluginSet );
pluginSet = newPluginSet;
// Here we make the resolved plugins field be the full set of plugins, and the plugins returned just the
// additional ones resolved.
// TODO: skip the remove step - don't add if it is already loaded
pluginSet.removeAll( resolvedPlugins );
resolvedPlugins.addAll( pluginSet );
LOGGER.debug( "final list of plugins to prepare: " + pluginSet );
return pluginSet;
}
/**
* @todo [1.0] use werkz beta-11 version instead
*/
public Goal[] getExecutionChain( String name, WerkzProject project )
throws NoSuchGoalException
{
Goal goal = project.getGoal( name );
LinkedList chain = new LinkedList();
LinkedList stack = new LinkedList();
stack.addLast( goal );
while ( !stack.isEmpty() )
{
goal = (Goal) stack.removeFirst();
if ( ( goal == null ) || chain.contains( goal ) )
{
continue;
}
chain.addFirst( goal );
List precursors = goal.getPrecursors();
for ( Iterator i = precursors.iterator(); i.hasNext(); )
{
Goal eachPrecursor = (Goal) i.next();
if ( !chain.contains( eachPrecursor ) )
{
stack.addLast( eachPrecursor );
}
}
}
return (Goal[]) chain.toArray( Goal.EMPTY_ARRAY );
}
/**
* Resolve plugins that provide dynamic tags for the given set of plugins.
*
* @param plugins the plugins containing tags
* @return the plugins providing tags, in insertion order
*/
private InsertionOrderedSet resolveDynaTagPlugins( Set plugins )
{
// Important to return an insertion ordered set as the calling function is going to add to it and
// depends on these remaining first
InsertionOrderedSet resolvedDynaTagPlugins = new InsertionOrderedSet();
for ( Iterator i = plugins.iterator(); i.hasNext(); )
{
JellyScriptHousing plugin = (JellyScriptHousing) i.next();
Set dynaTagDeps = getPluginDynaTagDeps( plugin );
for ( Iterator j = dynaTagDeps.iterator(); j.hasNext(); )
{
LinkedList dynaTagUris = new LinkedList();
Set seen = new HashSet();
Object next = j.next();
dynaTagUris.add( next );
while ( !dynaTagUris.isEmpty() )
{
String dynaTagUri = (String) dynaTagUris.removeFirst();
if ( seen.contains( dynaTagUri ) )
{
continue;
}
seen.add( dynaTagUri );
Object dynaTagPluginHome = dynaTagPluginMap.get( dynaTagUri );
if ( dynaTagPluginHome == null )
{
// This is essentially allowed for bootstrap so
// plugins can import taglibs they don't use until later
LOGGER.warn( "Tag library requested that is not present: '" + dynaTagUri + "' in plugin: '"
+ plugin.getName() + "'" );
}
else
{
Set set = getPluginDynaTagDeps( dynaTagPluginHome );
dynaTagUris.addAll( set );
resolvedDynaTagPlugins.add( dynaTagPluginHome );
}
}
}
}
return resolvedDynaTagPlugins;
}
// ----------------------------------------------------------------------
// Handler implementation
// ----------------------------------------------------------------------
public void addPluginDynaTagDep( JellyScriptHousing housing, String uri )
{
getPluginDynaTagDeps( housing ).add( uri );
}
/* (non-Javadoc)
* @see org.apache.maven.plugin.PluginDefintionHandler#removePluginDynaTagDep(org.apache.maven.plugin.JellyScriptHousing, java.lang.String)
*/
public void removePluginDynaTagDep( JellyScriptHousing housing, String uri )
{
getPluginDynaTagDeps( housing ).remove( uri );
}
public void addDynaTagLib( String tagLibUri, JellyScriptHousing jellyScriptHousing )
{
dynaTagPluginMap.put( tagLibUri, jellyScriptHousing );
}
public void addPostGoal( String name, JellyScriptHousing jellyScriptHousing )
{
getPostGoalDecorators( name ).add( jellyScriptHousing );
}
public void addPreGoal( String name, JellyScriptHousing jellyScriptHousing )
{
getPreGoalDecorators( name ).add( jellyScriptHousing );
}
public void addGoal( String name, String prereqs, String description, JellyScriptHousing jellyScriptHousing )
{
// We load plugins in order of priority, so don't add goals that already exist.
if ( !goalPluginMap.containsKey( name ) )
{
Goal goal = goalProject.getGoal( name, true );
goal.setDescription( description );
goalProject.addGoal( goal );
// Add to the goal -> plugin map.
goalPluginMap.put( name, jellyScriptHousing );
if ( prereqs != null )
{
String[] s = StringUtils.split( prereqs, "," );
for ( int i = 0; i < s.length; i++ )
{
try
{
Goal prereq = goalProject.getGoal( s[i].trim(), true );
goal.addPrecursor( prereq );
}
catch ( CyclicGoalChainException e )
{
// do nothing.
}
}
}
}
}
/**
* @return goal names
*/
Set getGoalNames()
{
return goalPluginMap.keySet();
}
String getGoalDescription( String goalName )
{
Goal goal = goalProject.getGoal( goalName );
String goalDescription = ( goal != null ? goal.getDescription() : null );
if ( goalDescription != null )
{
goalDescription = goalDescription.trim();
if ( "null".equals( goalDescription ) )
{
goalDescription = null;
}
else if ( goalDescription.length() == 0 )
{
goalDescription = null;
}
}
return goalDescription;
}
void addResolvedPlugins( List projectHousings )
{
resolvedPlugins.addAll( projectHousings );
}
void clearResolvedPlugins()
{
resolvedPlugins.clear();
}
void invalidatePlugin( JellyScriptHousing housing )
{
resolvedPlugins.remove( housing );
for ( Iterator i = dynaTagPluginMap.keySet().iterator(); i.hasNext(); )
{
String uri = (String) i.next();
if ( dynaTagPluginMap.get( uri ).equals( housing ) )
{
i.remove();
}
}
pluginDynaTagDepsMap.remove( housing );
for ( Iterator i = goalPluginMap.keySet().iterator(); i.hasNext(); )
{
String goal = (String) i.next();
if ( goalPluginMap.get( goal ).equals( housing ) )
{
i.remove();
goalProject.removeGoal( goal );
}
}
for ( Iterator i = preGoalDecoratorsMap.keySet().iterator(); i.hasNext(); )
{
String preGoal = (String) i.next();
Set decorators = (Set) preGoalDecoratorsMap.get( preGoal );
decorators.remove( housing );
}
for ( Iterator i = postGoalDecoratorsMap.keySet().iterator(); i.hasNext(); )
{
String postGoal = (String) i.next();
Set decorators = (Set) postGoalDecoratorsMap.get( postGoal );
decorators.remove( housing );
}
}
}