| /* |
| * Copyright (C) The Apache Software Foundation. All rights reserved. |
| * |
| * This software is published under the terms of the Apache Software License |
| * version 1.1, a copy of which has been included with this distribution in |
| * the LICENSE file. |
| */ |
| package org.apache.myrmidon.components.builder; |
| |
| import java.net.URL; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.parsers.SAXParserFactory; |
| import org.apache.avalon.framework.ExceptionUtil; |
| import org.apache.avalon.framework.configuration.Configuration; |
| import org.apache.avalon.framework.configuration.ConfigurationException; |
| import org.apache.avalon.framework.configuration.SAXConfigurationHandler; |
| import org.apache.avalon.framework.logger.AbstractLoggable; |
| import org.apache.avalon.framework.parameters.Parameters; |
| import org.apache.log.Logger; |
| import org.apache.myrmidon.api.TaskContext; |
| import org.apache.myrmidon.framework.Condition; |
| import org.apache.myrmidon.components.model.DefaultProject; |
| import org.apache.myrmidon.components.model.TypeLib; |
| import org.apache.myrmidon.components.model.Project; |
| import org.apache.myrmidon.components.model.Target; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.XMLReader; |
| |
| /** |
| * Default implementation to construct project from a build file. |
| * |
| * @author <a href="mailto:donaldp@apache.org">Peter Donald</a> |
| */ |
| public class DefaultProjectBuilder |
| extends AbstractLoggable |
| implements ProjectBuilder |
| { |
| private final static int PROJECT_REFERENCES = 0; |
| private final static int LIBRARY_IMPORTS = 1; |
| private final static int IMPLICIT_TASKS = 2; |
| private final static int TARGETS = 3; |
| |
| /** |
| * build a project from file. |
| * |
| * @param source the source |
| * @return the constructed Project |
| * @exception IOException if an error occurs |
| * @exception Exception if an error occurs |
| */ |
| public Project build( final String source ) |
| throws Exception |
| { |
| final File file = new File( source ); |
| return build( file, new HashMap() ); |
| } |
| |
| private Project build( final File file, final HashMap projects ) |
| throws Exception |
| { |
| final URL systemID = file.toURL(); |
| final Project result = (Project)projects.get( systemID.toString() ); |
| if( null != result ) |
| { |
| return result; |
| } |
| |
| final SAXConfigurationHandler handler = new SAXConfigurationHandler(); |
| |
| process( systemID, handler ); |
| |
| final Configuration configuration = handler.getConfiguration(); |
| |
| final DefaultProject project = buildProject( file, configuration ); |
| |
| projects.put( systemID.toString(), project ); |
| |
| //build using all top-level attributes |
| buildTopLevelProject( project, configuration, projects ); |
| |
| return project; |
| } |
| |
| protected void process( final URL systemID, |
| final SAXConfigurationHandler handler ) |
| throws Exception |
| { |
| final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); |
| final SAXParser saxParser = saxParserFactory.newSAXParser(); |
| final XMLReader parser = saxParser.getXMLReader(); |
| parser.setFeature( "http://xml.org/sax/features/namespace-prefixes", false ); |
| parser.setFeature( "http://xml.org/sax/features/namespaces", false ); |
| //parser.setFeature( "http://xml.org/sax/features/validation", false ); |
| |
| parser.setContentHandler( handler ); |
| parser.setErrorHandler( handler ); |
| parser.parse( systemID.toString() ); |
| } |
| |
| /** |
| * build project from configuration. |
| * |
| * @param file the file from which configuration was loaded |
| * @param configuration the configuration loaded |
| * @return the created Project |
| * @exception IOException if an error occurs |
| * @exception Exception if an error occurs |
| * @exception ConfigurationException if an error occurs |
| */ |
| private DefaultProject buildProject( final File file, |
| final Configuration configuration ) |
| throws Exception |
| { |
| if( !configuration.getName().equals( "project" ) ) |
| { |
| throw new Exception( "Project file must be enclosed in project element" ); |
| } |
| |
| //get project-level attributes |
| final String baseDirectoryName = configuration.getAttribute( "basedir" ); |
| final String defaultTarget = configuration.getAttribute( "default" ); |
| //final String name = configuration.getAttribute( "name" ); |
| |
| //determine base directory for project |
| final File baseDirectory = |
| (new File( file.getParentFile(), baseDirectoryName )).getAbsoluteFile(); |
| |
| getLogger().debug( "Project " + file + " base directory: " + baseDirectory ); |
| |
| //create project and ... |
| final DefaultProject project = new DefaultProject(); |
| project.setDefaultTargetName( defaultTarget ); |
| project.setBaseDirectory( baseDirectory ); |
| //project.setName( name ); |
| |
| return project; |
| } |
| |
| /** |
| * Handle all top level elements in configuration. |
| * |
| * @param project the project |
| * @param configuration the Configuration |
| * @exception Exception if an error occurs |
| */ |
| private void buildTopLevelProject( final DefaultProject project, |
| final Configuration configuration, |
| final HashMap projects ) |
| throws Exception |
| { |
| final ArrayList implicitTaskList = new ArrayList(); |
| final Configuration[] children = configuration.getChildren(); |
| |
| int state = PROJECT_REFERENCES; |
| |
| for( int i = 0; i < children.length; i++ ) |
| { |
| final Configuration element = children[ i ]; |
| final String name = element.getName(); |
| |
| if( PROJECT_REFERENCES == state ) |
| { |
| if( name.equals( "projectref" ) ) |
| { |
| buildProjectRef( project, element, projects ); |
| continue; |
| } |
| else |
| { |
| state = LIBRARY_IMPORTS; |
| } |
| } |
| |
| if( LIBRARY_IMPORTS == state ) |
| { |
| if( name.equals( "import" ) ) |
| { |
| buildTypeLib( project, element ); |
| continue; |
| } |
| else |
| { |
| state = IMPLICIT_TASKS; |
| } |
| } |
| |
| if( IMPLICIT_TASKS == state ) |
| { |
| //Check for any implicit tasks here |
| if( !name.equals( "target" ) ) |
| { |
| implicitTaskList.add( element ); |
| continue; |
| } |
| else |
| { |
| state = TARGETS; |
| } |
| } |
| |
| if( name.equals( "target" ) ) buildTarget( project, element ); |
| else |
| { |
| throw new Exception( "Unknown top-level element " + name + |
| " at " + element.getLocation() + |
| ". Expecting target" ); |
| } |
| } |
| |
| final Configuration[] implicitTasks = |
| (Configuration[])implicitTaskList.toArray( new Configuration[ 0 ] ); |
| |
| final Target implicitTarget = new Target( null, implicitTasks, null ); |
| project.setImplicitTarget( implicitTarget ); |
| } |
| |
| private void buildProjectRef( final DefaultProject project, |
| final Configuration element, |
| final HashMap projects ) |
| throws Exception |
| { |
| final String name = element.getAttribute( "name", null ); |
| final String location = element.getAttribute( "location", null ); |
| |
| if( null == name ) |
| { |
| throw new Exception( "Malformed projectref without a name attribute at " + |
| element.getLocation() ); |
| } |
| |
| if( !validName( name ) ) |
| { |
| throw new Exception( "Projectref with an invalid name attribute at " + |
| element.getLocation() ); |
| } |
| |
| if( null == location ) |
| { |
| throw new Exception( "Malformed projectref without a location attribute at " + |
| element.getLocation() ); |
| } |
| |
| final File baseDirectory = project.getBaseDirectory(); |
| |
| //TODO: standardize and migrate to Avalon-Excalibur.io |
| final File file = new File( baseDirectory, location ); |
| |
| final String systemID = file.toURL().toString(); |
| Project other = (Project)projects.get( systemID ); |
| |
| if( null == other ) |
| { |
| other = build( file, projects ); |
| } |
| |
| project.addProject( name, other ); |
| } |
| |
| private void buildTypeLib( final DefaultProject project, |
| final Configuration element ) |
| throws Exception |
| { |
| final String library = element.getAttribute( "library", null ); |
| final String name = element.getAttribute( "name", null ); |
| final String type = element.getAttribute( "type", null ); |
| |
| if( null == library ) |
| { |
| throw new Exception( "Malformed import without a library attribute at " + |
| element.getLocation() ); |
| } |
| |
| if( null == name || null == type ) |
| { |
| if( null != name || null != type ) |
| { |
| throw new Exception( "Malformed import at " + element.getLocation() + |
| ". If name or type attribute is specified, both " + |
| "attributes must be specified." ); |
| } |
| } |
| |
| project.addTypeLib( new TypeLib( library, type, name ) ); |
| } |
| |
| /** |
| * Build a target from configuration. |
| * |
| * @param project the project |
| * @param task the Configuration |
| */ |
| private void buildTarget( final DefaultProject project, final Configuration target ) |
| throws Exception |
| { |
| final String name = target.getAttribute( "name", null ); |
| final String depends = target.getAttribute( "depends", null ); |
| final String ifCondition = target.getAttribute( "if", null ); |
| final String unlessCondition = target.getAttribute( "unless", null ); |
| |
| if( null == name ) |
| { |
| throw new Exception( "Discovered un-named target at " + |
| target.getLocation() ); |
| } |
| |
| if( !validName( name ) ) |
| { |
| throw new Exception( "Target with an invalid name at " + |
| target.getLocation() ); |
| } |
| |
| getLogger().debug( "Parsing target: " + name ); |
| |
| if( null != ifCondition && null != unlessCondition ) |
| { |
| throw new Exception( "Discovered invalid target that has both a if and " + |
| "unless condition at " + target.getLocation() ); |
| } |
| |
| Condition condition = null; |
| |
| if( null != ifCondition ) |
| { |
| getLogger().debug( "Target if condition: " + ifCondition ); |
| condition = new Condition( true, ifCondition ); |
| } |
| else if( null != unlessCondition ) |
| { |
| getLogger().debug( "Target unless condition: " + unlessCondition ); |
| condition = new Condition( false, unlessCondition ); |
| } |
| |
| String[] dependencies = null; |
| |
| //apply depends attribute |
| if( null != depends ) |
| { |
| final String[] elements = ExceptionUtil.splitString( depends, "," ); |
| final ArrayList dependsList = new ArrayList(); |
| |
| for( int i = 0; i < elements.length; i++ ) |
| { |
| final String dependency = elements[ i ].trim(); |
| |
| if( 0 == dependency.length() ) |
| { |
| throw new Exception( "Discovered empty dependency in target " + |
| target.getName() + " at " + target.getLocation() ); |
| } |
| |
| getLogger().debug( "Target dependency: " + dependency ); |
| dependsList.add( dependency ); |
| } |
| |
| dependencies = (String[])dependsList.toArray( new String[ 0 ] ); |
| } |
| |
| final Target defaultTarget = |
| new Target( condition, target.getChildren(), dependencies ); |
| |
| //add target to project |
| project.addTarget( name, defaultTarget ); |
| } |
| |
| protected boolean validName( final String name ) |
| { |
| if( -1 != name.indexOf( "->" ) ) return false; |
| else return true; |
| } |
| } |