blob: 311e9020f375ca752433a8e986cd9243c944634a [file] [log] [blame]
package org.apache.maven.doxia.docrenderer;
/*
* 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.IOException;
import java.io.Reader;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.maven.doxia.Doxia;
import org.apache.maven.doxia.document.DocumentModel;
import org.apache.maven.doxia.document.io.xpp3.DocumentXpp3Reader;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.parser.ParseException;
import org.apache.maven.doxia.parser.Parser;
import org.apache.maven.doxia.parser.manager.ParserNotFoundException;
import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
import org.apache.maven.doxia.module.site.SiteModule;
import org.apache.maven.doxia.module.site.manager.SiteModuleManager;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
/**
* Abstract <code>document</code> renderer.
*
* @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
* @author ltheussl
* @version $Id$
* @since 1.1
*/
public abstract class AbstractDocumentRenderer
extends AbstractLogEnabled
implements DocumentRenderer
{
/** @plexus.requirement */
protected SiteModuleManager siteModuleManager;
/** @plexus.requirement */
protected Doxia doxia;
/**
* The common base directory of source files.
*/
private String baseDir;
//--------------------------------------------
//
//--------------------------------------------
/**
* Render an aggregate document from the files found in a Map.
*
* @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the
* source files (relative to {@link #getBaseDir() baseDir}), and the corresponding SiteModule as values.
* @param outputDirectory the output directory where the aggregate document should be generated.
* @param documentModel the document model, containing all the metadata, etc.
* @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
* @throws java.io.IOException if any
*/
public abstract void render( Map filesToProcess, File outputDirectory, DocumentModel documentModel )
throws DocumentRendererException, IOException;
//--------------------------------------------
//
//--------------------------------------------
/** {@inheritDoc} */
public void render( Collection files, File outputDirectory, DocumentModel documentModel )
throws DocumentRendererException, IOException
{
render( getFilesToProcess( files ), outputDirectory, documentModel );
}
/** {@inheritDoc} */
public void render( File baseDirectory, File outputDirectory, DocumentModel documentModel )
throws DocumentRendererException, IOException
{
render( getFilesToProcess( baseDirectory ), outputDirectory, documentModel );
}
/**
* Render a document from the files found in baseDirectory. This just forwards to
* {@link #render(File,File,DocumentModel)} with a new DocumentModel.
*
* @param baseDirectory the directory containing the source files.
* This should follow the standard Maven convention, ie containing all the site modules.
* @param outputDirectory the output directory where the document should be generated.
* @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
* @throws java.io.IOException if any
* @see #render(File, File, DocumentModel)
*/
public void render( File baseDirectory, File outputDirectory )
throws DocumentRendererException, IOException
{
render( baseDirectory, outputDirectory, (DocumentModel) null );
}
/**
* Render a document from the files found in baseDirectory.
*
* @param baseDirectory the directory containing the source files.
* This should follow the standard Maven convention, ie containing all the site modules.
* @param outputDirectory the output directory where the document should be generated.
* @param documentDescriptor a file containing the document model.
* If this file does not exist or is null, some default settings will be used.
* @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
* @throws java.io.IOException if any
* @see #render(File, File) if documentDescriptor does not exist or is null
* @see #render(Map, File, DocumentModel) otherwise
*/
public void render( File baseDirectory, File outputDirectory, File documentDescriptor )
throws DocumentRendererException, IOException
{
if ( ( documentDescriptor == null ) || ( !documentDescriptor.exists() ) )
{
if ( getLogger().isWarnEnabled() )
{
getLogger().warn( "No documentDescriptor found: using default settings!" );
}
render( baseDirectory, outputDirectory );
}
else
{
render( getFilesToProcess( baseDirectory ), outputDirectory, readDocumentModel( documentDescriptor ) );
}
}
/**
* Render documents separately for each file found in a Map.
*
* @param filesToProcess the Map of Files to process. The Map should contain as keys the paths of the
* source files (relative to {@link #getBaseDir() baseDir}), and the corresponding SiteModule as values.
* @param outputDirectory the output directory where the documents should be generated.
* @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
* @throws java.io.IOException if any
* @since 1.1.1
*/
public void renderIndividual( Map filesToProcess, File outputDirectory )
throws DocumentRendererException, IOException
{
// nop
}
/**
* Returns a Map of files to process. The Map contains as keys the paths of the source files
* (relative to {@link #getBaseDir() baseDir}), and the corresponding SiteModule as values.
*
* @param baseDirectory the directory containing the source files.
* This should follow the standard Maven convention, ie containing all the site modules.
* @return a Map of files to process.
* @throws java.io.IOException in case of a problem reading the files under baseDirectory.
* @throws org.apache.maven.doxia.docrenderer.DocumentRendererException if any
*/
public Map getFilesToProcess( File baseDirectory )
throws IOException, DocumentRendererException
{
if ( !baseDirectory.isDirectory() )
{
if ( getLogger().isWarnEnabled() )
{
getLogger().warn( "No files found to process!" );
}
return new HashMap();
}
setBaseDir( baseDirectory.getAbsolutePath() );
Map filesToProcess = new LinkedHashMap();
Map duplicatesFiles = new LinkedHashMap();
for ( Iterator i = siteModuleManager.getSiteModules().iterator(); i.hasNext(); )
{
SiteModule module = (SiteModule) i.next();
File moduleBasedir = new File( baseDirectory, module.getSourceDirectory() );
if ( moduleBasedir.exists() )
{
// TODO: handle in/excludes
List allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", null, false );
String lowerCaseExtension = module.getExtension().toLowerCase( Locale.ENGLISH );
List docs = new LinkedList( allFiles );
// Take care of extension case
for ( Iterator it = docs.iterator(); it.hasNext(); )
{
String name = it.next().toString().trim();
if ( !name.toLowerCase( Locale.ENGLISH ).endsWith( "." + lowerCaseExtension ) )
{
it.remove();
}
}
for ( Iterator j = docs.iterator(); j.hasNext(); )
{
String filePath = j.next().toString().trim();
if ( filePath.lastIndexOf( "." ) > 0 )
{
String key = filePath.substring( 0, filePath.lastIndexOf( "." ) );
if ( duplicatesFiles.containsKey( key ) )
{
throw new DocumentRendererException( "Files '" + module.getSourceDirectory()
+ File.separator + filePath + "' clashes with existing '"
+ duplicatesFiles.get( key ) + "'." );
}
duplicatesFiles.put( key, module.getSourceDirectory() + File.separator + filePath );
}
filesToProcess.put( filePath, module );
}
}
}
return filesToProcess;
}
/**
* Returns a Map of files to process. The Map contains as keys the paths of the source files
* (relative to {@link #getBaseDir() baseDir}), and the corresponding SiteModule as values.
*
* @param files The Collection of source files.
* @return a Map of files to process.
*/
public Map getFilesToProcess( Collection files )
{
// ----------------------------------------------------------------------
// Map all the file names to parser ids
// ----------------------------------------------------------------------
Map filesToProcess = new HashMap();
for ( Iterator it = siteModuleManager.getSiteModules().iterator(); it.hasNext(); )
{
SiteModule siteModule = (SiteModule) it.next();
String extension = "." + siteModule.getExtension();
String sourceDirectory = File.separator + siteModule.getSourceDirectory() + File.separator;
for ( Iterator j = files.iterator(); j.hasNext(); )
{
String file = (String) j.next();
// first check if the file path contains one of the recognized source dir identifiers
// (there's trouble if a pathname contains 2 identifiers), then match file extensions (not unique).
if ( file.indexOf( sourceDirectory ) != -1 )
{
filesToProcess.put( file, siteModule );
}
else if ( file.toLowerCase( Locale.ENGLISH ).endsWith( extension ) )
{
// don't overwrite if it's there already
if ( !filesToProcess.containsKey( file ) )
{
filesToProcess.put( file, siteModule );
}
}
}
}
return filesToProcess;
}
/** {@inheritDoc} */
public DocumentModel readDocumentModel( File documentDescriptor )
throws DocumentRendererException, IOException
{
DocumentModel documentModel;
Reader reader = null;
try
{
reader = ReaderFactory.newXmlReader( documentDescriptor );
documentModel = new DocumentXpp3Reader().read( reader );
}
catch ( XmlPullParserException e )
{
throw new DocumentRendererException( "Error parsing document descriptor", e );
}
finally
{
IOUtil.close( reader );
}
return documentModel;
}
/**
* Sets the current base directory.
*
* @param newDir the absolute path to the base directory to set.
*/
public void setBaseDir( String newDir )
{
this.baseDir = newDir;
}
/**
* Return the current base directory.
*
* @return the current base directory.
*/
public String getBaseDir()
{
return this.baseDir;
}
//--------------------------------------------
//
//--------------------------------------------
/**
* Parse a source document into a sink.
*
* @param fullDocPath absolute path to the source document.
* @param parserId determines the parser to use.
* @param sink the sink to receive the events.
* @throws org.apache.maven.doxia.docrenderer.DocumentRendererException in case of a parsing error.
* @throws java.io.IOException if the source document cannot be opened.
*/
protected void parse( String fullDocPath, String parserId, Sink sink )
throws DocumentRendererException, IOException
{
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "Parsing file " + fullDocPath );
}
Reader reader = null;
try
{
File f = new File( fullDocPath );
Parser parser = doxia.getParser( parserId );
switch ( parser.getType() )
{
case Parser.XML_TYPE:
reader = ReaderFactory.newXmlReader( f );
break;
case Parser.TXT_TYPE:
case Parser.UNKNOWN_TYPE:
default:
// TODO Platform dependent?
reader = ReaderFactory.newPlatformReader( f );
}
sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) );
doxia.parse( reader, parserId, sink );
}
catch ( ParserNotFoundException e )
{
throw new DocumentRendererException( "No parser '" + parserId
+ "' found for " + fullDocPath + ": " + e.getMessage() );
}
catch ( ParseException e )
{
throw new DocumentRendererException( "Error parsing " + fullDocPath + ": " + e.getMessage(), e );
}
finally
{
IOUtil.close( reader );
sink.flush();
}
}
/**
* Copies the contents of the resource directory to an output folder.
*
* @param outputDirectory the destination folder.
* @throws java.io.IOException if any.
*/
protected void copyResources( File outputDirectory )
throws IOException
{
File resourcesDirectory = new File( getBaseDir(), "resources" );
if ( !resourcesDirectory.isDirectory() )
{
return;
}
if ( !outputDirectory.exists() )
{
outputDirectory.mkdirs();
}
copyDirectory( resourcesDirectory, outputDirectory );
}
/**
* Copy content of a directory, excluding scm-specific files.
*
* @param source directory that contains the files and sub-directories to be copied.
* @param destination destination folder.
* @throws java.io.IOException if any.
*/
protected void copyDirectory( File source, File destination )
throws IOException
{
if ( source.isDirectory() && destination.isDirectory() )
{
DirectoryScanner scanner = new DirectoryScanner();
String[] includedResources = {"**/**"};
scanner.setIncludes( includedResources );
scanner.addDefaultExcludes();
scanner.setBasedir( source );
scanner.scan();
List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
for ( Iterator j = includedFiles.iterator(); j.hasNext(); )
{
String name = (String) j.next();
File sourceFile = new File( source, name );
File destinationFile = new File( destination, name );
FileUtils.copyFile( sourceFile, destinationFile );
}
}
}
/**
* @param documentModel not null
* @return the output name defined in the documentModel without the output extension. If the output name is not
* defined, return target by default.
* @since 1.1.1
* @see org.apache.maven.doxia.document.DocumentModel#getOutputName()
* @see #getOutputExtension()
*/
protected String getOutputName( DocumentModel documentModel )
{
String outputName = documentModel.getOutputName();
if ( outputName == null )
{
getLogger().info( "No outputName is defined in the document descriptor. Using 'target'" );
documentModel.setOutputName( "target" );
}
outputName = outputName.trim();
if ( outputName.toLowerCase( Locale.ENGLISH ).endsWith( "." + getOutputExtension() ) )
{
outputName =
outputName.substring( 0, outputName.toLowerCase( Locale.ENGLISH )
.lastIndexOf( "." + getOutputExtension() ) );
}
documentModel.setOutputName( outputName );
return documentModel.getOutputName();
}
}