( childList.size() + parentList.size() );
+
+ for ( Logo logo : parentList )
+ {
+ if ( !logos.contains( logo ) )
+ {
+ final Logo clone = logo.clone();
+
+ rebaseLogoPaths( clone, urlContainer );
+
+ logos.add( clone );
+ }
+ }
+
+ for ( Logo logo : childList )
+ {
+ if ( !logos.contains( logo ) )
+ {
+ logos.add( logo );
+ }
+ }
+
+ return logos;
+ }
+
+ // relativize only affects absolute links, if the link has the same scheme, host and port
+ // as the base, it is made into a relative link as viewed from the base
+ private String relativizeLink( final String link, final String baseUri )
+ {
+ if ( link == null || baseUri == null )
+ {
+ return link;
+ }
+
+ // this shouldn't be necessary, just to swallow mal-formed hrefs
+ try
+ {
+ final URIPathDescriptor path = new URIPathDescriptor( baseUri, link );
+
+ return path.relativizeLink().toString();
+ }
+ catch ( IllegalArgumentException e )
+ {
+ return link;
+ }
+ }
+
+ /**
+ * URL rebaser: based on an old and a new path, can rebase a link based on old path to a value based on the new
+ * path.
+ */
+ private static class URLRebaser
+ {
+
+ private final String oldPath;
+
+ private final String newPath;
+
+ /**
+ * Construct a URL rebaser.
+ *
+ * @param oldPath the old path.
+ * @param newPath the new path.
+ */
+ URLRebaser( final String oldPath, final String newPath )
+ {
+ this.oldPath = oldPath;
+ this.newPath = newPath;
+ }
+
+ /**
+ * Get the new path.
+ *
+ * @return the new path.
+ */
+ public String getNewPath()
+ {
+ return this.newPath;
+ }
+
+ /**
+ * Get the old path.
+ *
+ * @return the old path.
+ */
+ public String getOldPath()
+ {
+ return this.oldPath;
+ }
+
+ /**
+ * Rebase only affects relative links, a relative link wrt an old base gets translated,
+ * so it points to the same location as viewed from a new base
+ */
+ public String rebaseLink( final String link )
+ {
+ if ( link == null || getOldPath() == null )
+ {
+ return link;
+ }
+
+ if ( link.contains( "${project." ) )
+ {
+ throw new IllegalArgumentException( "site.xml late interpolation ${project.*} expression found"
+ + " in link: '" + link + "'. Use early interpolation ${this.*}" );
+ }
+
+ final URIPathDescriptor oldPath = new URIPathDescriptor( getOldPath(), link );
+
+ return oldPath.rebaseLink( getNewPath() ).toString();
+ }
+ }
+}
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_141/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_141/npe.json
new file mode 100644
index 000000000..19914fc51
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultDecorationModelInheritanceAssembler_141/npe.json
@@ -0,0 +1,7 @@
+{
+ "filepath": "doxia-decoration-model/src/main/java/org/apache/maven/doxia/site/decoration/inheritance/DefaultDecorationModelInheritanceAssembler.java",
+ "line": 137,
+ "npe_method": "resolvePaths",
+ "deref_field": "getBody",
+ "npe_class": "DefaultDecorationModelInheritanceAssembler"
+}
\ No newline at end of file
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_190/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_190/buggy.java
new file mode 100644
index 000000000..2a14c2ead
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_190/buggy.java
@@ -0,0 +1,1178 @@
+package org.apache.maven.doxia.siterenderer;
+
+/*
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+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 java.util.Properties;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.Restriction;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.doxia.Doxia;
+import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
+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.site.decoration.DecorationModel;
+import org.apache.maven.doxia.site.decoration.PublishDate;
+import org.apache.maven.doxia.site.skin.SkinModel;
+import org.apache.maven.doxia.site.skin.io.xpp3.SkinXpp3Reader;
+import org.apache.maven.doxia.parser.module.ParserModule;
+import org.apache.maven.doxia.parser.module.ParserModuleManager;
+import org.apache.maven.doxia.parser.module.ParserModuleNotFoundException;
+import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
+import org.apache.maven.doxia.util.XmlValidator;
+import org.apache.velocity.Template;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.Scope;
+import org.apache.velocity.tools.ToolManager;
+import org.apache.velocity.tools.config.ConfigurationUtils;
+import org.apache.velocity.tools.config.EasyFactoryConfiguration;
+import org.apache.velocity.tools.config.FactoryConfiguration;
+import org.apache.velocity.tools.generic.AlternatorTool;
+import org.apache.velocity.tools.generic.ClassTool;
+import org.apache.velocity.tools.generic.ComparisonDateTool;
+import org.apache.velocity.tools.generic.ContextTool;
+import org.apache.velocity.tools.generic.ConversionTool;
+import org.apache.velocity.tools.generic.DisplayTool;
+import org.apache.velocity.tools.generic.EscapeTool;
+import org.apache.velocity.tools.generic.FieldTool;
+import org.apache.velocity.tools.generic.LinkTool;
+import org.apache.velocity.tools.generic.LoopTool;
+import org.apache.velocity.tools.generic.MathTool;
+import org.apache.velocity.tools.generic.NumberTool;
+import org.apache.velocity.tools.generic.RenderTool;
+import org.apache.velocity.tools.generic.ResourceTool;
+import org.apache.velocity.tools.generic.SortTool;
+import org.apache.velocity.tools.generic.XmlTool;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.i18n.I18N;
+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.Os;
+import org.codehaus.plexus.util.PathTool;
+import org.codehaus.plexus.util.PropertyUtils;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.WriterFactory;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.codehaus.plexus.velocity.VelocityComponent;
+
+/**
+ * DefaultSiteRenderer class.
+ *
+ * @author Emmanuel Venisse
+ * @author Vincent Siveton
+ * @since 1.0
+ */
+@Component( role = Renderer.class )
+public class DefaultSiteRenderer
+ extends AbstractLogEnabled
+ implements Renderer
+{
+ // ----------------------------------------------------------------------
+ // Requirements
+ // ----------------------------------------------------------------------
+
+ @Requirement
+ private VelocityComponent velocity;
+
+ @Requirement
+ private ParserModuleManager parserModuleManager;
+
+ @Requirement
+ private Doxia doxia;
+
+ @Requirement
+ private I18N i18n;
+
+ @Requirement
+ private PlexusContainer plexus;
+
+ private static final String RESOURCE_DIR = "org/apache/maven/doxia/siterenderer/resources";
+
+ private static final String DEFAULT_TEMPLATE = RESOURCE_DIR + "/default-site.vm";
+
+ private static final String SKIN_TEMPLATE_LOCATION = "META-INF/maven/site.vm";
+
+ private static final String TOOLS_LOCATION = "META-INF/maven/site-tools.xml";
+
+ // ----------------------------------------------------------------------
+ // Renderer implementation
+ // ----------------------------------------------------------------------
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext )
+ throws IOException, RendererException
+ {
+ return locateDocumentFiles( siteRenderingContext, false );
+ }
+
+ /** {@inheritDoc} */
+/**
+ * {@inheritDoc }
+ */
+public java.util.Map locateDocumentFiles(org.apache.maven.doxia.siterenderer.SiteRenderingContext siteRenderingContext, boolean editable) throws java.io.IOException, org.apache.maven.doxia.siterenderer.RendererException {
+ java.util.Map files = new java.util.LinkedHashMap();
+ java.util.Map moduleExcludes = siteRenderingContext.getModuleExcludes();
+ // look in every site directory (in general src/site or target/generated-site)
+ for (java.io.File siteDirectory : siteRenderingContext.getSiteDirectories()) {
+ if (siteDirectory.exists()) {
+ java.util.Collection modules = parserModuleManager.getParserModules();
+ // use every Doxia parser module
+ for (org.apache.maven.doxia.parser.module.ParserModule module : modules) {
+ java.io.File moduleBasedir = new java.io.File(siteDirectory, module.getSourceDirectory());
+ java.lang.String excludes = /* NPEX_NULL_EXP */
+ moduleExcludes.get(module.getParserId());
+ addModuleFiles(siteRenderingContext.getRootDirectory(), moduleBasedir, module, excludes, files, editable);
+ }
+ }
+ }
+ // look in specific modules directories (used for old Maven 1.x site layout: xdoc and fml docs in /xdocs)
+ for (org.apache.maven.doxia.siterenderer.ExtraDoxiaModuleReference module : siteRenderingContext.getModules()) {
+ try {
+ org.apache.maven.doxia.parser.module.ParserModule parserModule = parserModuleManager.getParserModule(module.getParserId());
+ java.lang.String excludes = (moduleExcludes == null) ? null : moduleExcludes.get(module.getParserId());
+ addModuleFiles(siteRenderingContext.getRootDirectory(), module.getBasedir(), parserModule, excludes, files, editable);
+ } catch (org.apache.maven.doxia.parser.module.ParserModuleNotFoundException e) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("Unable to find module: " + e.getMessage(), e);
+ }
+ }
+ return files;
+}
+
+ private List filterExtensionIgnoreCase( List fileNames, String extension )
+ {
+ List filtered = new LinkedList( fileNames );
+ for ( Iterator it = filtered.iterator(); it.hasNext(); )
+ {
+ String name = it.next();
+
+ // Take care of extension case
+ if ( !endsWithIgnoreCase( name, extension ) )
+ {
+ it.remove();
+ }
+ }
+ return filtered;
+ }
+
+ private void addModuleFiles( File rootDir, File moduleBasedir, ParserModule module, String excludes,
+ Map files, boolean editable )
+ throws IOException, RendererException
+ {
+ if ( !moduleBasedir.exists() || ArrayUtils.isEmpty( module.getExtensions() ) )
+ {
+ return;
+ }
+
+ String moduleRelativePath =
+ PathTool.getRelativeFilePath( rootDir.getAbsolutePath(), moduleBasedir.getAbsolutePath() );
+
+ List allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", excludes, false );
+
+ for ( String extension : module.getExtensions() )
+ {
+ String fullExtension = "." + extension;
+
+ List docs = filterExtensionIgnoreCase( allFiles, fullExtension );
+
+ // *..vm
+ List velocityFiles = filterExtensionIgnoreCase( allFiles, fullExtension + ".vm" );
+
+ docs.addAll( velocityFiles );
+
+ for ( String doc : docs )
+ {
+ RenderingContext context = new RenderingContext( moduleBasedir, moduleRelativePath, doc,
+ module.getParserId(), extension, editable );
+
+ // TODO: DOXIA-111: we need a general filter here that knows how to alter the context
+ if ( endsWithIgnoreCase( doc, ".vm" ) )
+ {
+ context.setAttribute( "velocity", "true" );
+ }
+
+ String key = context.getOutputName();
+ key = StringUtils.replace( key, "\\", "/" );
+
+ if ( files.containsKey( key ) )
+ {
+ DocumentRenderer renderer = files.get( key );
+
+ RenderingContext originalContext = renderer.getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' clashes with existing '" + originalDoc + "'." );
+ }
+ // -----------------------------------------------------------------------
+ // Handle key without case differences
+ // -----------------------------------------------------------------------
+ for ( Map.Entry entry : files.entrySet() )
+ {
+ if ( entry.getKey().equalsIgnoreCase( key ) )
+ {
+ RenderingContext originalContext = entry.getValue().getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
+ {
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator
+ + doc + "' clashes with existing '" + originalDoc + "'." );
+ }
+
+ if ( getLogger().isWarnEnabled() )
+ {
+ getLogger().warn( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' could clash with existing '" + originalDoc + "'." );
+ }
+ }
+ }
+
+ files.put( key, new DoxiaDocumentRenderer( context ) );
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void render( Collection documents, SiteRenderingContext siteRenderingContext,
+ File outputDirectory )
+ throws RendererException, IOException
+ {
+ for ( DocumentRenderer docRenderer : documents )
+ {
+ RenderingContext renderingContext = docRenderer.getRenderingContext();
+
+ File outputFile = new File( outputDirectory, docRenderer.getOutputName() );
+
+ File inputFile = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
+
+ boolean modified = !outputFile.exists() || ( inputFile.lastModified() > outputFile.lastModified() )
+ || ( siteRenderingContext.getDecoration().getLastModified() > outputFile.lastModified() );
+
+ if ( modified || docRenderer.isOverwrite() )
+ {
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Generating " + outputFile );
+ }
+
+ Writer writer = null;
+ try
+ {
+ if ( !docRenderer.isExternalReport() )
+ {
+ writer = WriterFactory.newWriter( outputFile, siteRenderingContext.getOutputEncoding() );
+ }
+ docRenderer.renderDocument( writer, this, siteRenderingContext );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ else
+ {
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( inputFile + " unchanged, not regenerating..." );
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void renderDocument( Writer writer, RenderingContext docRenderingContext, SiteRenderingContext siteContext )
+ throws RendererException, FileNotFoundException, UnsupportedEncodingException
+ {
+ SiteRendererSink sink = new SiteRendererSink( docRenderingContext );
+
+ File doc = new File( docRenderingContext.getBasedir(), docRenderingContext.getInputName() );
+
+ Reader reader = null;
+ try
+ {
+ String resource = doc.getAbsolutePath();
+
+ Parser parser = doxia.getParser( docRenderingContext.getParserId() );
+ // DOXIASITETOOLS-146 don't render comments from source markup
+ parser.setEmitComments( false );
+
+ // TODO: DOXIA-111: the filter used here must be checked generally.
+ if ( docRenderingContext.getAttribute( "velocity" ) != null )
+ {
+ getLogger().debug( "Processing Velocity for " + docRenderingContext.getDoxiaSourcePath() );
+ try
+ {
+ Context vc = createDocumentVelocityContext( docRenderingContext, siteContext );
+
+ StringWriter sw = new StringWriter();
+
+ velocity.getEngine().mergeTemplate( resource, siteContext.getInputEncoding(), vc, sw );
+
+ String doxiaContent = sw.toString();
+
+ if ( siteContext.getProcessedContentOutput() != null )
+ {
+ // save Velocity processing result, ie the Doxia content that will be parsed after
+ saveVelocityProcessedContent( docRenderingContext, siteContext, doxiaContent );
+ }
+
+ reader = new StringReader( doxiaContent );
+ }
+ catch ( VelocityException e )
+ {
+ throw new RendererException( "Error parsing " + docRenderingContext.getDoxiaSourcePath()
+ + " as a Velocity template: " + e.getMessage(), e );
+ }
+
+ if ( parser.getType() == Parser.XML_TYPE && siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ }
+ else
+ {
+ switch ( parser.getType() )
+ {
+ case Parser.XML_TYPE:
+ reader = ReaderFactory.newXmlReader( doc );
+ if ( siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ break;
+
+ case Parser.TXT_TYPE:
+ case Parser.UNKNOWN_TYPE:
+ default:
+ reader = ReaderFactory.newReader( doc, siteContext.getInputEncoding() );
+ }
+ }
+ sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) );
+
+ doxia.parse( reader, docRenderingContext.getParserId(), sink );
+ }
+ catch ( ParserNotFoundException e )
+ {
+ throw new RendererException( "Error getting a parser for '" + doc + "': " + e.getMessage(), e );
+ }
+ catch ( ParseException e )
+ {
+ StringBuilder errorMsgBuilder = new StringBuilder();
+ errorMsgBuilder.append( "Error parsing '" ).append( doc ).append( "': " );
+ if ( e.getLineNumber() > 0 )
+ {
+ errorMsgBuilder.append( "line [" ).append( e.getLineNumber() ).append( "] " );
+ }
+ errorMsgBuilder.append( e.getMessage() );
+ throw new RendererException( errorMsgBuilder.toString(), e );
+ }
+ catch ( IOException e )
+ {
+ throw new RendererException( "IOException when processing '" + doc + "'", e );
+ }
+ finally
+ {
+ sink.flush();
+
+ sink.close();
+
+ IOUtil.close( reader );
+ }
+
+ mergeDocumentIntoSite( writer, (DocumentContent) sink, siteContext );
+ }
+
+ private void saveVelocityProcessedContent( RenderingContext docRenderingContext, SiteRenderingContext siteContext,
+ String doxiaContent )
+ throws IOException
+ {
+ if ( !siteContext.getProcessedContentOutput().exists() )
+ {
+ siteContext.getProcessedContentOutput().mkdirs();
+ }
+
+ String input = docRenderingContext.getInputName();
+ File outputFile = new File( siteContext.getProcessedContentOutput(),
+ input.substring( 0, input.length() - 3 ) );
+
+ File outputParent = outputFile.getParentFile();
+ if ( !outputParent.exists() )
+ {
+ outputParent.mkdirs();
+ }
+
+ FileUtils.fileWrite( outputFile, siteContext.getInputEncoding(), doxiaContent );
+ }
+
+ /**
+ * Creates a Velocity Context with all generic tools configured wit the site rendering context.
+ *
+ * @param siteRenderingContext the site rendering context
+ * @return a Velocity tools managed context
+ */
+ protected Context createToolManagedVelocityContext( SiteRenderingContext siteRenderingContext )
+ {
+ Locale locale = siteRenderingContext.getLocale();
+ String dateFormat = siteRenderingContext.getDecoration().getPublishDate().getFormat();
+
+ EasyFactoryConfiguration config = new EasyFactoryConfiguration( false );
+ config.property( "safeMode", Boolean.FALSE );
+ config.toolbox( Scope.REQUEST )
+ .tool( ContextTool.class )
+ .tool( LinkTool.class )
+ .tool( LoopTool.class )
+ .tool( RenderTool.class );
+ config.toolbox( Scope.APPLICATION ).property( "locale", locale )
+ .tool( AlternatorTool.class )
+ .tool( ClassTool.class )
+ .tool( ComparisonDateTool.class ).property( "format", dateFormat )
+ .tool( ConversionTool.class ).property( "dateFormat", dateFormat )
+ .tool( DisplayTool.class )
+ .tool( EscapeTool.class )
+ .tool( FieldTool.class )
+ .tool( MathTool.class )
+ .tool( NumberTool.class )
+ .tool( ResourceTool.class ).property( "bundles", new String[] { "site-renderer" } )
+ .tool( SortTool.class )
+ .tool( XmlTool.class );
+
+ FactoryConfiguration customConfig = ConfigurationUtils.findInClasspath( TOOLS_LOCATION );
+
+ if ( customConfig != null )
+ {
+ config.addConfiguration( customConfig );
+ }
+
+ ToolManager manager = new ToolManager( false, false );
+ manager.configure( config );
+
+ return manager.createContext();
+ }
+
+ /**
+ * Create a Velocity Context for a Doxia document, containing every information about rendered document.
+ *
+ * @param sink the site renderer sink for the document
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createDocumentVelocityContext( RenderingContext renderingContext,
+ SiteRenderingContext siteRenderingContext )
+ {
+ Context context = createToolManagedVelocityContext( siteRenderingContext );
+ // ----------------------------------------------------------------------
+ // Data objects
+ // ----------------------------------------------------------------------
+
+ context.put( "relativePath", renderingContext.getRelativePath() );
+
+ String currentFileName = renderingContext.getOutputName().replace( '\\', '/' );
+ context.put( "currentFileName", currentFileName );
+
+ context.put( "alignedFileName", PathTool.calculateLink( currentFileName, renderingContext.getRelativePath() ) );
+
+ context.put( "decoration", siteRenderingContext.getDecoration() );
+
+ Locale locale = siteRenderingContext.getLocale();
+ context.put( "locale", locale );
+ context.put( "supportedLocales", Collections.unmodifiableList( siteRenderingContext.getSiteLocales() ) );
+
+ context.put( "currentDate", new Date() );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateRevision", sdf.format( new Date() ) );
+
+ context.put( "publishDate", siteRenderingContext.getPublishDate() );
+
+ PublishDate publishDate = siteRenderingContext.getDecoration().getPublishDate();
+ DateFormat dateFormat = new SimpleDateFormat( publishDate.getFormat(), locale );
+ context.put( "dateFormat", dateFormat );
+
+ // doxiaSiteRendererVersion
+ InputStream inputStream = this.getClass().getResourceAsStream( "/META-INF/"
+ + "maven/org.apache.maven.doxia/doxia-site-renderer/pom.properties" );
+ Properties properties = PropertyUtils.loadProperties( inputStream );
+ if ( inputStream == null )
+ {
+ getLogger().debug( "pom.properties for doxia-site-renderer could not be found." );
+ }
+ else if ( properties == null )
+ {
+ getLogger().debug( "Failed to load pom.properties, so doxiaVersion is not available"
+ + " in the Velocity context." );
+ }
+ else
+ {
+ context.put( "doxiaSiteRendererVersion", properties.getProperty( "version" ) );
+ }
+
+ // Add user properties
+ Map templateProperties = siteRenderingContext.getTemplateProperties();
+
+ if ( templateProperties != null )
+ {
+ for ( Map.Entry entry : templateProperties.entrySet() )
+ {
+ context.put( entry.getKey(), entry.getValue() );
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Tools
+ // ----------------------------------------------------------------------
+
+ context.put( "PathTool", new PathTool() );
+
+ context.put( "FileUtils", new FileUtils() );
+
+ context.put( "StringUtils", new StringUtils() );
+
+ context.put( "i18n", i18n );
+
+ context.put( "plexus", plexus );
+ return context;
+ }
+
+ /**
+ * Create a Velocity Context for the site template decorating the document. In addition to all the informations
+ * from the document, this context contains data gathered in {@link SiteRendererSink} during document rendering.
+ *
+ * @param content the document content to be merged into the template
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createSiteTemplateVelocityContext( DocumentContent content,
+ SiteRenderingContext siteRenderingContext )
+ {
+ // first get the context from document
+ Context context = createDocumentVelocityContext( content.getRenderingContext(), siteRenderingContext );
+
+ // then add data objects from rendered document
+
+ // Add infos from document
+ context.put( "authors", content.getAuthors() );
+
+ context.put( "shortTitle", content.getTitle() );
+
+ // DOXIASITETOOLS-70: Prepend the project name to the title, if any
+ String title = "";
+ if ( siteRenderingContext.getDecoration() != null
+ && siteRenderingContext.getDecoration().getName() != null )
+ {
+ title = siteRenderingContext.getDecoration().getName();
+ }
+ else if ( siteRenderingContext.getDefaultWindowTitle() != null )
+ {
+ title = siteRenderingContext.getDefaultWindowTitle();
+ }
+
+ if ( title.length() > 0 )
+ {
+ title += " – "; // Symbol Name: En Dash, Html Entity: –
+ }
+ title += content.getTitle();
+
+ context.put( "title", title );
+
+ context.put( "headContent", content.getHead() );
+
+ context.put( "bodyContent", content.getBody() );
+
+ // document date (got from Doxia Sink date() API)
+ String documentDate = content.getDate();
+ if ( StringUtils.isNotEmpty( documentDate ) )
+ {
+ context.put( "documentDate", documentDate );
+
+ // deprecated variables that rework the document date, suppose one semantics over others
+ // (ie creation date, while it may be last modification date if the document writer decided so)
+ // see DOXIASITETOOLS-20 for the beginning and DOXIASITETOOLS-164 for the end of this story
+ try
+ {
+ // we support only ISO 8601 date
+ Date creationDate = new SimpleDateFormat( "yyyy-MM-dd" ).parse( documentDate );
+
+ context.put( "creationDate", creationDate );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateCreation", sdf.format( creationDate ) );
+ }
+ catch ( java.text.ParseException e )
+ {
+ getLogger().warn( "Could not parse date '" + documentDate + "' from "
+ + content.getRenderingContext().getInputName()
+ + " (expected yyyy-MM-dd format), ignoring!" );
+ }
+ }
+
+ // document rendering context, to get eventual inputName
+ context.put( "docRenderingContext", content.getRenderingContext() );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
+ throws RendererException
+ {
+ mergeDocumentIntoSite( writer, sink, siteRenderingContext );
+ }
+
+ /** {@inheritDoc} */
+ public void mergeDocumentIntoSite( Writer writer, DocumentContent content,
+ SiteRenderingContext siteRenderingContext )
+ throws RendererException
+ {
+ String templateName = siteRenderingContext.getTemplateName();
+
+ getLogger().debug( "Processing Velocity for template " + templateName + " on "
+ + content.getRenderingContext().getInputName() );
+
+ Context context = createSiteTemplateVelocityContext( content, siteRenderingContext );
+
+ ClassLoader old = null;
+
+ if ( siteRenderingContext.getTemplateClassLoader() != null )
+ {
+ // -------------------------------------------------------------------------
+ // If no template classloader was set we'll just use the context classloader
+ // -------------------------------------------------------------------------
+
+ old = Thread.currentThread().getContextClassLoader();
+
+ Thread.currentThread().setContextClassLoader( siteRenderingContext.getTemplateClassLoader() );
+ }
+
+ try
+ {
+ Template template;
+ Artifact skin = siteRenderingContext.getSkin();
+
+ try
+ {
+ SkinModel skinModel = siteRenderingContext.getSkinModel();
+ String encoding = ( skinModel == null ) ? null : skinModel.getEncoding();
+
+ template = ( encoding == null ) ? velocity.getEngine().getTemplate( templateName )
+ : velocity.getEngine().getTemplate( templateName, encoding );
+ }
+ catch ( ParseErrorException pee )
+ {
+ throw new RendererException( "Velocity parsing error while reading the site decoration template "
+ + ( ( skin == null ) ? ( "'" + templateName + "'" ) : ( "from " + skin.getId() + " skin" ) ),
+ pee );
+ }
+ catch ( ResourceNotFoundException rnfe )
+ {
+ throw new RendererException( "Could not find the site decoration template "
+ + ( ( skin == null ) ? ( "'" + templateName + "'" ) : ( "from " + skin.getId() + " skin" ) ),
+ rnfe );
+ }
+
+ try
+ {
+ StringWriter sw = new StringWriter();
+ template.merge( context, sw );
+ writer.write( sw.toString().replaceAll( "\r?\n", SystemUtils.LINE_SEPARATOR ) );
+ }
+ catch ( VelocityException ve )
+ {
+ throw new RendererException( "Velocity error while merging site decoration template.", ve );
+ }
+ catch ( IOException ioe )
+ {
+ throw new RendererException( "IO exception while merging site decoration template.", ioe );
+ }
+ }
+ finally
+ {
+ IOUtil.close( writer );
+
+ if ( old != null )
+ {
+ Thread.currentThread().setContextClassLoader( old );
+ }
+ }
+ }
+
+ private SiteRenderingContext createSiteRenderingContext( Map attributes, DecorationModel decoration,
+ String defaultWindowTitle, Locale locale )
+ {
+ SiteRenderingContext context = new SiteRenderingContext();
+
+ context.setTemplateProperties( attributes );
+ context.setLocale( locale );
+ context.setDecoration( decoration );
+ context.setDefaultWindowTitle( defaultWindowTitle );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public SiteRenderingContext createContextForSkin( Artifact skin, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws IOException, RendererException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setSkin( skin );
+
+ ZipFile zipFile = getZipFile( skin.getFile() );
+ InputStream in = null;
+
+ try
+ {
+ if ( zipFile.getEntry( SKIN_TEMPLATE_LOCATION ) != null )
+ {
+ context.setTemplateName( SKIN_TEMPLATE_LOCATION );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{skin.getFile().toURI().toURL()} ) );
+ }
+ else
+ {
+ context.setTemplateName( DEFAULT_TEMPLATE );
+ context.setTemplateClassLoader( getClass().getClassLoader() );
+ context.setUsingDefaultTemplate( true );
+ }
+
+ ZipEntry skinDescriptorEntry = zipFile.getEntry( SkinModel.SKIN_DESCRIPTOR_LOCATION );
+ if ( skinDescriptorEntry != null )
+ {
+ in = zipFile.getInputStream( skinDescriptorEntry );
+
+ SkinModel skinModel = new SkinXpp3Reader().read( in );
+ context.setSkinModel( skinModel );
+
+ String toolsPrerequisite =
+ skinModel.getPrerequisites() == null ? null : skinModel.getPrerequisites().getDoxiaSitetools();
+
+ Package p = DefaultSiteRenderer.class.getPackage();
+ String current = ( p == null ) ? null : p.getImplementationVersion();
+
+ if ( StringUtils.isNotBlank( toolsPrerequisite ) && ( current != null )
+ && !matchVersion( current, toolsPrerequisite ) )
+ {
+ throw new RendererException( "Cannot use skin: has " + toolsPrerequisite
+ + " Doxia Sitetools prerequisite, but current is " + current );
+ }
+ }
+ }
+ catch ( XmlPullParserException e )
+ {
+ throw new RendererException( "Failed to parse " + SkinModel.SKIN_DESCRIPTOR_LOCATION
+ + " skin descriptor from " + skin.getId() + " skin", e );
+ }
+ finally
+ {
+ IOUtil.close( in );
+ closeZipFile( zipFile );
+ }
+
+ return context;
+ }
+
+ boolean matchVersion( String current, String prerequisite )
+ throws RendererException
+ {
+ try
+ {
+ ArtifactVersion v = new DefaultArtifactVersion( current );
+ VersionRange vr = VersionRange.createFromVersionSpec( prerequisite );
+
+ boolean matched = false;
+ ArtifactVersion recommendedVersion = vr.getRecommendedVersion();
+ if ( recommendedVersion == null )
+ {
+ List restrictions = vr.getRestrictions();
+ for ( Restriction restriction : restrictions )
+ {
+ if ( restriction.containsVersion( v ) )
+ {
+ matched = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // only singular versions ever have a recommendedVersion
+ @SuppressWarnings( "unchecked" )
+ int compareTo = recommendedVersion.compareTo( v );
+ matched = ( compareTo <= 0 );
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Skin doxia-sitetools prerequisite: " + prerequisite + ", current: " + current
+ + ", matched = " + matched );
+ }
+
+ return matched;
+ }
+ catch ( InvalidVersionSpecificationException e )
+ {
+ throw new RendererException( "Invalid skin doxia-sitetools prerequisite: " + prerequisite, e );
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Deprecated
+ public SiteRenderingContext createContextForTemplate( File templateFile, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws MalformedURLException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setTemplateName( templateFile.getName() );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{templateFile.getParentFile().toURI().toURL()} ) );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File resourcesDirectory,
+ File outputDirectory )
+ throws IOException
+ {
+ throw new AssertionError( "copyResources( SiteRenderingContext, File, File ) is deprecated." );
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File outputDirectory )
+ throws IOException
+ {
+ if ( siteRenderingContext.getSkin() != null )
+ {
+ ZipFile file = getZipFile( siteRenderingContext.getSkin().getFile() );
+
+ try
+ {
+ for ( Enumeration extends ZipEntry> e = file.entries(); e.hasMoreElements(); )
+ {
+ ZipEntry entry = e.nextElement();
+
+ if ( !entry.getName().startsWith( "META-INF/" ) )
+ {
+ File destFile = new File( outputDirectory, entry.getName() );
+ if ( !entry.isDirectory() )
+ {
+ if ( destFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ destFile.getParentFile().mkdirs();
+
+ copyFileFromZip( file, entry, destFile );
+ }
+ else
+ {
+ destFile.mkdirs();
+ }
+ }
+ }
+ }
+ finally
+ {
+ closeZipFile( file );
+ }
+ }
+
+ if ( siteRenderingContext.isUsingDefaultTemplate() )
+ {
+ InputStream resourceList = getClass().getClassLoader()
+ .getResourceAsStream( RESOURCE_DIR + "/resources.txt" );
+
+ if ( resourceList != null )
+ {
+ Reader r = null;
+ LineNumberReader reader = null;
+ try
+ {
+ r = ReaderFactory.newReader( resourceList, ReaderFactory.UTF_8 );
+ reader = new LineNumberReader( r );
+
+ String line;
+
+ while ( ( line = reader.readLine() ) != null )
+ {
+ if ( line.startsWith( "#" ) || line.trim().length() == 0 )
+ {
+ continue;
+ }
+
+ InputStream is = getClass().getClassLoader().getResourceAsStream( RESOURCE_DIR + "/" + line );
+
+ if ( is == null )
+ {
+ throw new IOException( "The resource " + line + " doesn't exist." );
+ }
+
+ File outputFile = new File( outputDirectory, line );
+
+ if ( outputFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ OutputStream os = null;
+ try
+ {
+ // for the images
+ os = new FileOutputStream( outputFile );
+ IOUtil.copy( is, os );
+ }
+ finally
+ {
+ IOUtil.close( os );
+ }
+
+ IOUtil.close( is );
+ }
+ }
+ finally
+ {
+ IOUtil.close( reader );
+ IOUtil.close( r );
+ }
+ }
+ }
+
+ // Copy extra site resources
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ File resourcesDirectory = new File( siteDirectory, "resources" );
+
+ if ( resourcesDirectory != null && resourcesDirectory.exists() )
+ {
+ copyDirectory( resourcesDirectory, outputDirectory );
+ }
+ }
+
+ // Check for the existence of /css/site.css
+ File siteCssFile = new File( outputDirectory, "/css/site.css" );
+ if ( !siteCssFile.exists() )
+ {
+ // Create the subdirectory css if it doesn't exist, DOXIA-151
+ File cssDirectory = new File( outputDirectory, "/css/" );
+ boolean created = cssDirectory.mkdirs();
+ if ( created && getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created." );
+ }
+
+ // If the file is not there - create an empty file, DOXIA-86
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The file '" + siteCssFile.getAbsolutePath() + "' does not exist. Creating an empty file." );
+ }
+ Writer writer = null;
+ try
+ {
+ writer = WriterFactory.newWriter( siteCssFile, siteRenderingContext.getOutputEncoding() );
+ //DOXIA-290...the file should not be 0 bytes.
+ writer.write( "/* You can override this file with your own styles */" );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ }
+
+ private static void copyFileFromZip( ZipFile file, ZipEntry entry, File destFile )
+ throws IOException
+ {
+ FileOutputStream fos = new FileOutputStream( destFile );
+
+ try
+ {
+ IOUtil.copy( file.getInputStream( entry ), fos );
+ }
+ finally
+ {
+ IOUtil.close( fos );
+ }
+ }
+
+ /**
+ * Copy the directory
+ *
+ * @param source source file to be copied
+ * @param destination destination file
+ * @throws java.io.IOException if any
+ */
+ protected void copyDirectory( File source, File destination )
+ throws IOException
+ {
+ if ( source.exists() )
+ {
+ DirectoryScanner scanner = new DirectoryScanner();
+
+ String[] includedResources = {"**/**"};
+
+ scanner.setIncludes( includedResources );
+
+ scanner.addDefaultExcludes();
+
+ scanner.setBasedir( source );
+
+ scanner.scan();
+
+ List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
+
+ for ( String name : includedFiles )
+ {
+ File sourceFile = new File( source, name );
+
+ File destinationFile = new File( destination, name );
+
+ FileUtils.copyFile( sourceFile, destinationFile );
+ }
+ }
+ }
+
+ private Reader validate( Reader source, String resource )
+ throws ParseException, IOException
+ {
+ getLogger().debug( "Validating: " + resource );
+
+ try
+ {
+ String content = IOUtil.toString( new BufferedReader( source ) );
+
+ new XmlValidator( new PlexusLoggerWrapper( getLogger() ) ).validate( content );
+
+ return new StringReader( content );
+ }
+ finally
+ {
+ IOUtil.close( source );
+ }
+ }
+
+ // TODO replace with StringUtils.endsWithIgnoreCase() from maven-shared-utils 0.7
+ static boolean endsWithIgnoreCase( String str, String searchStr )
+ {
+ if ( str.length() < searchStr.length() )
+ {
+ return false;
+ }
+
+ return str.regionMatches( true, str.length() - searchStr.length(), searchStr, 0, searchStr.length() );
+ }
+
+ private static ZipFile getZipFile( File file )
+ throws IOException
+ {
+ if ( file == null )
+ {
+ throw new IOException( "Error opening ZipFile: null" );
+ }
+
+ try
+ {
+ // TODO: plexus-archiver, if it could do the excludes
+ return new ZipFile( file );
+ }
+ catch ( ZipException ex )
+ {
+ IOException ioe = new IOException( "Error opening ZipFile: " + file.getAbsolutePath() );
+ ioe.initCause( ex );
+ throw ioe;
+ }
+ }
+
+ private static void closeZipFile( ZipFile zipFile )
+ {
+ // TODO: move to plexus utils
+ try
+ {
+ zipFile.close();
+ }
+ catch ( IOException e )
+ {
+ // ignore
+ }
+ }
+}
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_190/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_190/npe.json
new file mode 100644
index 000000000..d7a4ae3bd
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_190/npe.json
@@ -0,0 +1,7 @@
+{
+ "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java",
+ "line": 186,
+ "npe_method": "locateDocumentFiles",
+ "deref_field": "moduleExcludes",
+ "npe_class": "DefaultSiteRenderer"
+}
\ No newline at end of file
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_525/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_525/buggy.java
new file mode 100644
index 000000000..acfd6afc5
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_525/buggy.java
@@ -0,0 +1,1177 @@
+package org.apache.maven.doxia.siterenderer;
+
+/*
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+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 java.util.Properties;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.Restriction;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.doxia.Doxia;
+import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
+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.site.decoration.DecorationModel;
+import org.apache.maven.doxia.site.decoration.PublishDate;
+import org.apache.maven.doxia.site.skin.SkinModel;
+import org.apache.maven.doxia.site.skin.io.xpp3.SkinXpp3Reader;
+import org.apache.maven.doxia.parser.module.ParserModule;
+import org.apache.maven.doxia.parser.module.ParserModuleManager;
+import org.apache.maven.doxia.parser.module.ParserModuleNotFoundException;
+import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
+import org.apache.maven.doxia.util.XmlValidator;
+import org.apache.velocity.Template;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.Scope;
+import org.apache.velocity.tools.ToolManager;
+import org.apache.velocity.tools.config.ConfigurationUtils;
+import org.apache.velocity.tools.config.EasyFactoryConfiguration;
+import org.apache.velocity.tools.config.FactoryConfiguration;
+import org.apache.velocity.tools.generic.AlternatorTool;
+import org.apache.velocity.tools.generic.ClassTool;
+import org.apache.velocity.tools.generic.ComparisonDateTool;
+import org.apache.velocity.tools.generic.ContextTool;
+import org.apache.velocity.tools.generic.ConversionTool;
+import org.apache.velocity.tools.generic.DisplayTool;
+import org.apache.velocity.tools.generic.EscapeTool;
+import org.apache.velocity.tools.generic.FieldTool;
+import org.apache.velocity.tools.generic.LinkTool;
+import org.apache.velocity.tools.generic.LoopTool;
+import org.apache.velocity.tools.generic.MathTool;
+import org.apache.velocity.tools.generic.NumberTool;
+import org.apache.velocity.tools.generic.RenderTool;
+import org.apache.velocity.tools.generic.ResourceTool;
+import org.apache.velocity.tools.generic.SortTool;
+import org.apache.velocity.tools.generic.XmlTool;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.i18n.I18N;
+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.Os;
+import org.codehaus.plexus.util.PathTool;
+import org.codehaus.plexus.util.PropertyUtils;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.WriterFactory;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.codehaus.plexus.velocity.VelocityComponent;
+
+/**
+ * DefaultSiteRenderer class.
+ *
+ * @author Emmanuel Venisse
+ * @author Vincent Siveton
+ * @since 1.0
+ */
+@Component( role = Renderer.class )
+public class DefaultSiteRenderer
+ extends AbstractLogEnabled
+ implements Renderer
+{
+ // ----------------------------------------------------------------------
+ // Requirements
+ // ----------------------------------------------------------------------
+
+ @Requirement
+ private VelocityComponent velocity;
+
+ @Requirement
+ private ParserModuleManager parserModuleManager;
+
+ @Requirement
+ private Doxia doxia;
+
+ @Requirement
+ private I18N i18n;
+
+ @Requirement
+ private PlexusContainer plexus;
+
+ private static final String RESOURCE_DIR = "org/apache/maven/doxia/siterenderer/resources";
+
+ private static final String DEFAULT_TEMPLATE = RESOURCE_DIR + "/default-site.vm";
+
+ private static final String SKIN_TEMPLATE_LOCATION = "META-INF/maven/site.vm";
+
+ private static final String TOOLS_LOCATION = "META-INF/maven/site-tools.xml";
+
+ // ----------------------------------------------------------------------
+ // Renderer implementation
+ // ----------------------------------------------------------------------
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext )
+ throws IOException, RendererException
+ {
+ return locateDocumentFiles( siteRenderingContext, false );
+ }
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext,
+ boolean editable )
+ throws IOException, RendererException
+ {
+ Map files = new LinkedHashMap();
+ Map moduleExcludes = siteRenderingContext.getModuleExcludes();
+
+ // look in every site directory (in general src/site or target/generated-site)
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ if ( siteDirectory.exists() )
+ {
+ Collection modules = parserModuleManager.getParserModules();
+ // use every Doxia parser module
+ for ( ParserModule module : modules )
+ {
+ File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() );
+
+ String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
+
+ addModuleFiles( siteRenderingContext.getRootDirectory(), moduleBasedir, module, excludes, files,
+ editable );
+ }
+ }
+ }
+
+ // look in specific modules directories (used for old Maven 1.x site layout: xdoc and fml docs in /xdocs)
+ for ( ExtraDoxiaModuleReference module : siteRenderingContext.getModules() )
+ {
+ try
+ {
+ ParserModule parserModule = parserModuleManager.getParserModule( module.getParserId() );
+
+ String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
+
+ addModuleFiles( siteRenderingContext.getRootDirectory(), module.getBasedir(), parserModule, excludes,
+ files, editable );
+ }
+ catch ( ParserModuleNotFoundException e )
+ {
+ throw new RendererException( "Unable to find module: " + e.getMessage(), e );
+ }
+ }
+ return files;
+ }
+
+ private List filterExtensionIgnoreCase( List fileNames, String extension )
+ {
+ List filtered = new LinkedList( fileNames );
+ for ( Iterator it = filtered.iterator(); it.hasNext(); )
+ {
+ String name = it.next();
+
+ // Take care of extension case
+ if ( !endsWithIgnoreCase( name, extension ) )
+ {
+ it.remove();
+ }
+ }
+ return filtered;
+ }
+
+ private void addModuleFiles( File rootDir, File moduleBasedir, ParserModule module, String excludes,
+ Map files, boolean editable )
+ throws IOException, RendererException
+ {
+ if ( !moduleBasedir.exists() || ArrayUtils.isEmpty( module.getExtensions() ) )
+ {
+ return;
+ }
+
+ String moduleRelativePath =
+ PathTool.getRelativeFilePath( rootDir.getAbsolutePath(), moduleBasedir.getAbsolutePath() );
+
+ List allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", excludes, false );
+
+ for ( String extension : module.getExtensions() )
+ {
+ String fullExtension = "." + extension;
+
+ List docs = filterExtensionIgnoreCase( allFiles, fullExtension );
+
+ // *..vm
+ List velocityFiles = filterExtensionIgnoreCase( allFiles, fullExtension + ".vm" );
+
+ docs.addAll( velocityFiles );
+
+ for ( String doc : docs )
+ {
+ RenderingContext context = new RenderingContext( moduleBasedir, moduleRelativePath, doc,
+ module.getParserId(), extension, editable );
+
+ // TODO: DOXIA-111: we need a general filter here that knows how to alter the context
+ if ( endsWithIgnoreCase( doc, ".vm" ) )
+ {
+ context.setAttribute( "velocity", "true" );
+ }
+
+ String key = context.getOutputName();
+ key = StringUtils.replace( key, "\\", "/" );
+
+ if ( files.containsKey( key ) )
+ {
+ DocumentRenderer renderer = files.get( key );
+
+ RenderingContext originalContext = renderer.getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' clashes with existing '" + originalDoc + "'." );
+ }
+ // -----------------------------------------------------------------------
+ // Handle key without case differences
+ // -----------------------------------------------------------------------
+ for ( Map.Entry entry : files.entrySet() )
+ {
+ if ( entry.getKey().equalsIgnoreCase( key ) )
+ {
+ RenderingContext originalContext = entry.getValue().getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
+ {
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator
+ + doc + "' clashes with existing '" + originalDoc + "'." );
+ }
+
+ if ( getLogger().isWarnEnabled() )
+ {
+ getLogger().warn( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' could clash with existing '" + originalDoc + "'." );
+ }
+ }
+ }
+
+ files.put( key, new DoxiaDocumentRenderer( context ) );
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void render( Collection documents, SiteRenderingContext siteRenderingContext,
+ File outputDirectory )
+ throws RendererException, IOException
+ {
+ for ( DocumentRenderer docRenderer : documents )
+ {
+ RenderingContext renderingContext = docRenderer.getRenderingContext();
+
+ File outputFile = new File( outputDirectory, docRenderer.getOutputName() );
+
+ File inputFile = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
+
+ boolean modified = !outputFile.exists() || ( inputFile.lastModified() > outputFile.lastModified() )
+ || ( siteRenderingContext.getDecoration().getLastModified() > outputFile.lastModified() );
+
+ if ( modified || docRenderer.isOverwrite() )
+ {
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Generating " + outputFile );
+ }
+
+ Writer writer = null;
+ try
+ {
+ if ( !docRenderer.isExternalReport() )
+ {
+ writer = WriterFactory.newWriter( outputFile, siteRenderingContext.getOutputEncoding() );
+ }
+ docRenderer.renderDocument( writer, this, siteRenderingContext );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ else
+ {
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( inputFile + " unchanged, not regenerating..." );
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void renderDocument( Writer writer, RenderingContext docRenderingContext, SiteRenderingContext siteContext )
+ throws RendererException, FileNotFoundException, UnsupportedEncodingException
+ {
+ SiteRendererSink sink = new SiteRendererSink( docRenderingContext );
+
+ File doc = new File( docRenderingContext.getBasedir(), docRenderingContext.getInputName() );
+
+ Reader reader = null;
+ try
+ {
+ String resource = doc.getAbsolutePath();
+
+ Parser parser = doxia.getParser( docRenderingContext.getParserId() );
+ // DOXIASITETOOLS-146 don't render comments from source markup
+ parser.setEmitComments( false );
+
+ // TODO: DOXIA-111: the filter used here must be checked generally.
+ if ( docRenderingContext.getAttribute( "velocity" ) != null )
+ {
+ getLogger().debug( "Processing Velocity for " + docRenderingContext.getDoxiaSourcePath() );
+ try
+ {
+ Context vc = createDocumentVelocityContext( docRenderingContext, siteContext );
+
+ StringWriter sw = new StringWriter();
+
+ velocity.getEngine().mergeTemplate( resource, siteContext.getInputEncoding(), vc, sw );
+
+ String doxiaContent = sw.toString();
+
+ if ( siteContext.getProcessedContentOutput() != null )
+ {
+ // save Velocity processing result, ie the Doxia content that will be parsed after
+ saveVelocityProcessedContent( docRenderingContext, siteContext, doxiaContent );
+ }
+
+ reader = new StringReader( doxiaContent );
+ }
+ catch ( VelocityException e )
+ {
+ throw new RendererException( "Error parsing " + docRenderingContext.getDoxiaSourcePath()
+ + " as a Velocity template: " + e.getMessage(), e );
+ }
+
+ if ( parser.getType() == Parser.XML_TYPE && siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ }
+ else
+ {
+ switch ( parser.getType() )
+ {
+ case Parser.XML_TYPE:
+ reader = ReaderFactory.newXmlReader( doc );
+ if ( siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ break;
+
+ case Parser.TXT_TYPE:
+ case Parser.UNKNOWN_TYPE:
+ default:
+ reader = ReaderFactory.newReader( doc, siteContext.getInputEncoding() );
+ }
+ }
+ sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) );
+
+ doxia.parse( reader, docRenderingContext.getParserId(), sink );
+ }
+ catch ( ParserNotFoundException e )
+ {
+ throw new RendererException( "Error getting a parser for '" + doc + "': " + e.getMessage(), e );
+ }
+ catch ( ParseException e )
+ {
+ StringBuilder errorMsgBuilder = new StringBuilder();
+ errorMsgBuilder.append( "Error parsing '" ).append( doc ).append( "': " );
+ if ( e.getLineNumber() > 0 )
+ {
+ errorMsgBuilder.append( "line [" ).append( e.getLineNumber() ).append( "] " );
+ }
+ errorMsgBuilder.append( e.getMessage() );
+ throw new RendererException( errorMsgBuilder.toString(), e );
+ }
+ catch ( IOException e )
+ {
+ throw new RendererException( "IOException when processing '" + doc + "'", e );
+ }
+ finally
+ {
+ sink.flush();
+
+ sink.close();
+
+ IOUtil.close( reader );
+ }
+
+ mergeDocumentIntoSite( writer, (DocumentContent) sink, siteContext );
+ }
+
+ private void saveVelocityProcessedContent( RenderingContext docRenderingContext, SiteRenderingContext siteContext,
+ String doxiaContent )
+ throws IOException
+ {
+ if ( !siteContext.getProcessedContentOutput().exists() )
+ {
+ siteContext.getProcessedContentOutput().mkdirs();
+ }
+
+ String input = docRenderingContext.getInputName();
+ File outputFile = new File( siteContext.getProcessedContentOutput(),
+ input.substring( 0, input.length() - 3 ) );
+
+ File outputParent = outputFile.getParentFile();
+ if ( !outputParent.exists() )
+ {
+ outputParent.mkdirs();
+ }
+
+ FileUtils.fileWrite( outputFile, siteContext.getInputEncoding(), doxiaContent );
+ }
+
+ /**
+ * Creates a Velocity Context with all generic tools configured wit the site rendering context.
+ *
+ * @param siteRenderingContext the site rendering context
+ * @return a Velocity tools managed context
+ */
+/**
+ * Creates a Velocity Context with all generic tools configured wit the site rendering context.
+ *
+ * @param siteRenderingContext
+ * the site rendering context
+ * @return a Velocity tools managed context
+ */
+protected org.apache.velocity.context.Context createToolManagedVelocityContext(org.apache.maven.doxia.siterenderer.SiteRenderingContext siteRenderingContext) {
+ java.util.Locale locale = siteRenderingContext.getLocale();
+ java.lang.String dateFormat = siteRenderingContext.getDecoration().getPublishDate().getFormat();
+ org.apache.velocity.tools.config.EasyFactoryConfiguration config = new org.apache.velocity.tools.config.EasyFactoryConfiguration(false);
+ config.property("safeMode", java.lang.Boolean.FALSE);
+ config.toolbox(org.apache.velocity.tools.Scope.REQUEST).tool(org.apache.velocity.tools.generic.ContextTool.class).tool(org.apache.velocity.tools.generic.LinkTool.class).tool(org.apache.velocity.tools.generic.LoopTool.class).tool(org.apache.velocity.tools.generic.RenderTool.class);
+ config.toolbox(org.apache.velocity.tools.Scope.APPLICATION).property("locale", locale).tool(org.apache.velocity.tools.generic.AlternatorTool.class).tool(org.apache.velocity.tools.generic.ClassTool.class).tool(org.apache.velocity.tools.generic.ComparisonDateTool.class).property("format", dateFormat).tool(org.apache.velocity.tools.generic.ConversionTool.class).property("dateFormat", dateFormat).tool(org.apache.velocity.tools.generic.DisplayTool.class).tool(org.apache.velocity.tools.generic.EscapeTool.class).tool(org.apache.velocity.tools.generic.FieldTool.class).tool(org.apache.velocity.tools.generic.MathTool.class).tool(org.apache.velocity.tools.generic.NumberTool.class).tool(org.apache.velocity.tools.generic.ResourceTool.class).property("bundles", new java.lang.String[]{ "site-renderer" }).tool(org.apache.velocity.tools.generic.SortTool.class).tool(org.apache.velocity.tools.generic.XmlTool.class);
+ org.apache.velocity.tools.config.FactoryConfiguration customConfig = org.apache.velocity.tools.config.ConfigurationUtils.findInClasspath(org.apache.maven.doxia.siterenderer.DefaultSiteRenderer.TOOLS_LOCATION);
+ {
+ config.addConfiguration(/* NPEX_NULL_EXP */
+ customConfig);
+ }
+ org.apache.velocity.tools.ToolManager manager = new org.apache.velocity.tools.ToolManager(false, false);
+ manager.configure(config);
+ return manager.createContext();
+}
+
+ /**
+ * Create a Velocity Context for a Doxia document, containing every information about rendered document.
+ *
+ * @param sink the site renderer sink for the document
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createDocumentVelocityContext( RenderingContext renderingContext,
+ SiteRenderingContext siteRenderingContext )
+ {
+ Context context = createToolManagedVelocityContext( siteRenderingContext );
+ // ----------------------------------------------------------------------
+ // Data objects
+ // ----------------------------------------------------------------------
+
+ context.put( "relativePath", renderingContext.getRelativePath() );
+
+ String currentFileName = renderingContext.getOutputName().replace( '\\', '/' );
+ context.put( "currentFileName", currentFileName );
+
+ context.put( "alignedFileName", PathTool.calculateLink( currentFileName, renderingContext.getRelativePath() ) );
+
+ context.put( "decoration", siteRenderingContext.getDecoration() );
+
+ Locale locale = siteRenderingContext.getLocale();
+ context.put( "locale", locale );
+ context.put( "supportedLocales", Collections.unmodifiableList( siteRenderingContext.getSiteLocales() ) );
+
+ context.put( "currentDate", new Date() );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateRevision", sdf.format( new Date() ) );
+
+ context.put( "publishDate", siteRenderingContext.getPublishDate() );
+
+ PublishDate publishDate = siteRenderingContext.getDecoration().getPublishDate();
+ DateFormat dateFormat = new SimpleDateFormat( publishDate.getFormat(), locale );
+ context.put( "dateFormat", dateFormat );
+
+ // doxiaSiteRendererVersion
+ InputStream inputStream = this.getClass().getResourceAsStream( "/META-INF/"
+ + "maven/org.apache.maven.doxia/doxia-site-renderer/pom.properties" );
+ Properties properties = PropertyUtils.loadProperties( inputStream );
+ if ( inputStream == null )
+ {
+ getLogger().debug( "pom.properties for doxia-site-renderer could not be found." );
+ }
+ else if ( properties == null )
+ {
+ getLogger().debug( "Failed to load pom.properties, so doxiaVersion is not available"
+ + " in the Velocity context." );
+ }
+ else
+ {
+ context.put( "doxiaSiteRendererVersion", properties.getProperty( "version" ) );
+ }
+
+ // Add user properties
+ Map templateProperties = siteRenderingContext.getTemplateProperties();
+
+ if ( templateProperties != null )
+ {
+ for ( Map.Entry entry : templateProperties.entrySet() )
+ {
+ context.put( entry.getKey(), entry.getValue() );
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Tools
+ // ----------------------------------------------------------------------
+
+ context.put( "PathTool", new PathTool() );
+
+ context.put( "FileUtils", new FileUtils() );
+
+ context.put( "StringUtils", new StringUtils() );
+
+ context.put( "i18n", i18n );
+
+ context.put( "plexus", plexus );
+ return context;
+ }
+
+ /**
+ * Create a Velocity Context for the site template decorating the document. In addition to all the informations
+ * from the document, this context contains data gathered in {@link SiteRendererSink} during document rendering.
+ *
+ * @param content the document content to be merged into the template
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createSiteTemplateVelocityContext( DocumentContent content,
+ SiteRenderingContext siteRenderingContext )
+ {
+ // first get the context from document
+ Context context = createDocumentVelocityContext( content.getRenderingContext(), siteRenderingContext );
+
+ // then add data objects from rendered document
+
+ // Add infos from document
+ context.put( "authors", content.getAuthors() );
+
+ context.put( "shortTitle", content.getTitle() );
+
+ // DOXIASITETOOLS-70: Prepend the project name to the title, if any
+ String title = "";
+ if ( siteRenderingContext.getDecoration() != null
+ && siteRenderingContext.getDecoration().getName() != null )
+ {
+ title = siteRenderingContext.getDecoration().getName();
+ }
+ else if ( siteRenderingContext.getDefaultWindowTitle() != null )
+ {
+ title = siteRenderingContext.getDefaultWindowTitle();
+ }
+
+ if ( title.length() > 0 )
+ {
+ title += " – "; // Symbol Name: En Dash, Html Entity: –
+ }
+ title += content.getTitle();
+
+ context.put( "title", title );
+
+ context.put( "headContent", content.getHead() );
+
+ context.put( "bodyContent", content.getBody() );
+
+ // document date (got from Doxia Sink date() API)
+ String documentDate = content.getDate();
+ if ( StringUtils.isNotEmpty( documentDate ) )
+ {
+ context.put( "documentDate", documentDate );
+
+ // deprecated variables that rework the document date, suppose one semantics over others
+ // (ie creation date, while it may be last modification date if the document writer decided so)
+ // see DOXIASITETOOLS-20 for the beginning and DOXIASITETOOLS-164 for the end of this story
+ try
+ {
+ // we support only ISO 8601 date
+ Date creationDate = new SimpleDateFormat( "yyyy-MM-dd" ).parse( documentDate );
+
+ context.put( "creationDate", creationDate );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateCreation", sdf.format( creationDate ) );
+ }
+ catch ( java.text.ParseException e )
+ {
+ getLogger().warn( "Could not parse date '" + documentDate + "' from "
+ + content.getRenderingContext().getInputName()
+ + " (expected yyyy-MM-dd format), ignoring!" );
+ }
+ }
+
+ // document rendering context, to get eventual inputName
+ context.put( "docRenderingContext", content.getRenderingContext() );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
+ throws RendererException
+ {
+ mergeDocumentIntoSite( writer, sink, siteRenderingContext );
+ }
+
+ /** {@inheritDoc} */
+ public void mergeDocumentIntoSite( Writer writer, DocumentContent content,
+ SiteRenderingContext siteRenderingContext )
+ throws RendererException
+ {
+ String templateName = siteRenderingContext.getTemplateName();
+
+ getLogger().debug( "Processing Velocity for template " + templateName + " on "
+ + content.getRenderingContext().getInputName() );
+
+ Context context = createSiteTemplateVelocityContext( content, siteRenderingContext );
+
+ ClassLoader old = null;
+
+ if ( siteRenderingContext.getTemplateClassLoader() != null )
+ {
+ // -------------------------------------------------------------------------
+ // If no template classloader was set we'll just use the context classloader
+ // -------------------------------------------------------------------------
+
+ old = Thread.currentThread().getContextClassLoader();
+
+ Thread.currentThread().setContextClassLoader( siteRenderingContext.getTemplateClassLoader() );
+ }
+
+ try
+ {
+ Template template;
+ Artifact skin = siteRenderingContext.getSkin();
+
+ try
+ {
+ SkinModel skinModel = siteRenderingContext.getSkinModel();
+ String encoding = ( skinModel == null ) ? null : skinModel.getEncoding();
+
+ template = ( encoding == null ) ? velocity.getEngine().getTemplate( templateName )
+ : velocity.getEngine().getTemplate( templateName, encoding );
+ }
+ catch ( ParseErrorException pee )
+ {
+ throw new RendererException( "Velocity parsing error while reading the site decoration template "
+ + ( ( skin == null ) ? ( "'" + templateName + "'" ) : ( "from " + skin.getId() + " skin" ) ),
+ pee );
+ }
+ catch ( ResourceNotFoundException rnfe )
+ {
+ throw new RendererException( "Could not find the site decoration template "
+ + ( ( skin == null ) ? ( "'" + templateName + "'" ) : ( "from " + skin.getId() + " skin" ) ),
+ rnfe );
+ }
+
+ try
+ {
+ StringWriter sw = new StringWriter();
+ template.merge( context, sw );
+ writer.write( sw.toString().replaceAll( "\r?\n", SystemUtils.LINE_SEPARATOR ) );
+ }
+ catch ( VelocityException ve )
+ {
+ throw new RendererException( "Velocity error while merging site decoration template.", ve );
+ }
+ catch ( IOException ioe )
+ {
+ throw new RendererException( "IO exception while merging site decoration template.", ioe );
+ }
+ }
+ finally
+ {
+ IOUtil.close( writer );
+
+ if ( old != null )
+ {
+ Thread.currentThread().setContextClassLoader( old );
+ }
+ }
+ }
+
+ private SiteRenderingContext createSiteRenderingContext( Map attributes, DecorationModel decoration,
+ String defaultWindowTitle, Locale locale )
+ {
+ SiteRenderingContext context = new SiteRenderingContext();
+
+ context.setTemplateProperties( attributes );
+ context.setLocale( locale );
+ context.setDecoration( decoration );
+ context.setDefaultWindowTitle( defaultWindowTitle );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public SiteRenderingContext createContextForSkin( Artifact skin, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws IOException, RendererException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setSkin( skin );
+
+ ZipFile zipFile = getZipFile( skin.getFile() );
+ InputStream in = null;
+
+ try
+ {
+ if ( zipFile.getEntry( SKIN_TEMPLATE_LOCATION ) != null )
+ {
+ context.setTemplateName( SKIN_TEMPLATE_LOCATION );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{skin.getFile().toURI().toURL()} ) );
+ }
+ else
+ {
+ context.setTemplateName( DEFAULT_TEMPLATE );
+ context.setTemplateClassLoader( getClass().getClassLoader() );
+ context.setUsingDefaultTemplate( true );
+ }
+
+ ZipEntry skinDescriptorEntry = zipFile.getEntry( SkinModel.SKIN_DESCRIPTOR_LOCATION );
+ if ( skinDescriptorEntry != null )
+ {
+ in = zipFile.getInputStream( skinDescriptorEntry );
+
+ SkinModel skinModel = new SkinXpp3Reader().read( in );
+ context.setSkinModel( skinModel );
+
+ String toolsPrerequisite =
+ skinModel.getPrerequisites() == null ? null : skinModel.getPrerequisites().getDoxiaSitetools();
+
+ Package p = DefaultSiteRenderer.class.getPackage();
+ String current = ( p == null ) ? null : p.getImplementationVersion();
+
+ if ( StringUtils.isNotBlank( toolsPrerequisite ) && ( current != null )
+ && !matchVersion( current, toolsPrerequisite ) )
+ {
+ throw new RendererException( "Cannot use skin: has " + toolsPrerequisite
+ + " Doxia Sitetools prerequisite, but current is " + current );
+ }
+ }
+ }
+ catch ( XmlPullParserException e )
+ {
+ throw new RendererException( "Failed to parse " + SkinModel.SKIN_DESCRIPTOR_LOCATION
+ + " skin descriptor from " + skin.getId() + " skin", e );
+ }
+ finally
+ {
+ IOUtil.close( in );
+ closeZipFile( zipFile );
+ }
+
+ return context;
+ }
+
+ boolean matchVersion( String current, String prerequisite )
+ throws RendererException
+ {
+ try
+ {
+ ArtifactVersion v = new DefaultArtifactVersion( current );
+ VersionRange vr = VersionRange.createFromVersionSpec( prerequisite );
+
+ boolean matched = false;
+ ArtifactVersion recommendedVersion = vr.getRecommendedVersion();
+ if ( recommendedVersion == null )
+ {
+ List restrictions = vr.getRestrictions();
+ for ( Restriction restriction : restrictions )
+ {
+ if ( restriction.containsVersion( v ) )
+ {
+ matched = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // only singular versions ever have a recommendedVersion
+ @SuppressWarnings( "unchecked" )
+ int compareTo = recommendedVersion.compareTo( v );
+ matched = ( compareTo <= 0 );
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Skin doxia-sitetools prerequisite: " + prerequisite + ", current: " + current
+ + ", matched = " + matched );
+ }
+
+ return matched;
+ }
+ catch ( InvalidVersionSpecificationException e )
+ {
+ throw new RendererException( "Invalid skin doxia-sitetools prerequisite: " + prerequisite, e );
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Deprecated
+ public SiteRenderingContext createContextForTemplate( File templateFile, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws MalformedURLException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setTemplateName( templateFile.getName() );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{templateFile.getParentFile().toURI().toURL()} ) );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File resourcesDirectory,
+ File outputDirectory )
+ throws IOException
+ {
+ throw new AssertionError( "copyResources( SiteRenderingContext, File, File ) is deprecated." );
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File outputDirectory )
+ throws IOException
+ {
+ if ( siteRenderingContext.getSkin() != null )
+ {
+ ZipFile file = getZipFile( siteRenderingContext.getSkin().getFile() );
+
+ try
+ {
+ for ( Enumeration extends ZipEntry> e = file.entries(); e.hasMoreElements(); )
+ {
+ ZipEntry entry = e.nextElement();
+
+ if ( !entry.getName().startsWith( "META-INF/" ) )
+ {
+ File destFile = new File( outputDirectory, entry.getName() );
+ if ( !entry.isDirectory() )
+ {
+ if ( destFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ destFile.getParentFile().mkdirs();
+
+ copyFileFromZip( file, entry, destFile );
+ }
+ else
+ {
+ destFile.mkdirs();
+ }
+ }
+ }
+ }
+ finally
+ {
+ closeZipFile( file );
+ }
+ }
+
+ if ( siteRenderingContext.isUsingDefaultTemplate() )
+ {
+ InputStream resourceList = getClass().getClassLoader()
+ .getResourceAsStream( RESOURCE_DIR + "/resources.txt" );
+
+ if ( resourceList != null )
+ {
+ Reader r = null;
+ LineNumberReader reader = null;
+ try
+ {
+ r = ReaderFactory.newReader( resourceList, ReaderFactory.UTF_8 );
+ reader = new LineNumberReader( r );
+
+ String line;
+
+ while ( ( line = reader.readLine() ) != null )
+ {
+ if ( line.startsWith( "#" ) || line.trim().length() == 0 )
+ {
+ continue;
+ }
+
+ InputStream is = getClass().getClassLoader().getResourceAsStream( RESOURCE_DIR + "/" + line );
+
+ if ( is == null )
+ {
+ throw new IOException( "The resource " + line + " doesn't exist." );
+ }
+
+ File outputFile = new File( outputDirectory, line );
+
+ if ( outputFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ OutputStream os = null;
+ try
+ {
+ // for the images
+ os = new FileOutputStream( outputFile );
+ IOUtil.copy( is, os );
+ }
+ finally
+ {
+ IOUtil.close( os );
+ }
+
+ IOUtil.close( is );
+ }
+ }
+ finally
+ {
+ IOUtil.close( reader );
+ IOUtil.close( r );
+ }
+ }
+ }
+
+ // Copy extra site resources
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ File resourcesDirectory = new File( siteDirectory, "resources" );
+
+ if ( resourcesDirectory != null && resourcesDirectory.exists() )
+ {
+ copyDirectory( resourcesDirectory, outputDirectory );
+ }
+ }
+
+ // Check for the existence of /css/site.css
+ File siteCssFile = new File( outputDirectory, "/css/site.css" );
+ if ( !siteCssFile.exists() )
+ {
+ // Create the subdirectory css if it doesn't exist, DOXIA-151
+ File cssDirectory = new File( outputDirectory, "/css/" );
+ boolean created = cssDirectory.mkdirs();
+ if ( created && getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created." );
+ }
+
+ // If the file is not there - create an empty file, DOXIA-86
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The file '" + siteCssFile.getAbsolutePath() + "' does not exist. Creating an empty file." );
+ }
+ Writer writer = null;
+ try
+ {
+ writer = WriterFactory.newWriter( siteCssFile, siteRenderingContext.getOutputEncoding() );
+ //DOXIA-290...the file should not be 0 bytes.
+ writer.write( "/* You can override this file with your own styles */" );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ }
+
+ private static void copyFileFromZip( ZipFile file, ZipEntry entry, File destFile )
+ throws IOException
+ {
+ FileOutputStream fos = new FileOutputStream( destFile );
+
+ try
+ {
+ IOUtil.copy( file.getInputStream( entry ), fos );
+ }
+ finally
+ {
+ IOUtil.close( fos );
+ }
+ }
+
+ /**
+ * Copy the directory
+ *
+ * @param source source file to be copied
+ * @param destination destination file
+ * @throws java.io.IOException if any
+ */
+ protected void copyDirectory( File source, File destination )
+ throws IOException
+ {
+ if ( source.exists() )
+ {
+ DirectoryScanner scanner = new DirectoryScanner();
+
+ String[] includedResources = {"**/**"};
+
+ scanner.setIncludes( includedResources );
+
+ scanner.addDefaultExcludes();
+
+ scanner.setBasedir( source );
+
+ scanner.scan();
+
+ List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
+
+ for ( String name : includedFiles )
+ {
+ File sourceFile = new File( source, name );
+
+ File destinationFile = new File( destination, name );
+
+ FileUtils.copyFile( sourceFile, destinationFile );
+ }
+ }
+ }
+
+ private Reader validate( Reader source, String resource )
+ throws ParseException, IOException
+ {
+ getLogger().debug( "Validating: " + resource );
+
+ try
+ {
+ String content = IOUtil.toString( new BufferedReader( source ) );
+
+ new XmlValidator( new PlexusLoggerWrapper( getLogger() ) ).validate( content );
+
+ return new StringReader( content );
+ }
+ finally
+ {
+ IOUtil.close( source );
+ }
+ }
+
+ // TODO replace with StringUtils.endsWithIgnoreCase() from maven-shared-utils 0.7
+ static boolean endsWithIgnoreCase( String str, String searchStr )
+ {
+ if ( str.length() < searchStr.length() )
+ {
+ return false;
+ }
+
+ return str.regionMatches( true, str.length() - searchStr.length(), searchStr, 0, searchStr.length() );
+ }
+
+ private static ZipFile getZipFile( File file )
+ throws IOException
+ {
+ if ( file == null )
+ {
+ throw new IOException( "Error opening ZipFile: null" );
+ }
+
+ try
+ {
+ // TODO: plexus-archiver, if it could do the excludes
+ return new ZipFile( file );
+ }
+ catch ( ZipException ex )
+ {
+ IOException ioe = new IOException( "Error opening ZipFile: " + file.getAbsolutePath() );
+ ioe.initCause( ex );
+ throw ioe;
+ }
+ }
+
+ private static void closeZipFile( ZipFile zipFile )
+ {
+ // TODO: move to plexus utils
+ try
+ {
+ zipFile.close();
+ }
+ catch ( IOException e )
+ {
+ // ignore
+ }
+ }
+}
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_525/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_525/npe.json
new file mode 100644
index 000000000..58e29ad93
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_525/npe.json
@@ -0,0 +1,7 @@
+{
+ "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java",
+ "line": 514,
+ "npe_method": "createToolManagedVelocityContext",
+ "deref_field": "customConfig",
+ "npe_class": "DefaultSiteRenderer"
+}
\ No newline at end of file
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_647/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_647/buggy.java
new file mode 100644
index 000000000..5f9eeeebf
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_647/buggy.java
@@ -0,0 +1,1180 @@
+package org.apache.maven.doxia.siterenderer;
+
+/*
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+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 java.util.Properties;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.Restriction;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.doxia.Doxia;
+import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
+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.site.decoration.DecorationModel;
+import org.apache.maven.doxia.site.decoration.PublishDate;
+import org.apache.maven.doxia.site.skin.SkinModel;
+import org.apache.maven.doxia.site.skin.io.xpp3.SkinXpp3Reader;
+import org.apache.maven.doxia.parser.module.ParserModule;
+import org.apache.maven.doxia.parser.module.ParserModuleManager;
+import org.apache.maven.doxia.parser.module.ParserModuleNotFoundException;
+import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
+import org.apache.maven.doxia.util.XmlValidator;
+import org.apache.velocity.Template;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.Scope;
+import org.apache.velocity.tools.ToolManager;
+import org.apache.velocity.tools.config.ConfigurationUtils;
+import org.apache.velocity.tools.config.EasyFactoryConfiguration;
+import org.apache.velocity.tools.config.FactoryConfiguration;
+import org.apache.velocity.tools.generic.AlternatorTool;
+import org.apache.velocity.tools.generic.ClassTool;
+import org.apache.velocity.tools.generic.ComparisonDateTool;
+import org.apache.velocity.tools.generic.ContextTool;
+import org.apache.velocity.tools.generic.ConversionTool;
+import org.apache.velocity.tools.generic.DisplayTool;
+import org.apache.velocity.tools.generic.EscapeTool;
+import org.apache.velocity.tools.generic.FieldTool;
+import org.apache.velocity.tools.generic.LinkTool;
+import org.apache.velocity.tools.generic.LoopTool;
+import org.apache.velocity.tools.generic.MathTool;
+import org.apache.velocity.tools.generic.NumberTool;
+import org.apache.velocity.tools.generic.RenderTool;
+import org.apache.velocity.tools.generic.ResourceTool;
+import org.apache.velocity.tools.generic.SortTool;
+import org.apache.velocity.tools.generic.XmlTool;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.i18n.I18N;
+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.Os;
+import org.codehaus.plexus.util.PathTool;
+import org.codehaus.plexus.util.PropertyUtils;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.WriterFactory;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.codehaus.plexus.velocity.VelocityComponent;
+
+/**
+ * DefaultSiteRenderer class.
+ *
+ * @author Emmanuel Venisse
+ * @author Vincent Siveton
+ * @since 1.0
+ */
+@Component( role = Renderer.class )
+public class DefaultSiteRenderer
+ extends AbstractLogEnabled
+ implements Renderer
+{
+ // ----------------------------------------------------------------------
+ // Requirements
+ // ----------------------------------------------------------------------
+
+ @Requirement
+ private VelocityComponent velocity;
+
+ @Requirement
+ private ParserModuleManager parserModuleManager;
+
+ @Requirement
+ private Doxia doxia;
+
+ @Requirement
+ private I18N i18n;
+
+ @Requirement
+ private PlexusContainer plexus;
+
+ private static final String RESOURCE_DIR = "org/apache/maven/doxia/siterenderer/resources";
+
+ private static final String DEFAULT_TEMPLATE = RESOURCE_DIR + "/default-site.vm";
+
+ private static final String SKIN_TEMPLATE_LOCATION = "META-INF/maven/site.vm";
+
+ private static final String TOOLS_LOCATION = "META-INF/maven/site-tools.xml";
+
+ // ----------------------------------------------------------------------
+ // Renderer implementation
+ // ----------------------------------------------------------------------
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext )
+ throws IOException, RendererException
+ {
+ return locateDocumentFiles( siteRenderingContext, false );
+ }
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext,
+ boolean editable )
+ throws IOException, RendererException
+ {
+ Map files = new LinkedHashMap();
+ Map moduleExcludes = siteRenderingContext.getModuleExcludes();
+
+ // look in every site directory (in general src/site or target/generated-site)
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ if ( siteDirectory.exists() )
+ {
+ Collection modules = parserModuleManager.getParserModules();
+ // use every Doxia parser module
+ for ( ParserModule module : modules )
+ {
+ File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() );
+
+ String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
+
+ addModuleFiles( siteRenderingContext.getRootDirectory(), moduleBasedir, module, excludes, files,
+ editable );
+ }
+ }
+ }
+
+ // look in specific modules directories (used for old Maven 1.x site layout: xdoc and fml docs in /xdocs)
+ for ( ExtraDoxiaModuleReference module : siteRenderingContext.getModules() )
+ {
+ try
+ {
+ ParserModule parserModule = parserModuleManager.getParserModule( module.getParserId() );
+
+ String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
+
+ addModuleFiles( siteRenderingContext.getRootDirectory(), module.getBasedir(), parserModule, excludes,
+ files, editable );
+ }
+ catch ( ParserModuleNotFoundException e )
+ {
+ throw new RendererException( "Unable to find module: " + e.getMessage(), e );
+ }
+ }
+ return files;
+ }
+
+ private List filterExtensionIgnoreCase( List fileNames, String extension )
+ {
+ List filtered = new LinkedList( fileNames );
+ for ( Iterator it = filtered.iterator(); it.hasNext(); )
+ {
+ String name = it.next();
+
+ // Take care of extension case
+ if ( !endsWithIgnoreCase( name, extension ) )
+ {
+ it.remove();
+ }
+ }
+ return filtered;
+ }
+
+ private void addModuleFiles( File rootDir, File moduleBasedir, ParserModule module, String excludes,
+ Map files, boolean editable )
+ throws IOException, RendererException
+ {
+ if ( !moduleBasedir.exists() || ArrayUtils.isEmpty( module.getExtensions() ) )
+ {
+ return;
+ }
+
+ String moduleRelativePath =
+ PathTool.getRelativeFilePath( rootDir.getAbsolutePath(), moduleBasedir.getAbsolutePath() );
+
+ List allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", excludes, false );
+
+ for ( String extension : module.getExtensions() )
+ {
+ String fullExtension = "." + extension;
+
+ List docs = filterExtensionIgnoreCase( allFiles, fullExtension );
+
+ // *..vm
+ List velocityFiles = filterExtensionIgnoreCase( allFiles, fullExtension + ".vm" );
+
+ docs.addAll( velocityFiles );
+
+ for ( String doc : docs )
+ {
+ RenderingContext context = new RenderingContext( moduleBasedir, moduleRelativePath, doc,
+ module.getParserId(), extension, editable );
+
+ // TODO: DOXIA-111: we need a general filter here that knows how to alter the context
+ if ( endsWithIgnoreCase( doc, ".vm" ) )
+ {
+ context.setAttribute( "velocity", "true" );
+ }
+
+ String key = context.getOutputName();
+ key = StringUtils.replace( key, "\\", "/" );
+
+ if ( files.containsKey( key ) )
+ {
+ DocumentRenderer renderer = files.get( key );
+
+ RenderingContext originalContext = renderer.getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' clashes with existing '" + originalDoc + "'." );
+ }
+ // -----------------------------------------------------------------------
+ // Handle key without case differences
+ // -----------------------------------------------------------------------
+ for ( Map.Entry entry : files.entrySet() )
+ {
+ if ( entry.getKey().equalsIgnoreCase( key ) )
+ {
+ RenderingContext originalContext = entry.getValue().getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
+ {
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator
+ + doc + "' clashes with existing '" + originalDoc + "'." );
+ }
+
+ if ( getLogger().isWarnEnabled() )
+ {
+ getLogger().warn( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' could clash with existing '" + originalDoc + "'." );
+ }
+ }
+ }
+
+ files.put( key, new DoxiaDocumentRenderer( context ) );
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void render( Collection documents, SiteRenderingContext siteRenderingContext,
+ File outputDirectory )
+ throws RendererException, IOException
+ {
+ for ( DocumentRenderer docRenderer : documents )
+ {
+ RenderingContext renderingContext = docRenderer.getRenderingContext();
+
+ File outputFile = new File( outputDirectory, docRenderer.getOutputName() );
+
+ File inputFile = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
+
+ boolean modified = !outputFile.exists() || ( inputFile.lastModified() > outputFile.lastModified() )
+ || ( siteRenderingContext.getDecoration().getLastModified() > outputFile.lastModified() );
+
+ if ( modified || docRenderer.isOverwrite() )
+ {
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Generating " + outputFile );
+ }
+
+ Writer writer = null;
+ try
+ {
+ if ( !docRenderer.isExternalReport() )
+ {
+ writer = WriterFactory.newWriter( outputFile, siteRenderingContext.getOutputEncoding() );
+ }
+ docRenderer.renderDocument( writer, this, siteRenderingContext );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ else
+ {
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( inputFile + " unchanged, not regenerating..." );
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void renderDocument( Writer writer, RenderingContext docRenderingContext, SiteRenderingContext siteContext )
+ throws RendererException, FileNotFoundException, UnsupportedEncodingException
+ {
+ SiteRendererSink sink = new SiteRendererSink( docRenderingContext );
+
+ File doc = new File( docRenderingContext.getBasedir(), docRenderingContext.getInputName() );
+
+ Reader reader = null;
+ try
+ {
+ String resource = doc.getAbsolutePath();
+
+ Parser parser = doxia.getParser( docRenderingContext.getParserId() );
+ // DOXIASITETOOLS-146 don't render comments from source markup
+ parser.setEmitComments( false );
+
+ // TODO: DOXIA-111: the filter used here must be checked generally.
+ if ( docRenderingContext.getAttribute( "velocity" ) != null )
+ {
+ getLogger().debug( "Processing Velocity for " + docRenderingContext.getDoxiaSourcePath() );
+ try
+ {
+ Context vc = createDocumentVelocityContext( docRenderingContext, siteContext );
+
+ StringWriter sw = new StringWriter();
+
+ velocity.getEngine().mergeTemplate( resource, siteContext.getInputEncoding(), vc, sw );
+
+ String doxiaContent = sw.toString();
+
+ if ( siteContext.getProcessedContentOutput() != null )
+ {
+ // save Velocity processing result, ie the Doxia content that will be parsed after
+ saveVelocityProcessedContent( docRenderingContext, siteContext, doxiaContent );
+ }
+
+ reader = new StringReader( doxiaContent );
+ }
+ catch ( VelocityException e )
+ {
+ throw new RendererException( "Error parsing " + docRenderingContext.getDoxiaSourcePath()
+ + " as a Velocity template: " + e.getMessage(), e );
+ }
+
+ if ( parser.getType() == Parser.XML_TYPE && siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ }
+ else
+ {
+ switch ( parser.getType() )
+ {
+ case Parser.XML_TYPE:
+ reader = ReaderFactory.newXmlReader( doc );
+ if ( siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ break;
+
+ case Parser.TXT_TYPE:
+ case Parser.UNKNOWN_TYPE:
+ default:
+ reader = ReaderFactory.newReader( doc, siteContext.getInputEncoding() );
+ }
+ }
+ sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) );
+
+ doxia.parse( reader, docRenderingContext.getParserId(), sink );
+ }
+ catch ( ParserNotFoundException e )
+ {
+ throw new RendererException( "Error getting a parser for '" + doc + "': " + e.getMessage(), e );
+ }
+ catch ( ParseException e )
+ {
+ StringBuilder errorMsgBuilder = new StringBuilder();
+ errorMsgBuilder.append( "Error parsing '" ).append( doc ).append( "': " );
+ if ( e.getLineNumber() > 0 )
+ {
+ errorMsgBuilder.append( "line [" ).append( e.getLineNumber() ).append( "] " );
+ }
+ errorMsgBuilder.append( e.getMessage() );
+ throw new RendererException( errorMsgBuilder.toString(), e );
+ }
+ catch ( IOException e )
+ {
+ throw new RendererException( "IOException when processing '" + doc + "'", e );
+ }
+ finally
+ {
+ sink.flush();
+
+ sink.close();
+
+ IOUtil.close( reader );
+ }
+
+ mergeDocumentIntoSite( writer, (DocumentContent) sink, siteContext );
+ }
+
+ private void saveVelocityProcessedContent( RenderingContext docRenderingContext, SiteRenderingContext siteContext,
+ String doxiaContent )
+ throws IOException
+ {
+ if ( !siteContext.getProcessedContentOutput().exists() )
+ {
+ siteContext.getProcessedContentOutput().mkdirs();
+ }
+
+ String input = docRenderingContext.getInputName();
+ File outputFile = new File( siteContext.getProcessedContentOutput(),
+ input.substring( 0, input.length() - 3 ) );
+
+ File outputParent = outputFile.getParentFile();
+ if ( !outputParent.exists() )
+ {
+ outputParent.mkdirs();
+ }
+
+ FileUtils.fileWrite( outputFile, siteContext.getInputEncoding(), doxiaContent );
+ }
+
+ /**
+ * Creates a Velocity Context with all generic tools configured wit the site rendering context.
+ *
+ * @param siteRenderingContext the site rendering context
+ * @return a Velocity tools managed context
+ */
+ protected Context createToolManagedVelocityContext( SiteRenderingContext siteRenderingContext )
+ {
+ Locale locale = siteRenderingContext.getLocale();
+ String dateFormat = siteRenderingContext.getDecoration().getPublishDate().getFormat();
+
+ EasyFactoryConfiguration config = new EasyFactoryConfiguration( false );
+ config.property( "safeMode", Boolean.FALSE );
+ config.toolbox( Scope.REQUEST )
+ .tool( ContextTool.class )
+ .tool( LinkTool.class )
+ .tool( LoopTool.class )
+ .tool( RenderTool.class );
+ config.toolbox( Scope.APPLICATION ).property( "locale", locale )
+ .tool( AlternatorTool.class )
+ .tool( ClassTool.class )
+ .tool( ComparisonDateTool.class ).property( "format", dateFormat )
+ .tool( ConversionTool.class ).property( "dateFormat", dateFormat )
+ .tool( DisplayTool.class )
+ .tool( EscapeTool.class )
+ .tool( FieldTool.class )
+ .tool( MathTool.class )
+ .tool( NumberTool.class )
+ .tool( ResourceTool.class ).property( "bundles", new String[] { "site-renderer" } )
+ .tool( SortTool.class )
+ .tool( XmlTool.class );
+
+ FactoryConfiguration customConfig = ConfigurationUtils.findInClasspath( TOOLS_LOCATION );
+
+ if ( customConfig != null )
+ {
+ config.addConfiguration( customConfig );
+ }
+
+ ToolManager manager = new ToolManager( false, false );
+ manager.configure( config );
+
+ return manager.createContext();
+ }
+
+ /**
+ * Create a Velocity Context for a Doxia document, containing every information about rendered document.
+ *
+ * @param sink the site renderer sink for the document
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createDocumentVelocityContext( RenderingContext renderingContext,
+ SiteRenderingContext siteRenderingContext )
+ {
+ Context context = createToolManagedVelocityContext( siteRenderingContext );
+ // ----------------------------------------------------------------------
+ // Data objects
+ // ----------------------------------------------------------------------
+
+ context.put( "relativePath", renderingContext.getRelativePath() );
+
+ String currentFileName = renderingContext.getOutputName().replace( '\\', '/' );
+ context.put( "currentFileName", currentFileName );
+
+ context.put( "alignedFileName", PathTool.calculateLink( currentFileName, renderingContext.getRelativePath() ) );
+
+ context.put( "decoration", siteRenderingContext.getDecoration() );
+
+ Locale locale = siteRenderingContext.getLocale();
+ context.put( "locale", locale );
+ context.put( "supportedLocales", Collections.unmodifiableList( siteRenderingContext.getSiteLocales() ) );
+
+ context.put( "currentDate", new Date() );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateRevision", sdf.format( new Date() ) );
+
+ context.put( "publishDate", siteRenderingContext.getPublishDate() );
+
+ PublishDate publishDate = siteRenderingContext.getDecoration().getPublishDate();
+ DateFormat dateFormat = new SimpleDateFormat( publishDate.getFormat(), locale );
+ context.put( "dateFormat", dateFormat );
+
+ // doxiaSiteRendererVersion
+ InputStream inputStream = this.getClass().getResourceAsStream( "/META-INF/"
+ + "maven/org.apache.maven.doxia/doxia-site-renderer/pom.properties" );
+ Properties properties = PropertyUtils.loadProperties( inputStream );
+ if ( inputStream == null )
+ {
+ getLogger().debug( "pom.properties for doxia-site-renderer could not be found." );
+ }
+ else if ( properties == null )
+ {
+ getLogger().debug( "Failed to load pom.properties, so doxiaVersion is not available"
+ + " in the Velocity context." );
+ }
+ else
+ {
+ context.put( "doxiaSiteRendererVersion", properties.getProperty( "version" ) );
+ }
+
+ // Add user properties
+ Map templateProperties = siteRenderingContext.getTemplateProperties();
+
+ if ( templateProperties != null )
+ {
+ for ( Map.Entry entry : templateProperties.entrySet() )
+ {
+ context.put( entry.getKey(), entry.getValue() );
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Tools
+ // ----------------------------------------------------------------------
+
+ context.put( "PathTool", new PathTool() );
+
+ context.put( "FileUtils", new FileUtils() );
+
+ context.put( "StringUtils", new StringUtils() );
+
+ context.put( "i18n", i18n );
+
+ context.put( "plexus", plexus );
+ return context;
+ }
+
+ /**
+ * Create a Velocity Context for the site template decorating the document. In addition to all the informations
+ * from the document, this context contains data gathered in {@link SiteRendererSink} during document rendering.
+ *
+ * @param content the document content to be merged into the template
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+/**
+ * Create a Velocity Context for the site template decorating the document. In addition to all the informations
+ * from the document, this context contains data gathered in {@link SiteRendererSink} during document rendering.
+ *
+ * @param content
+ * the document content to be merged into the template
+ * @param siteRenderingContext
+ * the site rendering context
+ * @return
+ */
+protected org.apache.velocity.context.Context createSiteTemplateVelocityContext(org.apache.maven.doxia.siterenderer.DocumentContent content, org.apache.maven.doxia.siterenderer.SiteRenderingContext siteRenderingContext) {
+ // first get the context from document
+ org.apache.velocity.context.Context context = createDocumentVelocityContext(content.getRenderingContext(), siteRenderingContext);
+ // then add data objects from rendered document
+ // Add infos from document
+ context.put("authors", content.getAuthors());
+ context.put("shortTitle", content.getTitle());
+ // DOXIASITETOOLS-70: Prepend the project name to the title, if any
+ java.lang.String title = "";
+ if ((siteRenderingContext.getDecoration() != null) && (siteRenderingContext.getDecoration().getName() != null)) {
+ title = siteRenderingContext.getDecoration().getName();
+ } else {
+ {
+ title = /* NPEX_NULL_EXP */
+ siteRenderingContext.getDefaultWindowTitle();
+ }
+ }
+ if (title.length() > 0) {
+ title += " – ";// Symbol Name: En Dash, Html Entity: –
+
+ }
+ title += content.getTitle();
+ context.put("title", title);
+ context.put("headContent", content.getHead());
+ context.put("bodyContent", content.getBody());
+ // document date (got from Doxia Sink date() API)
+ java.lang.String documentDate = content.getDate();
+ if (org.codehaus.plexus.util.StringUtils.isNotEmpty(documentDate)) {
+ context.put("documentDate", documentDate);
+ // deprecated variables that rework the document date, suppose one semantics over others
+ // (ie creation date, while it may be last modification date if the document writer decided so)
+ // see DOXIASITETOOLS-20 for the beginning and DOXIASITETOOLS-164 for the end of this story
+ try {
+ // we support only ISO 8601 date
+ java.util.Date creationDate = new java.text.SimpleDateFormat("yyyy-MM-dd").parse(documentDate);
+ context.put("creationDate", creationDate);
+ java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyyMMdd");
+ context.put("dateCreation", sdf.format(creationDate));
+ } catch (java.text.ParseException e) {
+ getLogger().warn(((("Could not parse date '" + documentDate) + "' from ") + content.getRenderingContext().getInputName()) + " (expected yyyy-MM-dd format), ignoring!");
+ }
+ }
+ // document rendering context, to get eventual inputName
+ context.put("docRenderingContext", content.getRenderingContext());
+ return context;
+}
+
+ /** {@inheritDoc} */
+ public void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
+ throws RendererException
+ {
+ mergeDocumentIntoSite( writer, sink, siteRenderingContext );
+ }
+
+ /** {@inheritDoc} */
+ public void mergeDocumentIntoSite( Writer writer, DocumentContent content,
+ SiteRenderingContext siteRenderingContext )
+ throws RendererException
+ {
+ String templateName = siteRenderingContext.getTemplateName();
+
+ getLogger().debug( "Processing Velocity for template " + templateName + " on "
+ + content.getRenderingContext().getInputName() );
+
+ Context context = createSiteTemplateVelocityContext( content, siteRenderingContext );
+
+ ClassLoader old = null;
+
+ if ( siteRenderingContext.getTemplateClassLoader() != null )
+ {
+ // -------------------------------------------------------------------------
+ // If no template classloader was set we'll just use the context classloader
+ // -------------------------------------------------------------------------
+
+ old = Thread.currentThread().getContextClassLoader();
+
+ Thread.currentThread().setContextClassLoader( siteRenderingContext.getTemplateClassLoader() );
+ }
+
+ try
+ {
+ Template template;
+ Artifact skin = siteRenderingContext.getSkin();
+
+ try
+ {
+ SkinModel skinModel = siteRenderingContext.getSkinModel();
+ String encoding = ( skinModel == null ) ? null : skinModel.getEncoding();
+
+ template = ( encoding == null ) ? velocity.getEngine().getTemplate( templateName )
+ : velocity.getEngine().getTemplate( templateName, encoding );
+ }
+ catch ( ParseErrorException pee )
+ {
+ throw new RendererException( "Velocity parsing error while reading the site decoration template "
+ + ( ( skin == null ) ? ( "'" + templateName + "'" ) : ( "from " + skin.getId() + " skin" ) ),
+ pee );
+ }
+ catch ( ResourceNotFoundException rnfe )
+ {
+ throw new RendererException( "Could not find the site decoration template "
+ + ( ( skin == null ) ? ( "'" + templateName + "'" ) : ( "from " + skin.getId() + " skin" ) ),
+ rnfe );
+ }
+
+ try
+ {
+ StringWriter sw = new StringWriter();
+ template.merge( context, sw );
+ writer.write( sw.toString().replaceAll( "\r?\n", SystemUtils.LINE_SEPARATOR ) );
+ }
+ catch ( VelocityException ve )
+ {
+ throw new RendererException( "Velocity error while merging site decoration template.", ve );
+ }
+ catch ( IOException ioe )
+ {
+ throw new RendererException( "IO exception while merging site decoration template.", ioe );
+ }
+ }
+ finally
+ {
+ IOUtil.close( writer );
+
+ if ( old != null )
+ {
+ Thread.currentThread().setContextClassLoader( old );
+ }
+ }
+ }
+
+ private SiteRenderingContext createSiteRenderingContext( Map attributes, DecorationModel decoration,
+ String defaultWindowTitle, Locale locale )
+ {
+ SiteRenderingContext context = new SiteRenderingContext();
+
+ context.setTemplateProperties( attributes );
+ context.setLocale( locale );
+ context.setDecoration( decoration );
+ context.setDefaultWindowTitle( defaultWindowTitle );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public SiteRenderingContext createContextForSkin( Artifact skin, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws IOException, RendererException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setSkin( skin );
+
+ ZipFile zipFile = getZipFile( skin.getFile() );
+ InputStream in = null;
+
+ try
+ {
+ if ( zipFile.getEntry( SKIN_TEMPLATE_LOCATION ) != null )
+ {
+ context.setTemplateName( SKIN_TEMPLATE_LOCATION );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{skin.getFile().toURI().toURL()} ) );
+ }
+ else
+ {
+ context.setTemplateName( DEFAULT_TEMPLATE );
+ context.setTemplateClassLoader( getClass().getClassLoader() );
+ context.setUsingDefaultTemplate( true );
+ }
+
+ ZipEntry skinDescriptorEntry = zipFile.getEntry( SkinModel.SKIN_DESCRIPTOR_LOCATION );
+ if ( skinDescriptorEntry != null )
+ {
+ in = zipFile.getInputStream( skinDescriptorEntry );
+
+ SkinModel skinModel = new SkinXpp3Reader().read( in );
+ context.setSkinModel( skinModel );
+
+ String toolsPrerequisite =
+ skinModel.getPrerequisites() == null ? null : skinModel.getPrerequisites().getDoxiaSitetools();
+
+ Package p = DefaultSiteRenderer.class.getPackage();
+ String current = ( p == null ) ? null : p.getImplementationVersion();
+
+ if ( StringUtils.isNotBlank( toolsPrerequisite ) && ( current != null )
+ && !matchVersion( current, toolsPrerequisite ) )
+ {
+ throw new RendererException( "Cannot use skin: has " + toolsPrerequisite
+ + " Doxia Sitetools prerequisite, but current is " + current );
+ }
+ }
+ }
+ catch ( XmlPullParserException e )
+ {
+ throw new RendererException( "Failed to parse " + SkinModel.SKIN_DESCRIPTOR_LOCATION
+ + " skin descriptor from " + skin.getId() + " skin", e );
+ }
+ finally
+ {
+ IOUtil.close( in );
+ closeZipFile( zipFile );
+ }
+
+ return context;
+ }
+
+ boolean matchVersion( String current, String prerequisite )
+ throws RendererException
+ {
+ try
+ {
+ ArtifactVersion v = new DefaultArtifactVersion( current );
+ VersionRange vr = VersionRange.createFromVersionSpec( prerequisite );
+
+ boolean matched = false;
+ ArtifactVersion recommendedVersion = vr.getRecommendedVersion();
+ if ( recommendedVersion == null )
+ {
+ List restrictions = vr.getRestrictions();
+ for ( Restriction restriction : restrictions )
+ {
+ if ( restriction.containsVersion( v ) )
+ {
+ matched = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // only singular versions ever have a recommendedVersion
+ @SuppressWarnings( "unchecked" )
+ int compareTo = recommendedVersion.compareTo( v );
+ matched = ( compareTo <= 0 );
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Skin doxia-sitetools prerequisite: " + prerequisite + ", current: " + current
+ + ", matched = " + matched );
+ }
+
+ return matched;
+ }
+ catch ( InvalidVersionSpecificationException e )
+ {
+ throw new RendererException( "Invalid skin doxia-sitetools prerequisite: " + prerequisite, e );
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Deprecated
+ public SiteRenderingContext createContextForTemplate( File templateFile, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws MalformedURLException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setTemplateName( templateFile.getName() );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{templateFile.getParentFile().toURI().toURL()} ) );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File resourcesDirectory,
+ File outputDirectory )
+ throws IOException
+ {
+ throw new AssertionError( "copyResources( SiteRenderingContext, File, File ) is deprecated." );
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File outputDirectory )
+ throws IOException
+ {
+ if ( siteRenderingContext.getSkin() != null )
+ {
+ ZipFile file = getZipFile( siteRenderingContext.getSkin().getFile() );
+
+ try
+ {
+ for ( Enumeration extends ZipEntry> e = file.entries(); e.hasMoreElements(); )
+ {
+ ZipEntry entry = e.nextElement();
+
+ if ( !entry.getName().startsWith( "META-INF/" ) )
+ {
+ File destFile = new File( outputDirectory, entry.getName() );
+ if ( !entry.isDirectory() )
+ {
+ if ( destFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ destFile.getParentFile().mkdirs();
+
+ copyFileFromZip( file, entry, destFile );
+ }
+ else
+ {
+ destFile.mkdirs();
+ }
+ }
+ }
+ }
+ finally
+ {
+ closeZipFile( file );
+ }
+ }
+
+ if ( siteRenderingContext.isUsingDefaultTemplate() )
+ {
+ InputStream resourceList = getClass().getClassLoader()
+ .getResourceAsStream( RESOURCE_DIR + "/resources.txt" );
+
+ if ( resourceList != null )
+ {
+ Reader r = null;
+ LineNumberReader reader = null;
+ try
+ {
+ r = ReaderFactory.newReader( resourceList, ReaderFactory.UTF_8 );
+ reader = new LineNumberReader( r );
+
+ String line;
+
+ while ( ( line = reader.readLine() ) != null )
+ {
+ if ( line.startsWith( "#" ) || line.trim().length() == 0 )
+ {
+ continue;
+ }
+
+ InputStream is = getClass().getClassLoader().getResourceAsStream( RESOURCE_DIR + "/" + line );
+
+ if ( is == null )
+ {
+ throw new IOException( "The resource " + line + " doesn't exist." );
+ }
+
+ File outputFile = new File( outputDirectory, line );
+
+ if ( outputFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ OutputStream os = null;
+ try
+ {
+ // for the images
+ os = new FileOutputStream( outputFile );
+ IOUtil.copy( is, os );
+ }
+ finally
+ {
+ IOUtil.close( os );
+ }
+
+ IOUtil.close( is );
+ }
+ }
+ finally
+ {
+ IOUtil.close( reader );
+ IOUtil.close( r );
+ }
+ }
+ }
+
+ // Copy extra site resources
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ File resourcesDirectory = new File( siteDirectory, "resources" );
+
+ if ( resourcesDirectory != null && resourcesDirectory.exists() )
+ {
+ copyDirectory( resourcesDirectory, outputDirectory );
+ }
+ }
+
+ // Check for the existence of /css/site.css
+ File siteCssFile = new File( outputDirectory, "/css/site.css" );
+ if ( !siteCssFile.exists() )
+ {
+ // Create the subdirectory css if it doesn't exist, DOXIA-151
+ File cssDirectory = new File( outputDirectory, "/css/" );
+ boolean created = cssDirectory.mkdirs();
+ if ( created && getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created." );
+ }
+
+ // If the file is not there - create an empty file, DOXIA-86
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The file '" + siteCssFile.getAbsolutePath() + "' does not exist. Creating an empty file." );
+ }
+ Writer writer = null;
+ try
+ {
+ writer = WriterFactory.newWriter( siteCssFile, siteRenderingContext.getOutputEncoding() );
+ //DOXIA-290...the file should not be 0 bytes.
+ writer.write( "/* You can override this file with your own styles */" );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ }
+
+ private static void copyFileFromZip( ZipFile file, ZipEntry entry, File destFile )
+ throws IOException
+ {
+ FileOutputStream fos = new FileOutputStream( destFile );
+
+ try
+ {
+ IOUtil.copy( file.getInputStream( entry ), fos );
+ }
+ finally
+ {
+ IOUtil.close( fos );
+ }
+ }
+
+ /**
+ * Copy the directory
+ *
+ * @param source source file to be copied
+ * @param destination destination file
+ * @throws java.io.IOException if any
+ */
+ protected void copyDirectory( File source, File destination )
+ throws IOException
+ {
+ if ( source.exists() )
+ {
+ DirectoryScanner scanner = new DirectoryScanner();
+
+ String[] includedResources = {"**/**"};
+
+ scanner.setIncludes( includedResources );
+
+ scanner.addDefaultExcludes();
+
+ scanner.setBasedir( source );
+
+ scanner.scan();
+
+ List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
+
+ for ( String name : includedFiles )
+ {
+ File sourceFile = new File( source, name );
+
+ File destinationFile = new File( destination, name );
+
+ FileUtils.copyFile( sourceFile, destinationFile );
+ }
+ }
+ }
+
+ private Reader validate( Reader source, String resource )
+ throws ParseException, IOException
+ {
+ getLogger().debug( "Validating: " + resource );
+
+ try
+ {
+ String content = IOUtil.toString( new BufferedReader( source ) );
+
+ new XmlValidator( new PlexusLoggerWrapper( getLogger() ) ).validate( content );
+
+ return new StringReader( content );
+ }
+ finally
+ {
+ IOUtil.close( source );
+ }
+ }
+
+ // TODO replace with StringUtils.endsWithIgnoreCase() from maven-shared-utils 0.7
+ static boolean endsWithIgnoreCase( String str, String searchStr )
+ {
+ if ( str.length() < searchStr.length() )
+ {
+ return false;
+ }
+
+ return str.regionMatches( true, str.length() - searchStr.length(), searchStr, 0, searchStr.length() );
+ }
+
+ private static ZipFile getZipFile( File file )
+ throws IOException
+ {
+ if ( file == null )
+ {
+ throw new IOException( "Error opening ZipFile: null" );
+ }
+
+ try
+ {
+ // TODO: plexus-archiver, if it could do the excludes
+ return new ZipFile( file );
+ }
+ catch ( ZipException ex )
+ {
+ IOException ioe = new IOException( "Error opening ZipFile: " + file.getAbsolutePath() );
+ ioe.initCause( ex );
+ throw ioe;
+ }
+ }
+
+ private static void closeZipFile( ZipFile zipFile )
+ {
+ // TODO: move to plexus utils
+ try
+ {
+ zipFile.close();
+ }
+ catch ( IOException e )
+ {
+ // ignore
+ }
+ }
+}
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_647/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_647/npe.json
new file mode 100644
index 000000000..34b3d9d45
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_647/npe.json
@@ -0,0 +1,7 @@
+{
+ "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java",
+ "line": 651,
+ "npe_method": "createSiteTemplateVelocityContext",
+ "deref_field": "getDefaultWindowTitle",
+ "npe_class": "DefaultSiteRenderer"
+}
\ No newline at end of file
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_736/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_736/buggy.java
new file mode 100644
index 000000000..53c640474
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_736/buggy.java
@@ -0,0 +1,1161 @@
+package org.apache.maven.doxia.siterenderer;
+
+/*
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+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 java.util.Properties;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.Restriction;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.doxia.Doxia;
+import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
+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.site.decoration.DecorationModel;
+import org.apache.maven.doxia.site.decoration.PublishDate;
+import org.apache.maven.doxia.site.skin.SkinModel;
+import org.apache.maven.doxia.site.skin.io.xpp3.SkinXpp3Reader;
+import org.apache.maven.doxia.parser.module.ParserModule;
+import org.apache.maven.doxia.parser.module.ParserModuleManager;
+import org.apache.maven.doxia.parser.module.ParserModuleNotFoundException;
+import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
+import org.apache.maven.doxia.util.XmlValidator;
+import org.apache.velocity.Template;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.Scope;
+import org.apache.velocity.tools.ToolManager;
+import org.apache.velocity.tools.config.ConfigurationUtils;
+import org.apache.velocity.tools.config.EasyFactoryConfiguration;
+import org.apache.velocity.tools.config.FactoryConfiguration;
+import org.apache.velocity.tools.generic.AlternatorTool;
+import org.apache.velocity.tools.generic.ClassTool;
+import org.apache.velocity.tools.generic.ComparisonDateTool;
+import org.apache.velocity.tools.generic.ContextTool;
+import org.apache.velocity.tools.generic.ConversionTool;
+import org.apache.velocity.tools.generic.DisplayTool;
+import org.apache.velocity.tools.generic.EscapeTool;
+import org.apache.velocity.tools.generic.FieldTool;
+import org.apache.velocity.tools.generic.LinkTool;
+import org.apache.velocity.tools.generic.LoopTool;
+import org.apache.velocity.tools.generic.MathTool;
+import org.apache.velocity.tools.generic.NumberTool;
+import org.apache.velocity.tools.generic.RenderTool;
+import org.apache.velocity.tools.generic.ResourceTool;
+import org.apache.velocity.tools.generic.SortTool;
+import org.apache.velocity.tools.generic.XmlTool;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.i18n.I18N;
+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.Os;
+import org.codehaus.plexus.util.PathTool;
+import org.codehaus.plexus.util.PropertyUtils;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.WriterFactory;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.codehaus.plexus.velocity.VelocityComponent;
+
+/**
+ * DefaultSiteRenderer class.
+ *
+ * @author Emmanuel Venisse
+ * @author Vincent Siveton
+ * @since 1.0
+ */
+@Component( role = Renderer.class )
+public class DefaultSiteRenderer
+ extends AbstractLogEnabled
+ implements Renderer
+{
+ // ----------------------------------------------------------------------
+ // Requirements
+ // ----------------------------------------------------------------------
+
+ @Requirement
+ private VelocityComponent velocity;
+
+ @Requirement
+ private ParserModuleManager parserModuleManager;
+
+ @Requirement
+ private Doxia doxia;
+
+ @Requirement
+ private I18N i18n;
+
+ @Requirement
+ private PlexusContainer plexus;
+
+ private static final String RESOURCE_DIR = "org/apache/maven/doxia/siterenderer/resources";
+
+ private static final String DEFAULT_TEMPLATE = RESOURCE_DIR + "/default-site.vm";
+
+ private static final String SKIN_TEMPLATE_LOCATION = "META-INF/maven/site.vm";
+
+ private static final String TOOLS_LOCATION = "META-INF/maven/site-tools.xml";
+
+ // ----------------------------------------------------------------------
+ // Renderer implementation
+ // ----------------------------------------------------------------------
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext )
+ throws IOException, RendererException
+ {
+ return locateDocumentFiles( siteRenderingContext, false );
+ }
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext,
+ boolean editable )
+ throws IOException, RendererException
+ {
+ Map files = new LinkedHashMap();
+ Map moduleExcludes = siteRenderingContext.getModuleExcludes();
+
+ // look in every site directory (in general src/site or target/generated-site)
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ if ( siteDirectory.exists() )
+ {
+ Collection modules = parserModuleManager.getParserModules();
+ // use every Doxia parser module
+ for ( ParserModule module : modules )
+ {
+ File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() );
+
+ String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
+
+ addModuleFiles( siteRenderingContext.getRootDirectory(), moduleBasedir, module, excludes, files,
+ editable );
+ }
+ }
+ }
+
+ // look in specific modules directories (used for old Maven 1.x site layout: xdoc and fml docs in /xdocs)
+ for ( ExtraDoxiaModuleReference module : siteRenderingContext.getModules() )
+ {
+ try
+ {
+ ParserModule parserModule = parserModuleManager.getParserModule( module.getParserId() );
+
+ String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
+
+ addModuleFiles( siteRenderingContext.getRootDirectory(), module.getBasedir(), parserModule, excludes,
+ files, editable );
+ }
+ catch ( ParserModuleNotFoundException e )
+ {
+ throw new RendererException( "Unable to find module: " + e.getMessage(), e );
+ }
+ }
+ return files;
+ }
+
+ private List filterExtensionIgnoreCase( List fileNames, String extension )
+ {
+ List filtered = new LinkedList( fileNames );
+ for ( Iterator it = filtered.iterator(); it.hasNext(); )
+ {
+ String name = it.next();
+
+ // Take care of extension case
+ if ( !endsWithIgnoreCase( name, extension ) )
+ {
+ it.remove();
+ }
+ }
+ return filtered;
+ }
+
+ private void addModuleFiles( File rootDir, File moduleBasedir, ParserModule module, String excludes,
+ Map files, boolean editable )
+ throws IOException, RendererException
+ {
+ if ( !moduleBasedir.exists() || ArrayUtils.isEmpty( module.getExtensions() ) )
+ {
+ return;
+ }
+
+ String moduleRelativePath =
+ PathTool.getRelativeFilePath( rootDir.getAbsolutePath(), moduleBasedir.getAbsolutePath() );
+
+ List allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", excludes, false );
+
+ for ( String extension : module.getExtensions() )
+ {
+ String fullExtension = "." + extension;
+
+ List docs = filterExtensionIgnoreCase( allFiles, fullExtension );
+
+ // *..vm
+ List velocityFiles = filterExtensionIgnoreCase( allFiles, fullExtension + ".vm" );
+
+ docs.addAll( velocityFiles );
+
+ for ( String doc : docs )
+ {
+ RenderingContext context = new RenderingContext( moduleBasedir, moduleRelativePath, doc,
+ module.getParserId(), extension, editable );
+
+ // TODO: DOXIA-111: we need a general filter here that knows how to alter the context
+ if ( endsWithIgnoreCase( doc, ".vm" ) )
+ {
+ context.setAttribute( "velocity", "true" );
+ }
+
+ String key = context.getOutputName();
+ key = StringUtils.replace( key, "\\", "/" );
+
+ if ( files.containsKey( key ) )
+ {
+ DocumentRenderer renderer = files.get( key );
+
+ RenderingContext originalContext = renderer.getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' clashes with existing '" + originalDoc + "'." );
+ }
+ // -----------------------------------------------------------------------
+ // Handle key without case differences
+ // -----------------------------------------------------------------------
+ for ( Map.Entry entry : files.entrySet() )
+ {
+ if ( entry.getKey().equalsIgnoreCase( key ) )
+ {
+ RenderingContext originalContext = entry.getValue().getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
+ {
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator
+ + doc + "' clashes with existing '" + originalDoc + "'." );
+ }
+
+ if ( getLogger().isWarnEnabled() )
+ {
+ getLogger().warn( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' could clash with existing '" + originalDoc + "'." );
+ }
+ }
+ }
+
+ files.put( key, new DoxiaDocumentRenderer( context ) );
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void render( Collection documents, SiteRenderingContext siteRenderingContext,
+ File outputDirectory )
+ throws RendererException, IOException
+ {
+ for ( DocumentRenderer docRenderer : documents )
+ {
+ RenderingContext renderingContext = docRenderer.getRenderingContext();
+
+ File outputFile = new File( outputDirectory, docRenderer.getOutputName() );
+
+ File inputFile = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
+
+ boolean modified = !outputFile.exists() || ( inputFile.lastModified() > outputFile.lastModified() )
+ || ( siteRenderingContext.getDecoration().getLastModified() > outputFile.lastModified() );
+
+ if ( modified || docRenderer.isOverwrite() )
+ {
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Generating " + outputFile );
+ }
+
+ Writer writer = null;
+ try
+ {
+ if ( !docRenderer.isExternalReport() )
+ {
+ writer = WriterFactory.newWriter( outputFile, siteRenderingContext.getOutputEncoding() );
+ }
+ docRenderer.renderDocument( writer, this, siteRenderingContext );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ else
+ {
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( inputFile + " unchanged, not regenerating..." );
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void renderDocument( Writer writer, RenderingContext docRenderingContext, SiteRenderingContext siteContext )
+ throws RendererException, FileNotFoundException, UnsupportedEncodingException
+ {
+ SiteRendererSink sink = new SiteRendererSink( docRenderingContext );
+
+ File doc = new File( docRenderingContext.getBasedir(), docRenderingContext.getInputName() );
+
+ Reader reader = null;
+ try
+ {
+ String resource = doc.getAbsolutePath();
+
+ Parser parser = doxia.getParser( docRenderingContext.getParserId() );
+ // DOXIASITETOOLS-146 don't render comments from source markup
+ parser.setEmitComments( false );
+
+ // TODO: DOXIA-111: the filter used here must be checked generally.
+ if ( docRenderingContext.getAttribute( "velocity" ) != null )
+ {
+ getLogger().debug( "Processing Velocity for " + docRenderingContext.getDoxiaSourcePath() );
+ try
+ {
+ Context vc = createDocumentVelocityContext( docRenderingContext, siteContext );
+
+ StringWriter sw = new StringWriter();
+
+ velocity.getEngine().mergeTemplate( resource, siteContext.getInputEncoding(), vc, sw );
+
+ String doxiaContent = sw.toString();
+
+ if ( siteContext.getProcessedContentOutput() != null )
+ {
+ // save Velocity processing result, ie the Doxia content that will be parsed after
+ saveVelocityProcessedContent( docRenderingContext, siteContext, doxiaContent );
+ }
+
+ reader = new StringReader( doxiaContent );
+ }
+ catch ( VelocityException e )
+ {
+ throw new RendererException( "Error parsing " + docRenderingContext.getDoxiaSourcePath()
+ + " as a Velocity template: " + e.getMessage(), e );
+ }
+
+ if ( parser.getType() == Parser.XML_TYPE && siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ }
+ else
+ {
+ switch ( parser.getType() )
+ {
+ case Parser.XML_TYPE:
+ reader = ReaderFactory.newXmlReader( doc );
+ if ( siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ break;
+
+ case Parser.TXT_TYPE:
+ case Parser.UNKNOWN_TYPE:
+ default:
+ reader = ReaderFactory.newReader( doc, siteContext.getInputEncoding() );
+ }
+ }
+ sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) );
+
+ doxia.parse( reader, docRenderingContext.getParserId(), sink );
+ }
+ catch ( ParserNotFoundException e )
+ {
+ throw new RendererException( "Error getting a parser for '" + doc + "': " + e.getMessage(), e );
+ }
+ catch ( ParseException e )
+ {
+ StringBuilder errorMsgBuilder = new StringBuilder();
+ errorMsgBuilder.append( "Error parsing '" ).append( doc ).append( "': " );
+ if ( e.getLineNumber() > 0 )
+ {
+ errorMsgBuilder.append( "line [" ).append( e.getLineNumber() ).append( "] " );
+ }
+ errorMsgBuilder.append( e.getMessage() );
+ throw new RendererException( errorMsgBuilder.toString(), e );
+ }
+ catch ( IOException e )
+ {
+ throw new RendererException( "IOException when processing '" + doc + "'", e );
+ }
+ finally
+ {
+ sink.flush();
+
+ sink.close();
+
+ IOUtil.close( reader );
+ }
+
+ mergeDocumentIntoSite( writer, (DocumentContent) sink, siteContext );
+ }
+
+ private void saveVelocityProcessedContent( RenderingContext docRenderingContext, SiteRenderingContext siteContext,
+ String doxiaContent )
+ throws IOException
+ {
+ if ( !siteContext.getProcessedContentOutput().exists() )
+ {
+ siteContext.getProcessedContentOutput().mkdirs();
+ }
+
+ String input = docRenderingContext.getInputName();
+ File outputFile = new File( siteContext.getProcessedContentOutput(),
+ input.substring( 0, input.length() - 3 ) );
+
+ File outputParent = outputFile.getParentFile();
+ if ( !outputParent.exists() )
+ {
+ outputParent.mkdirs();
+ }
+
+ FileUtils.fileWrite( outputFile, siteContext.getInputEncoding(), doxiaContent );
+ }
+
+ /**
+ * Creates a Velocity Context with all generic tools configured wit the site rendering context.
+ *
+ * @param siteRenderingContext the site rendering context
+ * @return a Velocity tools managed context
+ */
+ protected Context createToolManagedVelocityContext( SiteRenderingContext siteRenderingContext )
+ {
+ Locale locale = siteRenderingContext.getLocale();
+ String dateFormat = siteRenderingContext.getDecoration().getPublishDate().getFormat();
+
+ EasyFactoryConfiguration config = new EasyFactoryConfiguration( false );
+ config.property( "safeMode", Boolean.FALSE );
+ config.toolbox( Scope.REQUEST )
+ .tool( ContextTool.class )
+ .tool( LinkTool.class )
+ .tool( LoopTool.class )
+ .tool( RenderTool.class );
+ config.toolbox( Scope.APPLICATION ).property( "locale", locale )
+ .tool( AlternatorTool.class )
+ .tool( ClassTool.class )
+ .tool( ComparisonDateTool.class ).property( "format", dateFormat )
+ .tool( ConversionTool.class ).property( "dateFormat", dateFormat )
+ .tool( DisplayTool.class )
+ .tool( EscapeTool.class )
+ .tool( FieldTool.class )
+ .tool( MathTool.class )
+ .tool( NumberTool.class )
+ .tool( ResourceTool.class ).property( "bundles", new String[] { "site-renderer" } )
+ .tool( SortTool.class )
+ .tool( XmlTool.class );
+
+ FactoryConfiguration customConfig = ConfigurationUtils.findInClasspath( TOOLS_LOCATION );
+
+ if ( customConfig != null )
+ {
+ config.addConfiguration( customConfig );
+ }
+
+ ToolManager manager = new ToolManager( false, false );
+ manager.configure( config );
+
+ return manager.createContext();
+ }
+
+ /**
+ * Create a Velocity Context for a Doxia document, containing every information about rendered document.
+ *
+ * @param sink the site renderer sink for the document
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createDocumentVelocityContext( RenderingContext renderingContext,
+ SiteRenderingContext siteRenderingContext )
+ {
+ Context context = createToolManagedVelocityContext( siteRenderingContext );
+ // ----------------------------------------------------------------------
+ // Data objects
+ // ----------------------------------------------------------------------
+
+ context.put( "relativePath", renderingContext.getRelativePath() );
+
+ String currentFileName = renderingContext.getOutputName().replace( '\\', '/' );
+ context.put( "currentFileName", currentFileName );
+
+ context.put( "alignedFileName", PathTool.calculateLink( currentFileName, renderingContext.getRelativePath() ) );
+
+ context.put( "decoration", siteRenderingContext.getDecoration() );
+
+ Locale locale = siteRenderingContext.getLocale();
+ context.put( "locale", locale );
+ context.put( "supportedLocales", Collections.unmodifiableList( siteRenderingContext.getSiteLocales() ) );
+
+ context.put( "currentDate", new Date() );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateRevision", sdf.format( new Date() ) );
+
+ context.put( "publishDate", siteRenderingContext.getPublishDate() );
+
+ PublishDate publishDate = siteRenderingContext.getDecoration().getPublishDate();
+ DateFormat dateFormat = new SimpleDateFormat( publishDate.getFormat(), locale );
+ context.put( "dateFormat", dateFormat );
+
+ // doxiaSiteRendererVersion
+ InputStream inputStream = this.getClass().getResourceAsStream( "/META-INF/"
+ + "maven/org.apache.maven.doxia/doxia-site-renderer/pom.properties" );
+ Properties properties = PropertyUtils.loadProperties( inputStream );
+ if ( inputStream == null )
+ {
+ getLogger().debug( "pom.properties for doxia-site-renderer could not be found." );
+ }
+ else if ( properties == null )
+ {
+ getLogger().debug( "Failed to load pom.properties, so doxiaVersion is not available"
+ + " in the Velocity context." );
+ }
+ else
+ {
+ context.put( "doxiaSiteRendererVersion", properties.getProperty( "version" ) );
+ }
+
+ // Add user properties
+ Map templateProperties = siteRenderingContext.getTemplateProperties();
+
+ if ( templateProperties != null )
+ {
+ for ( Map.Entry entry : templateProperties.entrySet() )
+ {
+ context.put( entry.getKey(), entry.getValue() );
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Tools
+ // ----------------------------------------------------------------------
+
+ context.put( "PathTool", new PathTool() );
+
+ context.put( "FileUtils", new FileUtils() );
+
+ context.put( "StringUtils", new StringUtils() );
+
+ context.put( "i18n", i18n );
+
+ context.put( "plexus", plexus );
+ return context;
+ }
+
+ /**
+ * Create a Velocity Context for the site template decorating the document. In addition to all the informations
+ * from the document, this context contains data gathered in {@link SiteRendererSink} during document rendering.
+ *
+ * @param content the document content to be merged into the template
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createSiteTemplateVelocityContext( DocumentContent content,
+ SiteRenderingContext siteRenderingContext )
+ {
+ // first get the context from document
+ Context context = createDocumentVelocityContext( content.getRenderingContext(), siteRenderingContext );
+
+ // then add data objects from rendered document
+
+ // Add infos from document
+ context.put( "authors", content.getAuthors() );
+
+ context.put( "shortTitle", content.getTitle() );
+
+ // DOXIASITETOOLS-70: Prepend the project name to the title, if any
+ String title = "";
+ if ( siteRenderingContext.getDecoration() != null
+ && siteRenderingContext.getDecoration().getName() != null )
+ {
+ title = siteRenderingContext.getDecoration().getName();
+ }
+ else if ( siteRenderingContext.getDefaultWindowTitle() != null )
+ {
+ title = siteRenderingContext.getDefaultWindowTitle();
+ }
+
+ if ( title.length() > 0 )
+ {
+ title += " – "; // Symbol Name: En Dash, Html Entity: –
+ }
+ title += content.getTitle();
+
+ context.put( "title", title );
+
+ context.put( "headContent", content.getHead() );
+
+ context.put( "bodyContent", content.getBody() );
+
+ // document date (got from Doxia Sink date() API)
+ String documentDate = content.getDate();
+ if ( StringUtils.isNotEmpty( documentDate ) )
+ {
+ context.put( "documentDate", documentDate );
+
+ // deprecated variables that rework the document date, suppose one semantics over others
+ // (ie creation date, while it may be last modification date if the document writer decided so)
+ // see DOXIASITETOOLS-20 for the beginning and DOXIASITETOOLS-164 for the end of this story
+ try
+ {
+ // we support only ISO 8601 date
+ Date creationDate = new SimpleDateFormat( "yyyy-MM-dd" ).parse( documentDate );
+
+ context.put( "creationDate", creationDate );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateCreation", sdf.format( creationDate ) );
+ }
+ catch ( java.text.ParseException e )
+ {
+ getLogger().warn( "Could not parse date '" + documentDate + "' from "
+ + content.getRenderingContext().getInputName()
+ + " (expected yyyy-MM-dd format), ignoring!" );
+ }
+ }
+
+ // document rendering context, to get eventual inputName
+ context.put( "docRenderingContext", content.getRenderingContext() );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
+ throws RendererException
+ {
+ mergeDocumentIntoSite( writer, sink, siteRenderingContext );
+ }
+
+ /** {@inheritDoc} */
+/**
+ * {@inheritDoc }
+ */
+public void mergeDocumentIntoSite(java.io.Writer writer, org.apache.maven.doxia.siterenderer.DocumentContent content, org.apache.maven.doxia.siterenderer.SiteRenderingContext siteRenderingContext) throws org.apache.maven.doxia.siterenderer.RendererException {
+ java.lang.String templateName = siteRenderingContext.getTemplateName();
+ getLogger().debug((("Processing Velocity for template " + templateName) + " on ") + content.getRenderingContext().getInputName());
+ org.apache.velocity.context.Context context = createSiteTemplateVelocityContext(content, siteRenderingContext);
+ java.lang.ClassLoader old = null;
+ if (siteRenderingContext.getTemplateClassLoader() != null) {
+ // -------------------------------------------------------------------------
+ // If no template classloader was set we'll just use the context classloader
+ // -------------------------------------------------------------------------
+ old = java.lang.Thread.currentThread().getContextClassLoader();
+ java.lang.Thread.currentThread().setContextClassLoader(siteRenderingContext.getTemplateClassLoader());
+ }
+ try {
+ org.apache.velocity.Template template;
+ org.apache.maven.artifact.Artifact skin = siteRenderingContext.getSkin();
+ try {
+ org.apache.maven.doxia.site.skin.SkinModel skinModel = siteRenderingContext.getSkinModel();
+ java.lang.String encoding = /* NPEX_NULL_EXP */
+ skinModel.getEncoding();
+ template = (encoding == null) ? velocity.getEngine().getTemplate(templateName) : velocity.getEngine().getTemplate(templateName, encoding);
+ } catch (org.apache.velocity.exception.ParseErrorException pee) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("Velocity parsing error while reading the site decoration template " + (skin == null ? ("'" + templateName) + "'" : ("from " + skin.getId()) + " skin"), pee);
+ } catch (org.apache.velocity.exception.ResourceNotFoundException rnfe) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("Could not find the site decoration template " + (skin == null ? ("'" + templateName) + "'" : ("from " + skin.getId()) + " skin"), rnfe);
+ }
+ try {
+ java.io.StringWriter sw = new java.io.StringWriter();
+ template.merge(context, sw);
+ writer.write(sw.toString().replaceAll("\r?\n", org.apache.commons.lang3.SystemUtils.LINE_SEPARATOR));
+ } catch (org.apache.velocity.exception.VelocityException ve) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("Velocity error while merging site decoration template.", ve);
+ } catch (java.io.IOException ioe) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("IO exception while merging site decoration template.", ioe);
+ }
+ } finally {
+ org.codehaus.plexus.util.IOUtil.close(writer);
+ if (old != null) {
+ java.lang.Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+}
+
+ private SiteRenderingContext createSiteRenderingContext( Map attributes, DecorationModel decoration,
+ String defaultWindowTitle, Locale locale )
+ {
+ SiteRenderingContext context = new SiteRenderingContext();
+
+ context.setTemplateProperties( attributes );
+ context.setLocale( locale );
+ context.setDecoration( decoration );
+ context.setDefaultWindowTitle( defaultWindowTitle );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public SiteRenderingContext createContextForSkin( Artifact skin, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws IOException, RendererException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setSkin( skin );
+
+ ZipFile zipFile = getZipFile( skin.getFile() );
+ InputStream in = null;
+
+ try
+ {
+ if ( zipFile.getEntry( SKIN_TEMPLATE_LOCATION ) != null )
+ {
+ context.setTemplateName( SKIN_TEMPLATE_LOCATION );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{skin.getFile().toURI().toURL()} ) );
+ }
+ else
+ {
+ context.setTemplateName( DEFAULT_TEMPLATE );
+ context.setTemplateClassLoader( getClass().getClassLoader() );
+ context.setUsingDefaultTemplate( true );
+ }
+
+ ZipEntry skinDescriptorEntry = zipFile.getEntry( SkinModel.SKIN_DESCRIPTOR_LOCATION );
+ if ( skinDescriptorEntry != null )
+ {
+ in = zipFile.getInputStream( skinDescriptorEntry );
+
+ SkinModel skinModel = new SkinXpp3Reader().read( in );
+ context.setSkinModel( skinModel );
+
+ String toolsPrerequisite =
+ skinModel.getPrerequisites() == null ? null : skinModel.getPrerequisites().getDoxiaSitetools();
+
+ Package p = DefaultSiteRenderer.class.getPackage();
+ String current = ( p == null ) ? null : p.getImplementationVersion();
+
+ if ( StringUtils.isNotBlank( toolsPrerequisite ) && ( current != null )
+ && !matchVersion( current, toolsPrerequisite ) )
+ {
+ throw new RendererException( "Cannot use skin: has " + toolsPrerequisite
+ + " Doxia Sitetools prerequisite, but current is " + current );
+ }
+ }
+ }
+ catch ( XmlPullParserException e )
+ {
+ throw new RendererException( "Failed to parse " + SkinModel.SKIN_DESCRIPTOR_LOCATION
+ + " skin descriptor from " + skin.getId() + " skin", e );
+ }
+ finally
+ {
+ IOUtil.close( in );
+ closeZipFile( zipFile );
+ }
+
+ return context;
+ }
+
+ boolean matchVersion( String current, String prerequisite )
+ throws RendererException
+ {
+ try
+ {
+ ArtifactVersion v = new DefaultArtifactVersion( current );
+ VersionRange vr = VersionRange.createFromVersionSpec( prerequisite );
+
+ boolean matched = false;
+ ArtifactVersion recommendedVersion = vr.getRecommendedVersion();
+ if ( recommendedVersion == null )
+ {
+ List restrictions = vr.getRestrictions();
+ for ( Restriction restriction : restrictions )
+ {
+ if ( restriction.containsVersion( v ) )
+ {
+ matched = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // only singular versions ever have a recommendedVersion
+ @SuppressWarnings( "unchecked" )
+ int compareTo = recommendedVersion.compareTo( v );
+ matched = ( compareTo <= 0 );
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Skin doxia-sitetools prerequisite: " + prerequisite + ", current: " + current
+ + ", matched = " + matched );
+ }
+
+ return matched;
+ }
+ catch ( InvalidVersionSpecificationException e )
+ {
+ throw new RendererException( "Invalid skin doxia-sitetools prerequisite: " + prerequisite, e );
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Deprecated
+ public SiteRenderingContext createContextForTemplate( File templateFile, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws MalformedURLException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setTemplateName( templateFile.getName() );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{templateFile.getParentFile().toURI().toURL()} ) );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File resourcesDirectory,
+ File outputDirectory )
+ throws IOException
+ {
+ throw new AssertionError( "copyResources( SiteRenderingContext, File, File ) is deprecated." );
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File outputDirectory )
+ throws IOException
+ {
+ if ( siteRenderingContext.getSkin() != null )
+ {
+ ZipFile file = getZipFile( siteRenderingContext.getSkin().getFile() );
+
+ try
+ {
+ for ( Enumeration extends ZipEntry> e = file.entries(); e.hasMoreElements(); )
+ {
+ ZipEntry entry = e.nextElement();
+
+ if ( !entry.getName().startsWith( "META-INF/" ) )
+ {
+ File destFile = new File( outputDirectory, entry.getName() );
+ if ( !entry.isDirectory() )
+ {
+ if ( destFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ destFile.getParentFile().mkdirs();
+
+ copyFileFromZip( file, entry, destFile );
+ }
+ else
+ {
+ destFile.mkdirs();
+ }
+ }
+ }
+ }
+ finally
+ {
+ closeZipFile( file );
+ }
+ }
+
+ if ( siteRenderingContext.isUsingDefaultTemplate() )
+ {
+ InputStream resourceList = getClass().getClassLoader()
+ .getResourceAsStream( RESOURCE_DIR + "/resources.txt" );
+
+ if ( resourceList != null )
+ {
+ Reader r = null;
+ LineNumberReader reader = null;
+ try
+ {
+ r = ReaderFactory.newReader( resourceList, ReaderFactory.UTF_8 );
+ reader = new LineNumberReader( r );
+
+ String line;
+
+ while ( ( line = reader.readLine() ) != null )
+ {
+ if ( line.startsWith( "#" ) || line.trim().length() == 0 )
+ {
+ continue;
+ }
+
+ InputStream is = getClass().getClassLoader().getResourceAsStream( RESOURCE_DIR + "/" + line );
+
+ if ( is == null )
+ {
+ throw new IOException( "The resource " + line + " doesn't exist." );
+ }
+
+ File outputFile = new File( outputDirectory, line );
+
+ if ( outputFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ OutputStream os = null;
+ try
+ {
+ // for the images
+ os = new FileOutputStream( outputFile );
+ IOUtil.copy( is, os );
+ }
+ finally
+ {
+ IOUtil.close( os );
+ }
+
+ IOUtil.close( is );
+ }
+ }
+ finally
+ {
+ IOUtil.close( reader );
+ IOUtil.close( r );
+ }
+ }
+ }
+
+ // Copy extra site resources
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ File resourcesDirectory = new File( siteDirectory, "resources" );
+
+ if ( resourcesDirectory != null && resourcesDirectory.exists() )
+ {
+ copyDirectory( resourcesDirectory, outputDirectory );
+ }
+ }
+
+ // Check for the existence of /css/site.css
+ File siteCssFile = new File( outputDirectory, "/css/site.css" );
+ if ( !siteCssFile.exists() )
+ {
+ // Create the subdirectory css if it doesn't exist, DOXIA-151
+ File cssDirectory = new File( outputDirectory, "/css/" );
+ boolean created = cssDirectory.mkdirs();
+ if ( created && getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created." );
+ }
+
+ // If the file is not there - create an empty file, DOXIA-86
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The file '" + siteCssFile.getAbsolutePath() + "' does not exist. Creating an empty file." );
+ }
+ Writer writer = null;
+ try
+ {
+ writer = WriterFactory.newWriter( siteCssFile, siteRenderingContext.getOutputEncoding() );
+ //DOXIA-290...the file should not be 0 bytes.
+ writer.write( "/* You can override this file with your own styles */" );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ }
+
+ private static void copyFileFromZip( ZipFile file, ZipEntry entry, File destFile )
+ throws IOException
+ {
+ FileOutputStream fos = new FileOutputStream( destFile );
+
+ try
+ {
+ IOUtil.copy( file.getInputStream( entry ), fos );
+ }
+ finally
+ {
+ IOUtil.close( fos );
+ }
+ }
+
+ /**
+ * Copy the directory
+ *
+ * @param source source file to be copied
+ * @param destination destination file
+ * @throws java.io.IOException if any
+ */
+ protected void copyDirectory( File source, File destination )
+ throws IOException
+ {
+ if ( source.exists() )
+ {
+ DirectoryScanner scanner = new DirectoryScanner();
+
+ String[] includedResources = {"**/**"};
+
+ scanner.setIncludes( includedResources );
+
+ scanner.addDefaultExcludes();
+
+ scanner.setBasedir( source );
+
+ scanner.scan();
+
+ List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
+
+ for ( String name : includedFiles )
+ {
+ File sourceFile = new File( source, name );
+
+ File destinationFile = new File( destination, name );
+
+ FileUtils.copyFile( sourceFile, destinationFile );
+ }
+ }
+ }
+
+ private Reader validate( Reader source, String resource )
+ throws ParseException, IOException
+ {
+ getLogger().debug( "Validating: " + resource );
+
+ try
+ {
+ String content = IOUtil.toString( new BufferedReader( source ) );
+
+ new XmlValidator( new PlexusLoggerWrapper( getLogger() ) ).validate( content );
+
+ return new StringReader( content );
+ }
+ finally
+ {
+ IOUtil.close( source );
+ }
+ }
+
+ // TODO replace with StringUtils.endsWithIgnoreCase() from maven-shared-utils 0.7
+ static boolean endsWithIgnoreCase( String str, String searchStr )
+ {
+ if ( str.length() < searchStr.length() )
+ {
+ return false;
+ }
+
+ return str.regionMatches( true, str.length() - searchStr.length(), searchStr, 0, searchStr.length() );
+ }
+
+ private static ZipFile getZipFile( File file )
+ throws IOException
+ {
+ if ( file == null )
+ {
+ throw new IOException( "Error opening ZipFile: null" );
+ }
+
+ try
+ {
+ // TODO: plexus-archiver, if it could do the excludes
+ return new ZipFile( file );
+ }
+ catch ( ZipException ex )
+ {
+ IOException ioe = new IOException( "Error opening ZipFile: " + file.getAbsolutePath() );
+ ioe.initCause( ex );
+ throw ioe;
+ }
+ }
+
+ private static void closeZipFile( ZipFile zipFile )
+ {
+ // TODO: move to plexus utils
+ try
+ {
+ zipFile.close();
+ }
+ catch ( IOException e )
+ {
+ // ignore
+ }
+ }
+}
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_736/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_736/npe.json
new file mode 100644
index 000000000..02e49157f
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_736/npe.json
@@ -0,0 +1,7 @@
+{
+ "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java",
+ "line": 725,
+ "npe_method": "mergeDocumentIntoSite",
+ "deref_field": "skinModel",
+ "npe_class": "DefaultSiteRenderer"
+}
\ No newline at end of file
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_738/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_738/buggy.java
new file mode 100644
index 000000000..fdaf4cd5f
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_738/buggy.java
@@ -0,0 +1,1161 @@
+package org.apache.maven.doxia.siterenderer;
+
+/*
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+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 java.util.Properties;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.Restriction;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.doxia.Doxia;
+import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
+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.site.decoration.DecorationModel;
+import org.apache.maven.doxia.site.decoration.PublishDate;
+import org.apache.maven.doxia.site.skin.SkinModel;
+import org.apache.maven.doxia.site.skin.io.xpp3.SkinXpp3Reader;
+import org.apache.maven.doxia.parser.module.ParserModule;
+import org.apache.maven.doxia.parser.module.ParserModuleManager;
+import org.apache.maven.doxia.parser.module.ParserModuleNotFoundException;
+import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
+import org.apache.maven.doxia.util.XmlValidator;
+import org.apache.velocity.Template;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.Scope;
+import org.apache.velocity.tools.ToolManager;
+import org.apache.velocity.tools.config.ConfigurationUtils;
+import org.apache.velocity.tools.config.EasyFactoryConfiguration;
+import org.apache.velocity.tools.config.FactoryConfiguration;
+import org.apache.velocity.tools.generic.AlternatorTool;
+import org.apache.velocity.tools.generic.ClassTool;
+import org.apache.velocity.tools.generic.ComparisonDateTool;
+import org.apache.velocity.tools.generic.ContextTool;
+import org.apache.velocity.tools.generic.ConversionTool;
+import org.apache.velocity.tools.generic.DisplayTool;
+import org.apache.velocity.tools.generic.EscapeTool;
+import org.apache.velocity.tools.generic.FieldTool;
+import org.apache.velocity.tools.generic.LinkTool;
+import org.apache.velocity.tools.generic.LoopTool;
+import org.apache.velocity.tools.generic.MathTool;
+import org.apache.velocity.tools.generic.NumberTool;
+import org.apache.velocity.tools.generic.RenderTool;
+import org.apache.velocity.tools.generic.ResourceTool;
+import org.apache.velocity.tools.generic.SortTool;
+import org.apache.velocity.tools.generic.XmlTool;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.i18n.I18N;
+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.Os;
+import org.codehaus.plexus.util.PathTool;
+import org.codehaus.plexus.util.PropertyUtils;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.WriterFactory;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.codehaus.plexus.velocity.VelocityComponent;
+
+/**
+ * DefaultSiteRenderer class.
+ *
+ * @author Emmanuel Venisse
+ * @author Vincent Siveton
+ * @since 1.0
+ */
+@Component( role = Renderer.class )
+public class DefaultSiteRenderer
+ extends AbstractLogEnabled
+ implements Renderer
+{
+ // ----------------------------------------------------------------------
+ // Requirements
+ // ----------------------------------------------------------------------
+
+ @Requirement
+ private VelocityComponent velocity;
+
+ @Requirement
+ private ParserModuleManager parserModuleManager;
+
+ @Requirement
+ private Doxia doxia;
+
+ @Requirement
+ private I18N i18n;
+
+ @Requirement
+ private PlexusContainer plexus;
+
+ private static final String RESOURCE_DIR = "org/apache/maven/doxia/siterenderer/resources";
+
+ private static final String DEFAULT_TEMPLATE = RESOURCE_DIR + "/default-site.vm";
+
+ private static final String SKIN_TEMPLATE_LOCATION = "META-INF/maven/site.vm";
+
+ private static final String TOOLS_LOCATION = "META-INF/maven/site-tools.xml";
+
+ // ----------------------------------------------------------------------
+ // Renderer implementation
+ // ----------------------------------------------------------------------
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext )
+ throws IOException, RendererException
+ {
+ return locateDocumentFiles( siteRenderingContext, false );
+ }
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext,
+ boolean editable )
+ throws IOException, RendererException
+ {
+ Map files = new LinkedHashMap();
+ Map moduleExcludes = siteRenderingContext.getModuleExcludes();
+
+ // look in every site directory (in general src/site or target/generated-site)
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ if ( siteDirectory.exists() )
+ {
+ Collection modules = parserModuleManager.getParserModules();
+ // use every Doxia parser module
+ for ( ParserModule module : modules )
+ {
+ File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() );
+
+ String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
+
+ addModuleFiles( siteRenderingContext.getRootDirectory(), moduleBasedir, module, excludes, files,
+ editable );
+ }
+ }
+ }
+
+ // look in specific modules directories (used for old Maven 1.x site layout: xdoc and fml docs in /xdocs)
+ for ( ExtraDoxiaModuleReference module : siteRenderingContext.getModules() )
+ {
+ try
+ {
+ ParserModule parserModule = parserModuleManager.getParserModule( module.getParserId() );
+
+ String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
+
+ addModuleFiles( siteRenderingContext.getRootDirectory(), module.getBasedir(), parserModule, excludes,
+ files, editable );
+ }
+ catch ( ParserModuleNotFoundException e )
+ {
+ throw new RendererException( "Unable to find module: " + e.getMessage(), e );
+ }
+ }
+ return files;
+ }
+
+ private List filterExtensionIgnoreCase( List fileNames, String extension )
+ {
+ List filtered = new LinkedList( fileNames );
+ for ( Iterator it = filtered.iterator(); it.hasNext(); )
+ {
+ String name = it.next();
+
+ // Take care of extension case
+ if ( !endsWithIgnoreCase( name, extension ) )
+ {
+ it.remove();
+ }
+ }
+ return filtered;
+ }
+
+ private void addModuleFiles( File rootDir, File moduleBasedir, ParserModule module, String excludes,
+ Map files, boolean editable )
+ throws IOException, RendererException
+ {
+ if ( !moduleBasedir.exists() || ArrayUtils.isEmpty( module.getExtensions() ) )
+ {
+ return;
+ }
+
+ String moduleRelativePath =
+ PathTool.getRelativeFilePath( rootDir.getAbsolutePath(), moduleBasedir.getAbsolutePath() );
+
+ List allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", excludes, false );
+
+ for ( String extension : module.getExtensions() )
+ {
+ String fullExtension = "." + extension;
+
+ List docs = filterExtensionIgnoreCase( allFiles, fullExtension );
+
+ // *..vm
+ List velocityFiles = filterExtensionIgnoreCase( allFiles, fullExtension + ".vm" );
+
+ docs.addAll( velocityFiles );
+
+ for ( String doc : docs )
+ {
+ RenderingContext context = new RenderingContext( moduleBasedir, moduleRelativePath, doc,
+ module.getParserId(), extension, editable );
+
+ // TODO: DOXIA-111: we need a general filter here that knows how to alter the context
+ if ( endsWithIgnoreCase( doc, ".vm" ) )
+ {
+ context.setAttribute( "velocity", "true" );
+ }
+
+ String key = context.getOutputName();
+ key = StringUtils.replace( key, "\\", "/" );
+
+ if ( files.containsKey( key ) )
+ {
+ DocumentRenderer renderer = files.get( key );
+
+ RenderingContext originalContext = renderer.getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' clashes with existing '" + originalDoc + "'." );
+ }
+ // -----------------------------------------------------------------------
+ // Handle key without case differences
+ // -----------------------------------------------------------------------
+ for ( Map.Entry entry : files.entrySet() )
+ {
+ if ( entry.getKey().equalsIgnoreCase( key ) )
+ {
+ RenderingContext originalContext = entry.getValue().getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
+ {
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator
+ + doc + "' clashes with existing '" + originalDoc + "'." );
+ }
+
+ if ( getLogger().isWarnEnabled() )
+ {
+ getLogger().warn( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' could clash with existing '" + originalDoc + "'." );
+ }
+ }
+ }
+
+ files.put( key, new DoxiaDocumentRenderer( context ) );
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void render( Collection documents, SiteRenderingContext siteRenderingContext,
+ File outputDirectory )
+ throws RendererException, IOException
+ {
+ for ( DocumentRenderer docRenderer : documents )
+ {
+ RenderingContext renderingContext = docRenderer.getRenderingContext();
+
+ File outputFile = new File( outputDirectory, docRenderer.getOutputName() );
+
+ File inputFile = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
+
+ boolean modified = !outputFile.exists() || ( inputFile.lastModified() > outputFile.lastModified() )
+ || ( siteRenderingContext.getDecoration().getLastModified() > outputFile.lastModified() );
+
+ if ( modified || docRenderer.isOverwrite() )
+ {
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Generating " + outputFile );
+ }
+
+ Writer writer = null;
+ try
+ {
+ if ( !docRenderer.isExternalReport() )
+ {
+ writer = WriterFactory.newWriter( outputFile, siteRenderingContext.getOutputEncoding() );
+ }
+ docRenderer.renderDocument( writer, this, siteRenderingContext );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ else
+ {
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( inputFile + " unchanged, not regenerating..." );
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void renderDocument( Writer writer, RenderingContext docRenderingContext, SiteRenderingContext siteContext )
+ throws RendererException, FileNotFoundException, UnsupportedEncodingException
+ {
+ SiteRendererSink sink = new SiteRendererSink( docRenderingContext );
+
+ File doc = new File( docRenderingContext.getBasedir(), docRenderingContext.getInputName() );
+
+ Reader reader = null;
+ try
+ {
+ String resource = doc.getAbsolutePath();
+
+ Parser parser = doxia.getParser( docRenderingContext.getParserId() );
+ // DOXIASITETOOLS-146 don't render comments from source markup
+ parser.setEmitComments( false );
+
+ // TODO: DOXIA-111: the filter used here must be checked generally.
+ if ( docRenderingContext.getAttribute( "velocity" ) != null )
+ {
+ getLogger().debug( "Processing Velocity for " + docRenderingContext.getDoxiaSourcePath() );
+ try
+ {
+ Context vc = createDocumentVelocityContext( docRenderingContext, siteContext );
+
+ StringWriter sw = new StringWriter();
+
+ velocity.getEngine().mergeTemplate( resource, siteContext.getInputEncoding(), vc, sw );
+
+ String doxiaContent = sw.toString();
+
+ if ( siteContext.getProcessedContentOutput() != null )
+ {
+ // save Velocity processing result, ie the Doxia content that will be parsed after
+ saveVelocityProcessedContent( docRenderingContext, siteContext, doxiaContent );
+ }
+
+ reader = new StringReader( doxiaContent );
+ }
+ catch ( VelocityException e )
+ {
+ throw new RendererException( "Error parsing " + docRenderingContext.getDoxiaSourcePath()
+ + " as a Velocity template: " + e.getMessage(), e );
+ }
+
+ if ( parser.getType() == Parser.XML_TYPE && siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ }
+ else
+ {
+ switch ( parser.getType() )
+ {
+ case Parser.XML_TYPE:
+ reader = ReaderFactory.newXmlReader( doc );
+ if ( siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ break;
+
+ case Parser.TXT_TYPE:
+ case Parser.UNKNOWN_TYPE:
+ default:
+ reader = ReaderFactory.newReader( doc, siteContext.getInputEncoding() );
+ }
+ }
+ sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) );
+
+ doxia.parse( reader, docRenderingContext.getParserId(), sink );
+ }
+ catch ( ParserNotFoundException e )
+ {
+ throw new RendererException( "Error getting a parser for '" + doc + "': " + e.getMessage(), e );
+ }
+ catch ( ParseException e )
+ {
+ StringBuilder errorMsgBuilder = new StringBuilder();
+ errorMsgBuilder.append( "Error parsing '" ).append( doc ).append( "': " );
+ if ( e.getLineNumber() > 0 )
+ {
+ errorMsgBuilder.append( "line [" ).append( e.getLineNumber() ).append( "] " );
+ }
+ errorMsgBuilder.append( e.getMessage() );
+ throw new RendererException( errorMsgBuilder.toString(), e );
+ }
+ catch ( IOException e )
+ {
+ throw new RendererException( "IOException when processing '" + doc + "'", e );
+ }
+ finally
+ {
+ sink.flush();
+
+ sink.close();
+
+ IOUtil.close( reader );
+ }
+
+ mergeDocumentIntoSite( writer, (DocumentContent) sink, siteContext );
+ }
+
+ private void saveVelocityProcessedContent( RenderingContext docRenderingContext, SiteRenderingContext siteContext,
+ String doxiaContent )
+ throws IOException
+ {
+ if ( !siteContext.getProcessedContentOutput().exists() )
+ {
+ siteContext.getProcessedContentOutput().mkdirs();
+ }
+
+ String input = docRenderingContext.getInputName();
+ File outputFile = new File( siteContext.getProcessedContentOutput(),
+ input.substring( 0, input.length() - 3 ) );
+
+ File outputParent = outputFile.getParentFile();
+ if ( !outputParent.exists() )
+ {
+ outputParent.mkdirs();
+ }
+
+ FileUtils.fileWrite( outputFile, siteContext.getInputEncoding(), doxiaContent );
+ }
+
+ /**
+ * Creates a Velocity Context with all generic tools configured wit the site rendering context.
+ *
+ * @param siteRenderingContext the site rendering context
+ * @return a Velocity tools managed context
+ */
+ protected Context createToolManagedVelocityContext( SiteRenderingContext siteRenderingContext )
+ {
+ Locale locale = siteRenderingContext.getLocale();
+ String dateFormat = siteRenderingContext.getDecoration().getPublishDate().getFormat();
+
+ EasyFactoryConfiguration config = new EasyFactoryConfiguration( false );
+ config.property( "safeMode", Boolean.FALSE );
+ config.toolbox( Scope.REQUEST )
+ .tool( ContextTool.class )
+ .tool( LinkTool.class )
+ .tool( LoopTool.class )
+ .tool( RenderTool.class );
+ config.toolbox( Scope.APPLICATION ).property( "locale", locale )
+ .tool( AlternatorTool.class )
+ .tool( ClassTool.class )
+ .tool( ComparisonDateTool.class ).property( "format", dateFormat )
+ .tool( ConversionTool.class ).property( "dateFormat", dateFormat )
+ .tool( DisplayTool.class )
+ .tool( EscapeTool.class )
+ .tool( FieldTool.class )
+ .tool( MathTool.class )
+ .tool( NumberTool.class )
+ .tool( ResourceTool.class ).property( "bundles", new String[] { "site-renderer" } )
+ .tool( SortTool.class )
+ .tool( XmlTool.class );
+
+ FactoryConfiguration customConfig = ConfigurationUtils.findInClasspath( TOOLS_LOCATION );
+
+ if ( customConfig != null )
+ {
+ config.addConfiguration( customConfig );
+ }
+
+ ToolManager manager = new ToolManager( false, false );
+ manager.configure( config );
+
+ return manager.createContext();
+ }
+
+ /**
+ * Create a Velocity Context for a Doxia document, containing every information about rendered document.
+ *
+ * @param sink the site renderer sink for the document
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createDocumentVelocityContext( RenderingContext renderingContext,
+ SiteRenderingContext siteRenderingContext )
+ {
+ Context context = createToolManagedVelocityContext( siteRenderingContext );
+ // ----------------------------------------------------------------------
+ // Data objects
+ // ----------------------------------------------------------------------
+
+ context.put( "relativePath", renderingContext.getRelativePath() );
+
+ String currentFileName = renderingContext.getOutputName().replace( '\\', '/' );
+ context.put( "currentFileName", currentFileName );
+
+ context.put( "alignedFileName", PathTool.calculateLink( currentFileName, renderingContext.getRelativePath() ) );
+
+ context.put( "decoration", siteRenderingContext.getDecoration() );
+
+ Locale locale = siteRenderingContext.getLocale();
+ context.put( "locale", locale );
+ context.put( "supportedLocales", Collections.unmodifiableList( siteRenderingContext.getSiteLocales() ) );
+
+ context.put( "currentDate", new Date() );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateRevision", sdf.format( new Date() ) );
+
+ context.put( "publishDate", siteRenderingContext.getPublishDate() );
+
+ PublishDate publishDate = siteRenderingContext.getDecoration().getPublishDate();
+ DateFormat dateFormat = new SimpleDateFormat( publishDate.getFormat(), locale );
+ context.put( "dateFormat", dateFormat );
+
+ // doxiaSiteRendererVersion
+ InputStream inputStream = this.getClass().getResourceAsStream( "/META-INF/"
+ + "maven/org.apache.maven.doxia/doxia-site-renderer/pom.properties" );
+ Properties properties = PropertyUtils.loadProperties( inputStream );
+ if ( inputStream == null )
+ {
+ getLogger().debug( "pom.properties for doxia-site-renderer could not be found." );
+ }
+ else if ( properties == null )
+ {
+ getLogger().debug( "Failed to load pom.properties, so doxiaVersion is not available"
+ + " in the Velocity context." );
+ }
+ else
+ {
+ context.put( "doxiaSiteRendererVersion", properties.getProperty( "version" ) );
+ }
+
+ // Add user properties
+ Map templateProperties = siteRenderingContext.getTemplateProperties();
+
+ if ( templateProperties != null )
+ {
+ for ( Map.Entry entry : templateProperties.entrySet() )
+ {
+ context.put( entry.getKey(), entry.getValue() );
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Tools
+ // ----------------------------------------------------------------------
+
+ context.put( "PathTool", new PathTool() );
+
+ context.put( "FileUtils", new FileUtils() );
+
+ context.put( "StringUtils", new StringUtils() );
+
+ context.put( "i18n", i18n );
+
+ context.put( "plexus", plexus );
+ return context;
+ }
+
+ /**
+ * Create a Velocity Context for the site template decorating the document. In addition to all the informations
+ * from the document, this context contains data gathered in {@link SiteRendererSink} during document rendering.
+ *
+ * @param content the document content to be merged into the template
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createSiteTemplateVelocityContext( DocumentContent content,
+ SiteRenderingContext siteRenderingContext )
+ {
+ // first get the context from document
+ Context context = createDocumentVelocityContext( content.getRenderingContext(), siteRenderingContext );
+
+ // then add data objects from rendered document
+
+ // Add infos from document
+ context.put( "authors", content.getAuthors() );
+
+ context.put( "shortTitle", content.getTitle() );
+
+ // DOXIASITETOOLS-70: Prepend the project name to the title, if any
+ String title = "";
+ if ( siteRenderingContext.getDecoration() != null
+ && siteRenderingContext.getDecoration().getName() != null )
+ {
+ title = siteRenderingContext.getDecoration().getName();
+ }
+ else if ( siteRenderingContext.getDefaultWindowTitle() != null )
+ {
+ title = siteRenderingContext.getDefaultWindowTitle();
+ }
+
+ if ( title.length() > 0 )
+ {
+ title += " – "; // Symbol Name: En Dash, Html Entity: –
+ }
+ title += content.getTitle();
+
+ context.put( "title", title );
+
+ context.put( "headContent", content.getHead() );
+
+ context.put( "bodyContent", content.getBody() );
+
+ // document date (got from Doxia Sink date() API)
+ String documentDate = content.getDate();
+ if ( StringUtils.isNotEmpty( documentDate ) )
+ {
+ context.put( "documentDate", documentDate );
+
+ // deprecated variables that rework the document date, suppose one semantics over others
+ // (ie creation date, while it may be last modification date if the document writer decided so)
+ // see DOXIASITETOOLS-20 for the beginning and DOXIASITETOOLS-164 for the end of this story
+ try
+ {
+ // we support only ISO 8601 date
+ Date creationDate = new SimpleDateFormat( "yyyy-MM-dd" ).parse( documentDate );
+
+ context.put( "creationDate", creationDate );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateCreation", sdf.format( creationDate ) );
+ }
+ catch ( java.text.ParseException e )
+ {
+ getLogger().warn( "Could not parse date '" + documentDate + "' from "
+ + content.getRenderingContext().getInputName()
+ + " (expected yyyy-MM-dd format), ignoring!" );
+ }
+ }
+
+ // document rendering context, to get eventual inputName
+ context.put( "docRenderingContext", content.getRenderingContext() );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
+ throws RendererException
+ {
+ mergeDocumentIntoSite( writer, sink, siteRenderingContext );
+ }
+
+ /** {@inheritDoc} */
+/**
+ * {@inheritDoc }
+ */
+public void mergeDocumentIntoSite(java.io.Writer writer, org.apache.maven.doxia.siterenderer.DocumentContent content, org.apache.maven.doxia.siterenderer.SiteRenderingContext siteRenderingContext) throws org.apache.maven.doxia.siterenderer.RendererException {
+ java.lang.String templateName = siteRenderingContext.getTemplateName();
+ getLogger().debug((("Processing Velocity for template " + templateName) + " on ") + content.getRenderingContext().getInputName());
+ org.apache.velocity.context.Context context = createSiteTemplateVelocityContext(content, siteRenderingContext);
+ java.lang.ClassLoader old = null;
+ if (siteRenderingContext.getTemplateClassLoader() != null) {
+ // -------------------------------------------------------------------------
+ // If no template classloader was set we'll just use the context classloader
+ // -------------------------------------------------------------------------
+ old = java.lang.Thread.currentThread().getContextClassLoader();
+ java.lang.Thread.currentThread().setContextClassLoader(siteRenderingContext.getTemplateClassLoader());
+ }
+ try {
+ org.apache.velocity.Template template;
+ org.apache.maven.artifact.Artifact skin = siteRenderingContext.getSkin();
+ try {
+ org.apache.maven.doxia.site.skin.SkinModel skinModel = siteRenderingContext.getSkinModel();
+ java.lang.String encoding = (skinModel == null) ? null : skinModel.getEncoding();
+ template = velocity.getEngine().getTemplate(templateName, /* NPEX_NULL_EXP */
+ encoding);
+ } catch (org.apache.velocity.exception.ParseErrorException pee) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("Velocity parsing error while reading the site decoration template " + (skin == null ? ("'" + templateName) + "'" : ("from " + skin.getId()) + " skin"), pee);
+ } catch (org.apache.velocity.exception.ResourceNotFoundException rnfe) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("Could not find the site decoration template " + (skin == null ? ("'" + templateName) + "'" : ("from " + skin.getId()) + " skin"), rnfe);
+ }
+ try {
+ java.io.StringWriter sw = new java.io.StringWriter();
+ template.merge(context, sw);
+ writer.write(sw.toString().replaceAll("\r?\n", org.apache.commons.lang3.SystemUtils.LINE_SEPARATOR));
+ } catch (org.apache.velocity.exception.VelocityException ve) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("Velocity error while merging site decoration template.", ve);
+ } catch (java.io.IOException ioe) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("IO exception while merging site decoration template.", ioe);
+ }
+ } finally {
+ org.codehaus.plexus.util.IOUtil.close(writer);
+ if (old != null) {
+ java.lang.Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+}
+
+ private SiteRenderingContext createSiteRenderingContext( Map attributes, DecorationModel decoration,
+ String defaultWindowTitle, Locale locale )
+ {
+ SiteRenderingContext context = new SiteRenderingContext();
+
+ context.setTemplateProperties( attributes );
+ context.setLocale( locale );
+ context.setDecoration( decoration );
+ context.setDefaultWindowTitle( defaultWindowTitle );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public SiteRenderingContext createContextForSkin( Artifact skin, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws IOException, RendererException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setSkin( skin );
+
+ ZipFile zipFile = getZipFile( skin.getFile() );
+ InputStream in = null;
+
+ try
+ {
+ if ( zipFile.getEntry( SKIN_TEMPLATE_LOCATION ) != null )
+ {
+ context.setTemplateName( SKIN_TEMPLATE_LOCATION );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{skin.getFile().toURI().toURL()} ) );
+ }
+ else
+ {
+ context.setTemplateName( DEFAULT_TEMPLATE );
+ context.setTemplateClassLoader( getClass().getClassLoader() );
+ context.setUsingDefaultTemplate( true );
+ }
+
+ ZipEntry skinDescriptorEntry = zipFile.getEntry( SkinModel.SKIN_DESCRIPTOR_LOCATION );
+ if ( skinDescriptorEntry != null )
+ {
+ in = zipFile.getInputStream( skinDescriptorEntry );
+
+ SkinModel skinModel = new SkinXpp3Reader().read( in );
+ context.setSkinModel( skinModel );
+
+ String toolsPrerequisite =
+ skinModel.getPrerequisites() == null ? null : skinModel.getPrerequisites().getDoxiaSitetools();
+
+ Package p = DefaultSiteRenderer.class.getPackage();
+ String current = ( p == null ) ? null : p.getImplementationVersion();
+
+ if ( StringUtils.isNotBlank( toolsPrerequisite ) && ( current != null )
+ && !matchVersion( current, toolsPrerequisite ) )
+ {
+ throw new RendererException( "Cannot use skin: has " + toolsPrerequisite
+ + " Doxia Sitetools prerequisite, but current is " + current );
+ }
+ }
+ }
+ catch ( XmlPullParserException e )
+ {
+ throw new RendererException( "Failed to parse " + SkinModel.SKIN_DESCRIPTOR_LOCATION
+ + " skin descriptor from " + skin.getId() + " skin", e );
+ }
+ finally
+ {
+ IOUtil.close( in );
+ closeZipFile( zipFile );
+ }
+
+ return context;
+ }
+
+ boolean matchVersion( String current, String prerequisite )
+ throws RendererException
+ {
+ try
+ {
+ ArtifactVersion v = new DefaultArtifactVersion( current );
+ VersionRange vr = VersionRange.createFromVersionSpec( prerequisite );
+
+ boolean matched = false;
+ ArtifactVersion recommendedVersion = vr.getRecommendedVersion();
+ if ( recommendedVersion == null )
+ {
+ List restrictions = vr.getRestrictions();
+ for ( Restriction restriction : restrictions )
+ {
+ if ( restriction.containsVersion( v ) )
+ {
+ matched = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // only singular versions ever have a recommendedVersion
+ @SuppressWarnings( "unchecked" )
+ int compareTo = recommendedVersion.compareTo( v );
+ matched = ( compareTo <= 0 );
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Skin doxia-sitetools prerequisite: " + prerequisite + ", current: " + current
+ + ", matched = " + matched );
+ }
+
+ return matched;
+ }
+ catch ( InvalidVersionSpecificationException e )
+ {
+ throw new RendererException( "Invalid skin doxia-sitetools prerequisite: " + prerequisite, e );
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Deprecated
+ public SiteRenderingContext createContextForTemplate( File templateFile, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws MalformedURLException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setTemplateName( templateFile.getName() );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{templateFile.getParentFile().toURI().toURL()} ) );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File resourcesDirectory,
+ File outputDirectory )
+ throws IOException
+ {
+ throw new AssertionError( "copyResources( SiteRenderingContext, File, File ) is deprecated." );
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File outputDirectory )
+ throws IOException
+ {
+ if ( siteRenderingContext.getSkin() != null )
+ {
+ ZipFile file = getZipFile( siteRenderingContext.getSkin().getFile() );
+
+ try
+ {
+ for ( Enumeration extends ZipEntry> e = file.entries(); e.hasMoreElements(); )
+ {
+ ZipEntry entry = e.nextElement();
+
+ if ( !entry.getName().startsWith( "META-INF/" ) )
+ {
+ File destFile = new File( outputDirectory, entry.getName() );
+ if ( !entry.isDirectory() )
+ {
+ if ( destFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ destFile.getParentFile().mkdirs();
+
+ copyFileFromZip( file, entry, destFile );
+ }
+ else
+ {
+ destFile.mkdirs();
+ }
+ }
+ }
+ }
+ finally
+ {
+ closeZipFile( file );
+ }
+ }
+
+ if ( siteRenderingContext.isUsingDefaultTemplate() )
+ {
+ InputStream resourceList = getClass().getClassLoader()
+ .getResourceAsStream( RESOURCE_DIR + "/resources.txt" );
+
+ if ( resourceList != null )
+ {
+ Reader r = null;
+ LineNumberReader reader = null;
+ try
+ {
+ r = ReaderFactory.newReader( resourceList, ReaderFactory.UTF_8 );
+ reader = new LineNumberReader( r );
+
+ String line;
+
+ while ( ( line = reader.readLine() ) != null )
+ {
+ if ( line.startsWith( "#" ) || line.trim().length() == 0 )
+ {
+ continue;
+ }
+
+ InputStream is = getClass().getClassLoader().getResourceAsStream( RESOURCE_DIR + "/" + line );
+
+ if ( is == null )
+ {
+ throw new IOException( "The resource " + line + " doesn't exist." );
+ }
+
+ File outputFile = new File( outputDirectory, line );
+
+ if ( outputFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ OutputStream os = null;
+ try
+ {
+ // for the images
+ os = new FileOutputStream( outputFile );
+ IOUtil.copy( is, os );
+ }
+ finally
+ {
+ IOUtil.close( os );
+ }
+
+ IOUtil.close( is );
+ }
+ }
+ finally
+ {
+ IOUtil.close( reader );
+ IOUtil.close( r );
+ }
+ }
+ }
+
+ // Copy extra site resources
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ File resourcesDirectory = new File( siteDirectory, "resources" );
+
+ if ( resourcesDirectory != null && resourcesDirectory.exists() )
+ {
+ copyDirectory( resourcesDirectory, outputDirectory );
+ }
+ }
+
+ // Check for the existence of /css/site.css
+ File siteCssFile = new File( outputDirectory, "/css/site.css" );
+ if ( !siteCssFile.exists() )
+ {
+ // Create the subdirectory css if it doesn't exist, DOXIA-151
+ File cssDirectory = new File( outputDirectory, "/css/" );
+ boolean created = cssDirectory.mkdirs();
+ if ( created && getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created." );
+ }
+
+ // If the file is not there - create an empty file, DOXIA-86
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The file '" + siteCssFile.getAbsolutePath() + "' does not exist. Creating an empty file." );
+ }
+ Writer writer = null;
+ try
+ {
+ writer = WriterFactory.newWriter( siteCssFile, siteRenderingContext.getOutputEncoding() );
+ //DOXIA-290...the file should not be 0 bytes.
+ writer.write( "/* You can override this file with your own styles */" );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ }
+
+ private static void copyFileFromZip( ZipFile file, ZipEntry entry, File destFile )
+ throws IOException
+ {
+ FileOutputStream fos = new FileOutputStream( destFile );
+
+ try
+ {
+ IOUtil.copy( file.getInputStream( entry ), fos );
+ }
+ finally
+ {
+ IOUtil.close( fos );
+ }
+ }
+
+ /**
+ * Copy the directory
+ *
+ * @param source source file to be copied
+ * @param destination destination file
+ * @throws java.io.IOException if any
+ */
+ protected void copyDirectory( File source, File destination )
+ throws IOException
+ {
+ if ( source.exists() )
+ {
+ DirectoryScanner scanner = new DirectoryScanner();
+
+ String[] includedResources = {"**/**"};
+
+ scanner.setIncludes( includedResources );
+
+ scanner.addDefaultExcludes();
+
+ scanner.setBasedir( source );
+
+ scanner.scan();
+
+ List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
+
+ for ( String name : includedFiles )
+ {
+ File sourceFile = new File( source, name );
+
+ File destinationFile = new File( destination, name );
+
+ FileUtils.copyFile( sourceFile, destinationFile );
+ }
+ }
+ }
+
+ private Reader validate( Reader source, String resource )
+ throws ParseException, IOException
+ {
+ getLogger().debug( "Validating: " + resource );
+
+ try
+ {
+ String content = IOUtil.toString( new BufferedReader( source ) );
+
+ new XmlValidator( new PlexusLoggerWrapper( getLogger() ) ).validate( content );
+
+ return new StringReader( content );
+ }
+ finally
+ {
+ IOUtil.close( source );
+ }
+ }
+
+ // TODO replace with StringUtils.endsWithIgnoreCase() from maven-shared-utils 0.7
+ static boolean endsWithIgnoreCase( String str, String searchStr )
+ {
+ if ( str.length() < searchStr.length() )
+ {
+ return false;
+ }
+
+ return str.regionMatches( true, str.length() - searchStr.length(), searchStr, 0, searchStr.length() );
+ }
+
+ private static ZipFile getZipFile( File file )
+ throws IOException
+ {
+ if ( file == null )
+ {
+ throw new IOException( "Error opening ZipFile: null" );
+ }
+
+ try
+ {
+ // TODO: plexus-archiver, if it could do the excludes
+ return new ZipFile( file );
+ }
+ catch ( ZipException ex )
+ {
+ IOException ioe = new IOException( "Error opening ZipFile: " + file.getAbsolutePath() );
+ ioe.initCause( ex );
+ throw ioe;
+ }
+ }
+
+ private static void closeZipFile( ZipFile zipFile )
+ {
+ // TODO: move to plexus utils
+ try
+ {
+ zipFile.close();
+ }
+ catch ( IOException e )
+ {
+ // ignore
+ }
+ }
+}
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_738/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_738/npe.json
new file mode 100644
index 000000000..c87c1ad1a
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_738/npe.json
@@ -0,0 +1,7 @@
+{
+ "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java",
+ "line": 726,
+ "npe_method": "mergeDocumentIntoSite",
+ "deref_field": "encoding",
+ "npe_class": "DefaultSiteRenderer"
+}
\ No newline at end of file
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_773/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_773/buggy.java
new file mode 100644
index 000000000..0515ad48f
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_773/buggy.java
@@ -0,0 +1,1161 @@
+package org.apache.maven.doxia.siterenderer;
+
+/*
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+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 java.util.Properties;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.Restriction;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.doxia.Doxia;
+import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
+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.site.decoration.DecorationModel;
+import org.apache.maven.doxia.site.decoration.PublishDate;
+import org.apache.maven.doxia.site.skin.SkinModel;
+import org.apache.maven.doxia.site.skin.io.xpp3.SkinXpp3Reader;
+import org.apache.maven.doxia.parser.module.ParserModule;
+import org.apache.maven.doxia.parser.module.ParserModuleManager;
+import org.apache.maven.doxia.parser.module.ParserModuleNotFoundException;
+import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
+import org.apache.maven.doxia.util.XmlValidator;
+import org.apache.velocity.Template;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.Scope;
+import org.apache.velocity.tools.ToolManager;
+import org.apache.velocity.tools.config.ConfigurationUtils;
+import org.apache.velocity.tools.config.EasyFactoryConfiguration;
+import org.apache.velocity.tools.config.FactoryConfiguration;
+import org.apache.velocity.tools.generic.AlternatorTool;
+import org.apache.velocity.tools.generic.ClassTool;
+import org.apache.velocity.tools.generic.ComparisonDateTool;
+import org.apache.velocity.tools.generic.ContextTool;
+import org.apache.velocity.tools.generic.ConversionTool;
+import org.apache.velocity.tools.generic.DisplayTool;
+import org.apache.velocity.tools.generic.EscapeTool;
+import org.apache.velocity.tools.generic.FieldTool;
+import org.apache.velocity.tools.generic.LinkTool;
+import org.apache.velocity.tools.generic.LoopTool;
+import org.apache.velocity.tools.generic.MathTool;
+import org.apache.velocity.tools.generic.NumberTool;
+import org.apache.velocity.tools.generic.RenderTool;
+import org.apache.velocity.tools.generic.ResourceTool;
+import org.apache.velocity.tools.generic.SortTool;
+import org.apache.velocity.tools.generic.XmlTool;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.i18n.I18N;
+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.Os;
+import org.codehaus.plexus.util.PathTool;
+import org.codehaus.plexus.util.PropertyUtils;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.WriterFactory;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.codehaus.plexus.velocity.VelocityComponent;
+
+/**
+ * DefaultSiteRenderer class.
+ *
+ * @author Emmanuel Venisse
+ * @author Vincent Siveton
+ * @since 1.0
+ */
+@Component( role = Renderer.class )
+public class DefaultSiteRenderer
+ extends AbstractLogEnabled
+ implements Renderer
+{
+ // ----------------------------------------------------------------------
+ // Requirements
+ // ----------------------------------------------------------------------
+
+ @Requirement
+ private VelocityComponent velocity;
+
+ @Requirement
+ private ParserModuleManager parserModuleManager;
+
+ @Requirement
+ private Doxia doxia;
+
+ @Requirement
+ private I18N i18n;
+
+ @Requirement
+ private PlexusContainer plexus;
+
+ private static final String RESOURCE_DIR = "org/apache/maven/doxia/siterenderer/resources";
+
+ private static final String DEFAULT_TEMPLATE = RESOURCE_DIR + "/default-site.vm";
+
+ private static final String SKIN_TEMPLATE_LOCATION = "META-INF/maven/site.vm";
+
+ private static final String TOOLS_LOCATION = "META-INF/maven/site-tools.xml";
+
+ // ----------------------------------------------------------------------
+ // Renderer implementation
+ // ----------------------------------------------------------------------
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext )
+ throws IOException, RendererException
+ {
+ return locateDocumentFiles( siteRenderingContext, false );
+ }
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext,
+ boolean editable )
+ throws IOException, RendererException
+ {
+ Map files = new LinkedHashMap();
+ Map moduleExcludes = siteRenderingContext.getModuleExcludes();
+
+ // look in every site directory (in general src/site or target/generated-site)
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ if ( siteDirectory.exists() )
+ {
+ Collection modules = parserModuleManager.getParserModules();
+ // use every Doxia parser module
+ for ( ParserModule module : modules )
+ {
+ File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() );
+
+ String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
+
+ addModuleFiles( siteRenderingContext.getRootDirectory(), moduleBasedir, module, excludes, files,
+ editable );
+ }
+ }
+ }
+
+ // look in specific modules directories (used for old Maven 1.x site layout: xdoc and fml docs in /xdocs)
+ for ( ExtraDoxiaModuleReference module : siteRenderingContext.getModules() )
+ {
+ try
+ {
+ ParserModule parserModule = parserModuleManager.getParserModule( module.getParserId() );
+
+ String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
+
+ addModuleFiles( siteRenderingContext.getRootDirectory(), module.getBasedir(), parserModule, excludes,
+ files, editable );
+ }
+ catch ( ParserModuleNotFoundException e )
+ {
+ throw new RendererException( "Unable to find module: " + e.getMessage(), e );
+ }
+ }
+ return files;
+ }
+
+ private List filterExtensionIgnoreCase( List fileNames, String extension )
+ {
+ List filtered = new LinkedList( fileNames );
+ for ( Iterator it = filtered.iterator(); it.hasNext(); )
+ {
+ String name = it.next();
+
+ // Take care of extension case
+ if ( !endsWithIgnoreCase( name, extension ) )
+ {
+ it.remove();
+ }
+ }
+ return filtered;
+ }
+
+ private void addModuleFiles( File rootDir, File moduleBasedir, ParserModule module, String excludes,
+ Map files, boolean editable )
+ throws IOException, RendererException
+ {
+ if ( !moduleBasedir.exists() || ArrayUtils.isEmpty( module.getExtensions() ) )
+ {
+ return;
+ }
+
+ String moduleRelativePath =
+ PathTool.getRelativeFilePath( rootDir.getAbsolutePath(), moduleBasedir.getAbsolutePath() );
+
+ List allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", excludes, false );
+
+ for ( String extension : module.getExtensions() )
+ {
+ String fullExtension = "." + extension;
+
+ List docs = filterExtensionIgnoreCase( allFiles, fullExtension );
+
+ // *..vm
+ List velocityFiles = filterExtensionIgnoreCase( allFiles, fullExtension + ".vm" );
+
+ docs.addAll( velocityFiles );
+
+ for ( String doc : docs )
+ {
+ RenderingContext context = new RenderingContext( moduleBasedir, moduleRelativePath, doc,
+ module.getParserId(), extension, editable );
+
+ // TODO: DOXIA-111: we need a general filter here that knows how to alter the context
+ if ( endsWithIgnoreCase( doc, ".vm" ) )
+ {
+ context.setAttribute( "velocity", "true" );
+ }
+
+ String key = context.getOutputName();
+ key = StringUtils.replace( key, "\\", "/" );
+
+ if ( files.containsKey( key ) )
+ {
+ DocumentRenderer renderer = files.get( key );
+
+ RenderingContext originalContext = renderer.getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' clashes with existing '" + originalDoc + "'." );
+ }
+ // -----------------------------------------------------------------------
+ // Handle key without case differences
+ // -----------------------------------------------------------------------
+ for ( Map.Entry entry : files.entrySet() )
+ {
+ if ( entry.getKey().equalsIgnoreCase( key ) )
+ {
+ RenderingContext originalContext = entry.getValue().getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
+ {
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator
+ + doc + "' clashes with existing '" + originalDoc + "'." );
+ }
+
+ if ( getLogger().isWarnEnabled() )
+ {
+ getLogger().warn( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' could clash with existing '" + originalDoc + "'." );
+ }
+ }
+ }
+
+ files.put( key, new DoxiaDocumentRenderer( context ) );
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void render( Collection documents, SiteRenderingContext siteRenderingContext,
+ File outputDirectory )
+ throws RendererException, IOException
+ {
+ for ( DocumentRenderer docRenderer : documents )
+ {
+ RenderingContext renderingContext = docRenderer.getRenderingContext();
+
+ File outputFile = new File( outputDirectory, docRenderer.getOutputName() );
+
+ File inputFile = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
+
+ boolean modified = !outputFile.exists() || ( inputFile.lastModified() > outputFile.lastModified() )
+ || ( siteRenderingContext.getDecoration().getLastModified() > outputFile.lastModified() );
+
+ if ( modified || docRenderer.isOverwrite() )
+ {
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Generating " + outputFile );
+ }
+
+ Writer writer = null;
+ try
+ {
+ if ( !docRenderer.isExternalReport() )
+ {
+ writer = WriterFactory.newWriter( outputFile, siteRenderingContext.getOutputEncoding() );
+ }
+ docRenderer.renderDocument( writer, this, siteRenderingContext );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ else
+ {
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( inputFile + " unchanged, not regenerating..." );
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void renderDocument( Writer writer, RenderingContext docRenderingContext, SiteRenderingContext siteContext )
+ throws RendererException, FileNotFoundException, UnsupportedEncodingException
+ {
+ SiteRendererSink sink = new SiteRendererSink( docRenderingContext );
+
+ File doc = new File( docRenderingContext.getBasedir(), docRenderingContext.getInputName() );
+
+ Reader reader = null;
+ try
+ {
+ String resource = doc.getAbsolutePath();
+
+ Parser parser = doxia.getParser( docRenderingContext.getParserId() );
+ // DOXIASITETOOLS-146 don't render comments from source markup
+ parser.setEmitComments( false );
+
+ // TODO: DOXIA-111: the filter used here must be checked generally.
+ if ( docRenderingContext.getAttribute( "velocity" ) != null )
+ {
+ getLogger().debug( "Processing Velocity for " + docRenderingContext.getDoxiaSourcePath() );
+ try
+ {
+ Context vc = createDocumentVelocityContext( docRenderingContext, siteContext );
+
+ StringWriter sw = new StringWriter();
+
+ velocity.getEngine().mergeTemplate( resource, siteContext.getInputEncoding(), vc, sw );
+
+ String doxiaContent = sw.toString();
+
+ if ( siteContext.getProcessedContentOutput() != null )
+ {
+ // save Velocity processing result, ie the Doxia content that will be parsed after
+ saveVelocityProcessedContent( docRenderingContext, siteContext, doxiaContent );
+ }
+
+ reader = new StringReader( doxiaContent );
+ }
+ catch ( VelocityException e )
+ {
+ throw new RendererException( "Error parsing " + docRenderingContext.getDoxiaSourcePath()
+ + " as a Velocity template: " + e.getMessage(), e );
+ }
+
+ if ( parser.getType() == Parser.XML_TYPE && siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ }
+ else
+ {
+ switch ( parser.getType() )
+ {
+ case Parser.XML_TYPE:
+ reader = ReaderFactory.newXmlReader( doc );
+ if ( siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ break;
+
+ case Parser.TXT_TYPE:
+ case Parser.UNKNOWN_TYPE:
+ default:
+ reader = ReaderFactory.newReader( doc, siteContext.getInputEncoding() );
+ }
+ }
+ sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) );
+
+ doxia.parse( reader, docRenderingContext.getParserId(), sink );
+ }
+ catch ( ParserNotFoundException e )
+ {
+ throw new RendererException( "Error getting a parser for '" + doc + "': " + e.getMessage(), e );
+ }
+ catch ( ParseException e )
+ {
+ StringBuilder errorMsgBuilder = new StringBuilder();
+ errorMsgBuilder.append( "Error parsing '" ).append( doc ).append( "': " );
+ if ( e.getLineNumber() > 0 )
+ {
+ errorMsgBuilder.append( "line [" ).append( e.getLineNumber() ).append( "] " );
+ }
+ errorMsgBuilder.append( e.getMessage() );
+ throw new RendererException( errorMsgBuilder.toString(), e );
+ }
+ catch ( IOException e )
+ {
+ throw new RendererException( "IOException when processing '" + doc + "'", e );
+ }
+ finally
+ {
+ sink.flush();
+
+ sink.close();
+
+ IOUtil.close( reader );
+ }
+
+ mergeDocumentIntoSite( writer, (DocumentContent) sink, siteContext );
+ }
+
+ private void saveVelocityProcessedContent( RenderingContext docRenderingContext, SiteRenderingContext siteContext,
+ String doxiaContent )
+ throws IOException
+ {
+ if ( !siteContext.getProcessedContentOutput().exists() )
+ {
+ siteContext.getProcessedContentOutput().mkdirs();
+ }
+
+ String input = docRenderingContext.getInputName();
+ File outputFile = new File( siteContext.getProcessedContentOutput(),
+ input.substring( 0, input.length() - 3 ) );
+
+ File outputParent = outputFile.getParentFile();
+ if ( !outputParent.exists() )
+ {
+ outputParent.mkdirs();
+ }
+
+ FileUtils.fileWrite( outputFile, siteContext.getInputEncoding(), doxiaContent );
+ }
+
+ /**
+ * Creates a Velocity Context with all generic tools configured wit the site rendering context.
+ *
+ * @param siteRenderingContext the site rendering context
+ * @return a Velocity tools managed context
+ */
+ protected Context createToolManagedVelocityContext( SiteRenderingContext siteRenderingContext )
+ {
+ Locale locale = siteRenderingContext.getLocale();
+ String dateFormat = siteRenderingContext.getDecoration().getPublishDate().getFormat();
+
+ EasyFactoryConfiguration config = new EasyFactoryConfiguration( false );
+ config.property( "safeMode", Boolean.FALSE );
+ config.toolbox( Scope.REQUEST )
+ .tool( ContextTool.class )
+ .tool( LinkTool.class )
+ .tool( LoopTool.class )
+ .tool( RenderTool.class );
+ config.toolbox( Scope.APPLICATION ).property( "locale", locale )
+ .tool( AlternatorTool.class )
+ .tool( ClassTool.class )
+ .tool( ComparisonDateTool.class ).property( "format", dateFormat )
+ .tool( ConversionTool.class ).property( "dateFormat", dateFormat )
+ .tool( DisplayTool.class )
+ .tool( EscapeTool.class )
+ .tool( FieldTool.class )
+ .tool( MathTool.class )
+ .tool( NumberTool.class )
+ .tool( ResourceTool.class ).property( "bundles", new String[] { "site-renderer" } )
+ .tool( SortTool.class )
+ .tool( XmlTool.class );
+
+ FactoryConfiguration customConfig = ConfigurationUtils.findInClasspath( TOOLS_LOCATION );
+
+ if ( customConfig != null )
+ {
+ config.addConfiguration( customConfig );
+ }
+
+ ToolManager manager = new ToolManager( false, false );
+ manager.configure( config );
+
+ return manager.createContext();
+ }
+
+ /**
+ * Create a Velocity Context for a Doxia document, containing every information about rendered document.
+ *
+ * @param sink the site renderer sink for the document
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createDocumentVelocityContext( RenderingContext renderingContext,
+ SiteRenderingContext siteRenderingContext )
+ {
+ Context context = createToolManagedVelocityContext( siteRenderingContext );
+ // ----------------------------------------------------------------------
+ // Data objects
+ // ----------------------------------------------------------------------
+
+ context.put( "relativePath", renderingContext.getRelativePath() );
+
+ String currentFileName = renderingContext.getOutputName().replace( '\\', '/' );
+ context.put( "currentFileName", currentFileName );
+
+ context.put( "alignedFileName", PathTool.calculateLink( currentFileName, renderingContext.getRelativePath() ) );
+
+ context.put( "decoration", siteRenderingContext.getDecoration() );
+
+ Locale locale = siteRenderingContext.getLocale();
+ context.put( "locale", locale );
+ context.put( "supportedLocales", Collections.unmodifiableList( siteRenderingContext.getSiteLocales() ) );
+
+ context.put( "currentDate", new Date() );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateRevision", sdf.format( new Date() ) );
+
+ context.put( "publishDate", siteRenderingContext.getPublishDate() );
+
+ PublishDate publishDate = siteRenderingContext.getDecoration().getPublishDate();
+ DateFormat dateFormat = new SimpleDateFormat( publishDate.getFormat(), locale );
+ context.put( "dateFormat", dateFormat );
+
+ // doxiaSiteRendererVersion
+ InputStream inputStream = this.getClass().getResourceAsStream( "/META-INF/"
+ + "maven/org.apache.maven.doxia/doxia-site-renderer/pom.properties" );
+ Properties properties = PropertyUtils.loadProperties( inputStream );
+ if ( inputStream == null )
+ {
+ getLogger().debug( "pom.properties for doxia-site-renderer could not be found." );
+ }
+ else if ( properties == null )
+ {
+ getLogger().debug( "Failed to load pom.properties, so doxiaVersion is not available"
+ + " in the Velocity context." );
+ }
+ else
+ {
+ context.put( "doxiaSiteRendererVersion", properties.getProperty( "version" ) );
+ }
+
+ // Add user properties
+ Map templateProperties = siteRenderingContext.getTemplateProperties();
+
+ if ( templateProperties != null )
+ {
+ for ( Map.Entry entry : templateProperties.entrySet() )
+ {
+ context.put( entry.getKey(), entry.getValue() );
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Tools
+ // ----------------------------------------------------------------------
+
+ context.put( "PathTool", new PathTool() );
+
+ context.put( "FileUtils", new FileUtils() );
+
+ context.put( "StringUtils", new StringUtils() );
+
+ context.put( "i18n", i18n );
+
+ context.put( "plexus", plexus );
+ return context;
+ }
+
+ /**
+ * Create a Velocity Context for the site template decorating the document. In addition to all the informations
+ * from the document, this context contains data gathered in {@link SiteRendererSink} during document rendering.
+ *
+ * @param content the document content to be merged into the template
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createSiteTemplateVelocityContext( DocumentContent content,
+ SiteRenderingContext siteRenderingContext )
+ {
+ // first get the context from document
+ Context context = createDocumentVelocityContext( content.getRenderingContext(), siteRenderingContext );
+
+ // then add data objects from rendered document
+
+ // Add infos from document
+ context.put( "authors", content.getAuthors() );
+
+ context.put( "shortTitle", content.getTitle() );
+
+ // DOXIASITETOOLS-70: Prepend the project name to the title, if any
+ String title = "";
+ if ( siteRenderingContext.getDecoration() != null
+ && siteRenderingContext.getDecoration().getName() != null )
+ {
+ title = siteRenderingContext.getDecoration().getName();
+ }
+ else if ( siteRenderingContext.getDefaultWindowTitle() != null )
+ {
+ title = siteRenderingContext.getDefaultWindowTitle();
+ }
+
+ if ( title.length() > 0 )
+ {
+ title += " – "; // Symbol Name: En Dash, Html Entity: –
+ }
+ title += content.getTitle();
+
+ context.put( "title", title );
+
+ context.put( "headContent", content.getHead() );
+
+ context.put( "bodyContent", content.getBody() );
+
+ // document date (got from Doxia Sink date() API)
+ String documentDate = content.getDate();
+ if ( StringUtils.isNotEmpty( documentDate ) )
+ {
+ context.put( "documentDate", documentDate );
+
+ // deprecated variables that rework the document date, suppose one semantics over others
+ // (ie creation date, while it may be last modification date if the document writer decided so)
+ // see DOXIASITETOOLS-20 for the beginning and DOXIASITETOOLS-164 for the end of this story
+ try
+ {
+ // we support only ISO 8601 date
+ Date creationDate = new SimpleDateFormat( "yyyy-MM-dd" ).parse( documentDate );
+
+ context.put( "creationDate", creationDate );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateCreation", sdf.format( creationDate ) );
+ }
+ catch ( java.text.ParseException e )
+ {
+ getLogger().warn( "Could not parse date '" + documentDate + "' from "
+ + content.getRenderingContext().getInputName()
+ + " (expected yyyy-MM-dd format), ignoring!" );
+ }
+ }
+
+ // document rendering context, to get eventual inputName
+ context.put( "docRenderingContext", content.getRenderingContext() );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
+ throws RendererException
+ {
+ mergeDocumentIntoSite( writer, sink, siteRenderingContext );
+ }
+
+ /** {@inheritDoc} */
+/**
+ * {@inheritDoc }
+ */
+public void mergeDocumentIntoSite(java.io.Writer writer, org.apache.maven.doxia.siterenderer.DocumentContent content, org.apache.maven.doxia.siterenderer.SiteRenderingContext siteRenderingContext) throws org.apache.maven.doxia.siterenderer.RendererException {
+ java.lang.String templateName = siteRenderingContext.getTemplateName();
+ getLogger().debug((("Processing Velocity for template " + templateName) + " on ") + content.getRenderingContext().getInputName());
+ org.apache.velocity.context.Context context = createSiteTemplateVelocityContext(content, siteRenderingContext);
+ java.lang.ClassLoader old = null;
+ if (siteRenderingContext.getTemplateClassLoader() != null) {
+ // -------------------------------------------------------------------------
+ // If no template classloader was set we'll just use the context classloader
+ // -------------------------------------------------------------------------
+ old = java.lang.Thread.currentThread().getContextClassLoader();
+ java.lang.Thread.currentThread().setContextClassLoader(siteRenderingContext.getTemplateClassLoader());
+ }
+ try {
+ org.apache.velocity.Template template;
+ org.apache.maven.artifact.Artifact skin = siteRenderingContext.getSkin();
+ try {
+ org.apache.maven.doxia.site.skin.SkinModel skinModel = siteRenderingContext.getSkinModel();
+ java.lang.String encoding = (skinModel == null) ? null : skinModel.getEncoding();
+ template = (encoding == null) ? velocity.getEngine().getTemplate(templateName) : velocity.getEngine().getTemplate(templateName, encoding);
+ } catch (org.apache.velocity.exception.ParseErrorException pee) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("Velocity parsing error while reading the site decoration template " + (skin == null ? ("'" + templateName) + "'" : ("from " + skin.getId()) + " skin"), pee);
+ } catch (org.apache.velocity.exception.ResourceNotFoundException rnfe) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("Could not find the site decoration template " + (skin == null ? ("'" + templateName) + "'" : ("from " + skin.getId()) + " skin"), rnfe);
+ }
+ try {
+ java.io.StringWriter sw = new java.io.StringWriter();
+ template.merge(context, sw);
+ writer.write(sw.toString().replaceAll("\r?\n", org.apache.commons.lang3.SystemUtils.LINE_SEPARATOR));
+ } catch (org.apache.velocity.exception.VelocityException ve) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("Velocity error while merging site decoration template.", ve);
+ } catch (java.io.IOException ioe) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException("IO exception while merging site decoration template.", ioe);
+ }
+ } finally {
+ org.codehaus.plexus.util.IOUtil.close(writer);
+ {
+ java.lang.Thread.currentThread().setContextClassLoader(/* NPEX_NULL_EXP */
+ old);
+ }
+ }
+}
+
+ private SiteRenderingContext createSiteRenderingContext( Map attributes, DecorationModel decoration,
+ String defaultWindowTitle, Locale locale )
+ {
+ SiteRenderingContext context = new SiteRenderingContext();
+
+ context.setTemplateProperties( attributes );
+ context.setLocale( locale );
+ context.setDecoration( decoration );
+ context.setDefaultWindowTitle( defaultWindowTitle );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public SiteRenderingContext createContextForSkin( Artifact skin, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws IOException, RendererException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setSkin( skin );
+
+ ZipFile zipFile = getZipFile( skin.getFile() );
+ InputStream in = null;
+
+ try
+ {
+ if ( zipFile.getEntry( SKIN_TEMPLATE_LOCATION ) != null )
+ {
+ context.setTemplateName( SKIN_TEMPLATE_LOCATION );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{skin.getFile().toURI().toURL()} ) );
+ }
+ else
+ {
+ context.setTemplateName( DEFAULT_TEMPLATE );
+ context.setTemplateClassLoader( getClass().getClassLoader() );
+ context.setUsingDefaultTemplate( true );
+ }
+
+ ZipEntry skinDescriptorEntry = zipFile.getEntry( SkinModel.SKIN_DESCRIPTOR_LOCATION );
+ if ( skinDescriptorEntry != null )
+ {
+ in = zipFile.getInputStream( skinDescriptorEntry );
+
+ SkinModel skinModel = new SkinXpp3Reader().read( in );
+ context.setSkinModel( skinModel );
+
+ String toolsPrerequisite =
+ skinModel.getPrerequisites() == null ? null : skinModel.getPrerequisites().getDoxiaSitetools();
+
+ Package p = DefaultSiteRenderer.class.getPackage();
+ String current = ( p == null ) ? null : p.getImplementationVersion();
+
+ if ( StringUtils.isNotBlank( toolsPrerequisite ) && ( current != null )
+ && !matchVersion( current, toolsPrerequisite ) )
+ {
+ throw new RendererException( "Cannot use skin: has " + toolsPrerequisite
+ + " Doxia Sitetools prerequisite, but current is " + current );
+ }
+ }
+ }
+ catch ( XmlPullParserException e )
+ {
+ throw new RendererException( "Failed to parse " + SkinModel.SKIN_DESCRIPTOR_LOCATION
+ + " skin descriptor from " + skin.getId() + " skin", e );
+ }
+ finally
+ {
+ IOUtil.close( in );
+ closeZipFile( zipFile );
+ }
+
+ return context;
+ }
+
+ boolean matchVersion( String current, String prerequisite )
+ throws RendererException
+ {
+ try
+ {
+ ArtifactVersion v = new DefaultArtifactVersion( current );
+ VersionRange vr = VersionRange.createFromVersionSpec( prerequisite );
+
+ boolean matched = false;
+ ArtifactVersion recommendedVersion = vr.getRecommendedVersion();
+ if ( recommendedVersion == null )
+ {
+ List restrictions = vr.getRestrictions();
+ for ( Restriction restriction : restrictions )
+ {
+ if ( restriction.containsVersion( v ) )
+ {
+ matched = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // only singular versions ever have a recommendedVersion
+ @SuppressWarnings( "unchecked" )
+ int compareTo = recommendedVersion.compareTo( v );
+ matched = ( compareTo <= 0 );
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Skin doxia-sitetools prerequisite: " + prerequisite + ", current: " + current
+ + ", matched = " + matched );
+ }
+
+ return matched;
+ }
+ catch ( InvalidVersionSpecificationException e )
+ {
+ throw new RendererException( "Invalid skin doxia-sitetools prerequisite: " + prerequisite, e );
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Deprecated
+ public SiteRenderingContext createContextForTemplate( File templateFile, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws MalformedURLException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setTemplateName( templateFile.getName() );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{templateFile.getParentFile().toURI().toURL()} ) );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File resourcesDirectory,
+ File outputDirectory )
+ throws IOException
+ {
+ throw new AssertionError( "copyResources( SiteRenderingContext, File, File ) is deprecated." );
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File outputDirectory )
+ throws IOException
+ {
+ if ( siteRenderingContext.getSkin() != null )
+ {
+ ZipFile file = getZipFile( siteRenderingContext.getSkin().getFile() );
+
+ try
+ {
+ for ( Enumeration extends ZipEntry> e = file.entries(); e.hasMoreElements(); )
+ {
+ ZipEntry entry = e.nextElement();
+
+ if ( !entry.getName().startsWith( "META-INF/" ) )
+ {
+ File destFile = new File( outputDirectory, entry.getName() );
+ if ( !entry.isDirectory() )
+ {
+ if ( destFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ destFile.getParentFile().mkdirs();
+
+ copyFileFromZip( file, entry, destFile );
+ }
+ else
+ {
+ destFile.mkdirs();
+ }
+ }
+ }
+ }
+ finally
+ {
+ closeZipFile( file );
+ }
+ }
+
+ if ( siteRenderingContext.isUsingDefaultTemplate() )
+ {
+ InputStream resourceList = getClass().getClassLoader()
+ .getResourceAsStream( RESOURCE_DIR + "/resources.txt" );
+
+ if ( resourceList != null )
+ {
+ Reader r = null;
+ LineNumberReader reader = null;
+ try
+ {
+ r = ReaderFactory.newReader( resourceList, ReaderFactory.UTF_8 );
+ reader = new LineNumberReader( r );
+
+ String line;
+
+ while ( ( line = reader.readLine() ) != null )
+ {
+ if ( line.startsWith( "#" ) || line.trim().length() == 0 )
+ {
+ continue;
+ }
+
+ InputStream is = getClass().getClassLoader().getResourceAsStream( RESOURCE_DIR + "/" + line );
+
+ if ( is == null )
+ {
+ throw new IOException( "The resource " + line + " doesn't exist." );
+ }
+
+ File outputFile = new File( outputDirectory, line );
+
+ if ( outputFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ OutputStream os = null;
+ try
+ {
+ // for the images
+ os = new FileOutputStream( outputFile );
+ IOUtil.copy( is, os );
+ }
+ finally
+ {
+ IOUtil.close( os );
+ }
+
+ IOUtil.close( is );
+ }
+ }
+ finally
+ {
+ IOUtil.close( reader );
+ IOUtil.close( r );
+ }
+ }
+ }
+
+ // Copy extra site resources
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ File resourcesDirectory = new File( siteDirectory, "resources" );
+
+ if ( resourcesDirectory != null && resourcesDirectory.exists() )
+ {
+ copyDirectory( resourcesDirectory, outputDirectory );
+ }
+ }
+
+ // Check for the existence of /css/site.css
+ File siteCssFile = new File( outputDirectory, "/css/site.css" );
+ if ( !siteCssFile.exists() )
+ {
+ // Create the subdirectory css if it doesn't exist, DOXIA-151
+ File cssDirectory = new File( outputDirectory, "/css/" );
+ boolean created = cssDirectory.mkdirs();
+ if ( created && getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created." );
+ }
+
+ // If the file is not there - create an empty file, DOXIA-86
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The file '" + siteCssFile.getAbsolutePath() + "' does not exist. Creating an empty file." );
+ }
+ Writer writer = null;
+ try
+ {
+ writer = WriterFactory.newWriter( siteCssFile, siteRenderingContext.getOutputEncoding() );
+ //DOXIA-290...the file should not be 0 bytes.
+ writer.write( "/* You can override this file with your own styles */" );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ }
+
+ private static void copyFileFromZip( ZipFile file, ZipEntry entry, File destFile )
+ throws IOException
+ {
+ FileOutputStream fos = new FileOutputStream( destFile );
+
+ try
+ {
+ IOUtil.copy( file.getInputStream( entry ), fos );
+ }
+ finally
+ {
+ IOUtil.close( fos );
+ }
+ }
+
+ /**
+ * Copy the directory
+ *
+ * @param source source file to be copied
+ * @param destination destination file
+ * @throws java.io.IOException if any
+ */
+ protected void copyDirectory( File source, File destination )
+ throws IOException
+ {
+ if ( source.exists() )
+ {
+ DirectoryScanner scanner = new DirectoryScanner();
+
+ String[] includedResources = {"**/**"};
+
+ scanner.setIncludes( includedResources );
+
+ scanner.addDefaultExcludes();
+
+ scanner.setBasedir( source );
+
+ scanner.scan();
+
+ List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
+
+ for ( String name : includedFiles )
+ {
+ File sourceFile = new File( source, name );
+
+ File destinationFile = new File( destination, name );
+
+ FileUtils.copyFile( sourceFile, destinationFile );
+ }
+ }
+ }
+
+ private Reader validate( Reader source, String resource )
+ throws ParseException, IOException
+ {
+ getLogger().debug( "Validating: " + resource );
+
+ try
+ {
+ String content = IOUtil.toString( new BufferedReader( source ) );
+
+ new XmlValidator( new PlexusLoggerWrapper( getLogger() ) ).validate( content );
+
+ return new StringReader( content );
+ }
+ finally
+ {
+ IOUtil.close( source );
+ }
+ }
+
+ // TODO replace with StringUtils.endsWithIgnoreCase() from maven-shared-utils 0.7
+ static boolean endsWithIgnoreCase( String str, String searchStr )
+ {
+ if ( str.length() < searchStr.length() )
+ {
+ return false;
+ }
+
+ return str.regionMatches( true, str.length() - searchStr.length(), searchStr, 0, searchStr.length() );
+ }
+
+ private static ZipFile getZipFile( File file )
+ throws IOException
+ {
+ if ( file == null )
+ {
+ throw new IOException( "Error opening ZipFile: null" );
+ }
+
+ try
+ {
+ // TODO: plexus-archiver, if it could do the excludes
+ return new ZipFile( file );
+ }
+ catch ( ZipException ex )
+ {
+ IOException ioe = new IOException( "Error opening ZipFile: " + file.getAbsolutePath() );
+ ioe.initCause( ex );
+ throw ioe;
+ }
+ }
+
+ private static void closeZipFile( ZipFile zipFile )
+ {
+ // TODO: move to plexus utils
+ try
+ {
+ zipFile.close();
+ }
+ catch ( IOException e )
+ {
+ // ignore
+ }
+ }
+}
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_773/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_773/npe.json
new file mode 100644
index 000000000..55be19965
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_773/npe.json
@@ -0,0 +1,7 @@
+{
+ "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java",
+ "line": 744,
+ "npe_method": "mergeDocumentIntoSite",
+ "deref_field": "old",
+ "npe_class": "DefaultSiteRenderer"
+}
\ No newline at end of file
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_821/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_821/buggy.java
new file mode 100644
index 000000000..c771b6653
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_821/buggy.java
@@ -0,0 +1,1169 @@
+package org.apache.maven.doxia.siterenderer;
+
+/*
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+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 java.util.Properties;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.Restriction;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.doxia.Doxia;
+import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
+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.site.decoration.DecorationModel;
+import org.apache.maven.doxia.site.decoration.PublishDate;
+import org.apache.maven.doxia.site.skin.SkinModel;
+import org.apache.maven.doxia.site.skin.io.xpp3.SkinXpp3Reader;
+import org.apache.maven.doxia.parser.module.ParserModule;
+import org.apache.maven.doxia.parser.module.ParserModuleManager;
+import org.apache.maven.doxia.parser.module.ParserModuleNotFoundException;
+import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
+import org.apache.maven.doxia.util.XmlValidator;
+import org.apache.velocity.Template;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.Scope;
+import org.apache.velocity.tools.ToolManager;
+import org.apache.velocity.tools.config.ConfigurationUtils;
+import org.apache.velocity.tools.config.EasyFactoryConfiguration;
+import org.apache.velocity.tools.config.FactoryConfiguration;
+import org.apache.velocity.tools.generic.AlternatorTool;
+import org.apache.velocity.tools.generic.ClassTool;
+import org.apache.velocity.tools.generic.ComparisonDateTool;
+import org.apache.velocity.tools.generic.ContextTool;
+import org.apache.velocity.tools.generic.ConversionTool;
+import org.apache.velocity.tools.generic.DisplayTool;
+import org.apache.velocity.tools.generic.EscapeTool;
+import org.apache.velocity.tools.generic.FieldTool;
+import org.apache.velocity.tools.generic.LinkTool;
+import org.apache.velocity.tools.generic.LoopTool;
+import org.apache.velocity.tools.generic.MathTool;
+import org.apache.velocity.tools.generic.NumberTool;
+import org.apache.velocity.tools.generic.RenderTool;
+import org.apache.velocity.tools.generic.ResourceTool;
+import org.apache.velocity.tools.generic.SortTool;
+import org.apache.velocity.tools.generic.XmlTool;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.i18n.I18N;
+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.Os;
+import org.codehaus.plexus.util.PathTool;
+import org.codehaus.plexus.util.PropertyUtils;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.WriterFactory;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.codehaus.plexus.velocity.VelocityComponent;
+
+/**
+ * DefaultSiteRenderer class.
+ *
+ * @author Emmanuel Venisse
+ * @author Vincent Siveton
+ * @since 1.0
+ */
+@Component( role = Renderer.class )
+public class DefaultSiteRenderer
+ extends AbstractLogEnabled
+ implements Renderer
+{
+ // ----------------------------------------------------------------------
+ // Requirements
+ // ----------------------------------------------------------------------
+
+ @Requirement
+ private VelocityComponent velocity;
+
+ @Requirement
+ private ParserModuleManager parserModuleManager;
+
+ @Requirement
+ private Doxia doxia;
+
+ @Requirement
+ private I18N i18n;
+
+ @Requirement
+ private PlexusContainer plexus;
+
+ private static final String RESOURCE_DIR = "org/apache/maven/doxia/siterenderer/resources";
+
+ private static final String DEFAULT_TEMPLATE = RESOURCE_DIR + "/default-site.vm";
+
+ private static final String SKIN_TEMPLATE_LOCATION = "META-INF/maven/site.vm";
+
+ private static final String TOOLS_LOCATION = "META-INF/maven/site-tools.xml";
+
+ // ----------------------------------------------------------------------
+ // Renderer implementation
+ // ----------------------------------------------------------------------
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext )
+ throws IOException, RendererException
+ {
+ return locateDocumentFiles( siteRenderingContext, false );
+ }
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext,
+ boolean editable )
+ throws IOException, RendererException
+ {
+ Map files = new LinkedHashMap();
+ Map moduleExcludes = siteRenderingContext.getModuleExcludes();
+
+ // look in every site directory (in general src/site or target/generated-site)
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ if ( siteDirectory.exists() )
+ {
+ Collection modules = parserModuleManager.getParserModules();
+ // use every Doxia parser module
+ for ( ParserModule module : modules )
+ {
+ File moduleBasedir = new File( siteDirectory, module.getSourceDirectory() );
+
+ String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
+
+ addModuleFiles( siteRenderingContext.getRootDirectory(), moduleBasedir, module, excludes, files,
+ editable );
+ }
+ }
+ }
+
+ // look in specific modules directories (used for old Maven 1.x site layout: xdoc and fml docs in /xdocs)
+ for ( ExtraDoxiaModuleReference module : siteRenderingContext.getModules() )
+ {
+ try
+ {
+ ParserModule parserModule = parserModuleManager.getParserModule( module.getParserId() );
+
+ String excludes = ( moduleExcludes == null ) ? null : moduleExcludes.get( module.getParserId() );
+
+ addModuleFiles( siteRenderingContext.getRootDirectory(), module.getBasedir(), parserModule, excludes,
+ files, editable );
+ }
+ catch ( ParserModuleNotFoundException e )
+ {
+ throw new RendererException( "Unable to find module: " + e.getMessage(), e );
+ }
+ }
+ return files;
+ }
+
+ private List filterExtensionIgnoreCase( List fileNames, String extension )
+ {
+ List filtered = new LinkedList( fileNames );
+ for ( Iterator it = filtered.iterator(); it.hasNext(); )
+ {
+ String name = it.next();
+
+ // Take care of extension case
+ if ( !endsWithIgnoreCase( name, extension ) )
+ {
+ it.remove();
+ }
+ }
+ return filtered;
+ }
+
+ private void addModuleFiles( File rootDir, File moduleBasedir, ParserModule module, String excludes,
+ Map files, boolean editable )
+ throws IOException, RendererException
+ {
+ if ( !moduleBasedir.exists() || ArrayUtils.isEmpty( module.getExtensions() ) )
+ {
+ return;
+ }
+
+ String moduleRelativePath =
+ PathTool.getRelativeFilePath( rootDir.getAbsolutePath(), moduleBasedir.getAbsolutePath() );
+
+ List allFiles = FileUtils.getFileNames( moduleBasedir, "**/*.*", excludes, false );
+
+ for ( String extension : module.getExtensions() )
+ {
+ String fullExtension = "." + extension;
+
+ List docs = filterExtensionIgnoreCase( allFiles, fullExtension );
+
+ // *..vm
+ List velocityFiles = filterExtensionIgnoreCase( allFiles, fullExtension + ".vm" );
+
+ docs.addAll( velocityFiles );
+
+ for ( String doc : docs )
+ {
+ RenderingContext context = new RenderingContext( moduleBasedir, moduleRelativePath, doc,
+ module.getParserId(), extension, editable );
+
+ // TODO: DOXIA-111: we need a general filter here that knows how to alter the context
+ if ( endsWithIgnoreCase( doc, ".vm" ) )
+ {
+ context.setAttribute( "velocity", "true" );
+ }
+
+ String key = context.getOutputName();
+ key = StringUtils.replace( key, "\\", "/" );
+
+ if ( files.containsKey( key ) )
+ {
+ DocumentRenderer renderer = files.get( key );
+
+ RenderingContext originalContext = renderer.getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' clashes with existing '" + originalDoc + "'." );
+ }
+ // -----------------------------------------------------------------------
+ // Handle key without case differences
+ // -----------------------------------------------------------------------
+ for ( Map.Entry entry : files.entrySet() )
+ {
+ if ( entry.getKey().equalsIgnoreCase( key ) )
+ {
+ RenderingContext originalContext = entry.getValue().getRenderingContext();
+
+ File originalDoc = new File( originalContext.getBasedir(), originalContext.getInputName() );
+
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
+ {
+ throw new RendererException( "File '" + module.getSourceDirectory() + File.separator
+ + doc + "' clashes with existing '" + originalDoc + "'." );
+ }
+
+ if ( getLogger().isWarnEnabled() )
+ {
+ getLogger().warn( "File '" + module.getSourceDirectory() + File.separator + doc
+ + "' could clash with existing '" + originalDoc + "'." );
+ }
+ }
+ }
+
+ files.put( key, new DoxiaDocumentRenderer( context ) );
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void render( Collection documents, SiteRenderingContext siteRenderingContext,
+ File outputDirectory )
+ throws RendererException, IOException
+ {
+ for ( DocumentRenderer docRenderer : documents )
+ {
+ RenderingContext renderingContext = docRenderer.getRenderingContext();
+
+ File outputFile = new File( outputDirectory, docRenderer.getOutputName() );
+
+ File inputFile = new File( renderingContext.getBasedir(), renderingContext.getInputName() );
+
+ boolean modified = !outputFile.exists() || ( inputFile.lastModified() > outputFile.lastModified() )
+ || ( siteRenderingContext.getDecoration().getLastModified() > outputFile.lastModified() );
+
+ if ( modified || docRenderer.isOverwrite() )
+ {
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Generating " + outputFile );
+ }
+
+ Writer writer = null;
+ try
+ {
+ if ( !docRenderer.isExternalReport() )
+ {
+ writer = WriterFactory.newWriter( outputFile, siteRenderingContext.getOutputEncoding() );
+ }
+ docRenderer.renderDocument( writer, this, siteRenderingContext );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ else
+ {
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( inputFile + " unchanged, not regenerating..." );
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void renderDocument( Writer writer, RenderingContext docRenderingContext, SiteRenderingContext siteContext )
+ throws RendererException, FileNotFoundException, UnsupportedEncodingException
+ {
+ SiteRendererSink sink = new SiteRendererSink( docRenderingContext );
+
+ File doc = new File( docRenderingContext.getBasedir(), docRenderingContext.getInputName() );
+
+ Reader reader = null;
+ try
+ {
+ String resource = doc.getAbsolutePath();
+
+ Parser parser = doxia.getParser( docRenderingContext.getParserId() );
+ // DOXIASITETOOLS-146 don't render comments from source markup
+ parser.setEmitComments( false );
+
+ // TODO: DOXIA-111: the filter used here must be checked generally.
+ if ( docRenderingContext.getAttribute( "velocity" ) != null )
+ {
+ getLogger().debug( "Processing Velocity for " + docRenderingContext.getDoxiaSourcePath() );
+ try
+ {
+ Context vc = createDocumentVelocityContext( docRenderingContext, siteContext );
+
+ StringWriter sw = new StringWriter();
+
+ velocity.getEngine().mergeTemplate( resource, siteContext.getInputEncoding(), vc, sw );
+
+ String doxiaContent = sw.toString();
+
+ if ( siteContext.getProcessedContentOutput() != null )
+ {
+ // save Velocity processing result, ie the Doxia content that will be parsed after
+ saveVelocityProcessedContent( docRenderingContext, siteContext, doxiaContent );
+ }
+
+ reader = new StringReader( doxiaContent );
+ }
+ catch ( VelocityException e )
+ {
+ throw new RendererException( "Error parsing " + docRenderingContext.getDoxiaSourcePath()
+ + " as a Velocity template: " + e.getMessage(), e );
+ }
+
+ if ( parser.getType() == Parser.XML_TYPE && siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ }
+ else
+ {
+ switch ( parser.getType() )
+ {
+ case Parser.XML_TYPE:
+ reader = ReaderFactory.newXmlReader( doc );
+ if ( siteContext.isValidate() )
+ {
+ reader = validate( reader, resource );
+ }
+ break;
+
+ case Parser.TXT_TYPE:
+ case Parser.UNKNOWN_TYPE:
+ default:
+ reader = ReaderFactory.newReader( doc, siteContext.getInputEncoding() );
+ }
+ }
+ sink.enableLogging( new PlexusLoggerWrapper( getLogger() ) );
+
+ doxia.parse( reader, docRenderingContext.getParserId(), sink );
+ }
+ catch ( ParserNotFoundException e )
+ {
+ throw new RendererException( "Error getting a parser for '" + doc + "': " + e.getMessage(), e );
+ }
+ catch ( ParseException e )
+ {
+ StringBuilder errorMsgBuilder = new StringBuilder();
+ errorMsgBuilder.append( "Error parsing '" ).append( doc ).append( "': " );
+ if ( e.getLineNumber() > 0 )
+ {
+ errorMsgBuilder.append( "line [" ).append( e.getLineNumber() ).append( "] " );
+ }
+ errorMsgBuilder.append( e.getMessage() );
+ throw new RendererException( errorMsgBuilder.toString(), e );
+ }
+ catch ( IOException e )
+ {
+ throw new RendererException( "IOException when processing '" + doc + "'", e );
+ }
+ finally
+ {
+ sink.flush();
+
+ sink.close();
+
+ IOUtil.close( reader );
+ }
+
+ mergeDocumentIntoSite( writer, (DocumentContent) sink, siteContext );
+ }
+
+ private void saveVelocityProcessedContent( RenderingContext docRenderingContext, SiteRenderingContext siteContext,
+ String doxiaContent )
+ throws IOException
+ {
+ if ( !siteContext.getProcessedContentOutput().exists() )
+ {
+ siteContext.getProcessedContentOutput().mkdirs();
+ }
+
+ String input = docRenderingContext.getInputName();
+ File outputFile = new File( siteContext.getProcessedContentOutput(),
+ input.substring( 0, input.length() - 3 ) );
+
+ File outputParent = outputFile.getParentFile();
+ if ( !outputParent.exists() )
+ {
+ outputParent.mkdirs();
+ }
+
+ FileUtils.fileWrite( outputFile, siteContext.getInputEncoding(), doxiaContent );
+ }
+
+ /**
+ * Creates a Velocity Context with all generic tools configured wit the site rendering context.
+ *
+ * @param siteRenderingContext the site rendering context
+ * @return a Velocity tools managed context
+ */
+ protected Context createToolManagedVelocityContext( SiteRenderingContext siteRenderingContext )
+ {
+ Locale locale = siteRenderingContext.getLocale();
+ String dateFormat = siteRenderingContext.getDecoration().getPublishDate().getFormat();
+
+ EasyFactoryConfiguration config = new EasyFactoryConfiguration( false );
+ config.property( "safeMode", Boolean.FALSE );
+ config.toolbox( Scope.REQUEST )
+ .tool( ContextTool.class )
+ .tool( LinkTool.class )
+ .tool( LoopTool.class )
+ .tool( RenderTool.class );
+ config.toolbox( Scope.APPLICATION ).property( "locale", locale )
+ .tool( AlternatorTool.class )
+ .tool( ClassTool.class )
+ .tool( ComparisonDateTool.class ).property( "format", dateFormat )
+ .tool( ConversionTool.class ).property( "dateFormat", dateFormat )
+ .tool( DisplayTool.class )
+ .tool( EscapeTool.class )
+ .tool( FieldTool.class )
+ .tool( MathTool.class )
+ .tool( NumberTool.class )
+ .tool( ResourceTool.class ).property( "bundles", new String[] { "site-renderer" } )
+ .tool( SortTool.class )
+ .tool( XmlTool.class );
+
+ FactoryConfiguration customConfig = ConfigurationUtils.findInClasspath( TOOLS_LOCATION );
+
+ if ( customConfig != null )
+ {
+ config.addConfiguration( customConfig );
+ }
+
+ ToolManager manager = new ToolManager( false, false );
+ manager.configure( config );
+
+ return manager.createContext();
+ }
+
+ /**
+ * Create a Velocity Context for a Doxia document, containing every information about rendered document.
+ *
+ * @param sink the site renderer sink for the document
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createDocumentVelocityContext( RenderingContext renderingContext,
+ SiteRenderingContext siteRenderingContext )
+ {
+ Context context = createToolManagedVelocityContext( siteRenderingContext );
+ // ----------------------------------------------------------------------
+ // Data objects
+ // ----------------------------------------------------------------------
+
+ context.put( "relativePath", renderingContext.getRelativePath() );
+
+ String currentFileName = renderingContext.getOutputName().replace( '\\', '/' );
+ context.put( "currentFileName", currentFileName );
+
+ context.put( "alignedFileName", PathTool.calculateLink( currentFileName, renderingContext.getRelativePath() ) );
+
+ context.put( "decoration", siteRenderingContext.getDecoration() );
+
+ Locale locale = siteRenderingContext.getLocale();
+ context.put( "locale", locale );
+ context.put( "supportedLocales", Collections.unmodifiableList( siteRenderingContext.getSiteLocales() ) );
+
+ context.put( "currentDate", new Date() );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateRevision", sdf.format( new Date() ) );
+
+ context.put( "publishDate", siteRenderingContext.getPublishDate() );
+
+ PublishDate publishDate = siteRenderingContext.getDecoration().getPublishDate();
+ DateFormat dateFormat = new SimpleDateFormat( publishDate.getFormat(), locale );
+ context.put( "dateFormat", dateFormat );
+
+ // doxiaSiteRendererVersion
+ InputStream inputStream = this.getClass().getResourceAsStream( "/META-INF/"
+ + "maven/org.apache.maven.doxia/doxia-site-renderer/pom.properties" );
+ Properties properties = PropertyUtils.loadProperties( inputStream );
+ if ( inputStream == null )
+ {
+ getLogger().debug( "pom.properties for doxia-site-renderer could not be found." );
+ }
+ else if ( properties == null )
+ {
+ getLogger().debug( "Failed to load pom.properties, so doxiaVersion is not available"
+ + " in the Velocity context." );
+ }
+ else
+ {
+ context.put( "doxiaSiteRendererVersion", properties.getProperty( "version" ) );
+ }
+
+ // Add user properties
+ Map templateProperties = siteRenderingContext.getTemplateProperties();
+
+ if ( templateProperties != null )
+ {
+ for ( Map.Entry entry : templateProperties.entrySet() )
+ {
+ context.put( entry.getKey(), entry.getValue() );
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Tools
+ // ----------------------------------------------------------------------
+
+ context.put( "PathTool", new PathTool() );
+
+ context.put( "FileUtils", new FileUtils() );
+
+ context.put( "StringUtils", new StringUtils() );
+
+ context.put( "i18n", i18n );
+
+ context.put( "plexus", plexus );
+ return context;
+ }
+
+ /**
+ * Create a Velocity Context for the site template decorating the document. In addition to all the informations
+ * from the document, this context contains data gathered in {@link SiteRendererSink} during document rendering.
+ *
+ * @param content the document content to be merged into the template
+ * @param siteRenderingContext the site rendering context
+ * @return
+ */
+ protected Context createSiteTemplateVelocityContext( DocumentContent content,
+ SiteRenderingContext siteRenderingContext )
+ {
+ // first get the context from document
+ Context context = createDocumentVelocityContext( content.getRenderingContext(), siteRenderingContext );
+
+ // then add data objects from rendered document
+
+ // Add infos from document
+ context.put( "authors", content.getAuthors() );
+
+ context.put( "shortTitle", content.getTitle() );
+
+ // DOXIASITETOOLS-70: Prepend the project name to the title, if any
+ String title = "";
+ if ( siteRenderingContext.getDecoration() != null
+ && siteRenderingContext.getDecoration().getName() != null )
+ {
+ title = siteRenderingContext.getDecoration().getName();
+ }
+ else if ( siteRenderingContext.getDefaultWindowTitle() != null )
+ {
+ title = siteRenderingContext.getDefaultWindowTitle();
+ }
+
+ if ( title.length() > 0 )
+ {
+ title += " – "; // Symbol Name: En Dash, Html Entity: –
+ }
+ title += content.getTitle();
+
+ context.put( "title", title );
+
+ context.put( "headContent", content.getHead() );
+
+ context.put( "bodyContent", content.getBody() );
+
+ // document date (got from Doxia Sink date() API)
+ String documentDate = content.getDate();
+ if ( StringUtils.isNotEmpty( documentDate ) )
+ {
+ context.put( "documentDate", documentDate );
+
+ // deprecated variables that rework the document date, suppose one semantics over others
+ // (ie creation date, while it may be last modification date if the document writer decided so)
+ // see DOXIASITETOOLS-20 for the beginning and DOXIASITETOOLS-164 for the end of this story
+ try
+ {
+ // we support only ISO 8601 date
+ Date creationDate = new SimpleDateFormat( "yyyy-MM-dd" ).parse( documentDate );
+
+ context.put( "creationDate", creationDate );
+ SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMdd" );
+ context.put( "dateCreation", sdf.format( creationDate ) );
+ }
+ catch ( java.text.ParseException e )
+ {
+ getLogger().warn( "Could not parse date '" + documentDate + "' from "
+ + content.getRenderingContext().getInputName()
+ + " (expected yyyy-MM-dd format), ignoring!" );
+ }
+ }
+
+ // document rendering context, to get eventual inputName
+ context.put( "docRenderingContext", content.getRenderingContext() );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void generateDocument( Writer writer, SiteRendererSink sink, SiteRenderingContext siteRenderingContext )
+ throws RendererException
+ {
+ mergeDocumentIntoSite( writer, sink, siteRenderingContext );
+ }
+
+ /** {@inheritDoc} */
+ public void mergeDocumentIntoSite( Writer writer, DocumentContent content,
+ SiteRenderingContext siteRenderingContext )
+ throws RendererException
+ {
+ String templateName = siteRenderingContext.getTemplateName();
+
+ getLogger().debug( "Processing Velocity for template " + templateName + " on "
+ + content.getRenderingContext().getInputName() );
+
+ Context context = createSiteTemplateVelocityContext( content, siteRenderingContext );
+
+ ClassLoader old = null;
+
+ if ( siteRenderingContext.getTemplateClassLoader() != null )
+ {
+ // -------------------------------------------------------------------------
+ // If no template classloader was set we'll just use the context classloader
+ // -------------------------------------------------------------------------
+
+ old = Thread.currentThread().getContextClassLoader();
+
+ Thread.currentThread().setContextClassLoader( siteRenderingContext.getTemplateClassLoader() );
+ }
+
+ try
+ {
+ Template template;
+ Artifact skin = siteRenderingContext.getSkin();
+
+ try
+ {
+ SkinModel skinModel = siteRenderingContext.getSkinModel();
+ String encoding = ( skinModel == null ) ? null : skinModel.getEncoding();
+
+ template = ( encoding == null ) ? velocity.getEngine().getTemplate( templateName )
+ : velocity.getEngine().getTemplate( templateName, encoding );
+ }
+ catch ( ParseErrorException pee )
+ {
+ throw new RendererException( "Velocity parsing error while reading the site decoration template "
+ + ( ( skin == null ) ? ( "'" + templateName + "'" ) : ( "from " + skin.getId() + " skin" ) ),
+ pee );
+ }
+ catch ( ResourceNotFoundException rnfe )
+ {
+ throw new RendererException( "Could not find the site decoration template "
+ + ( ( skin == null ) ? ( "'" + templateName + "'" ) : ( "from " + skin.getId() + " skin" ) ),
+ rnfe );
+ }
+
+ try
+ {
+ StringWriter sw = new StringWriter();
+ template.merge( context, sw );
+ writer.write( sw.toString().replaceAll( "\r?\n", SystemUtils.LINE_SEPARATOR ) );
+ }
+ catch ( VelocityException ve )
+ {
+ throw new RendererException( "Velocity error while merging site decoration template.", ve );
+ }
+ catch ( IOException ioe )
+ {
+ throw new RendererException( "IO exception while merging site decoration template.", ioe );
+ }
+ }
+ finally
+ {
+ IOUtil.close( writer );
+
+ if ( old != null )
+ {
+ Thread.currentThread().setContextClassLoader( old );
+ }
+ }
+ }
+
+ private SiteRenderingContext createSiteRenderingContext( Map attributes, DecorationModel decoration,
+ String defaultWindowTitle, Locale locale )
+ {
+ SiteRenderingContext context = new SiteRenderingContext();
+
+ context.setTemplateProperties( attributes );
+ context.setLocale( locale );
+ context.setDecoration( decoration );
+ context.setDefaultWindowTitle( defaultWindowTitle );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+/**
+ * {@inheritDoc }
+ */
+public org.apache.maven.doxia.siterenderer.SiteRenderingContext createContextForSkin(org.apache.maven.artifact.Artifact skin, java.util.Map attributes, org.apache.maven.doxia.site.decoration.DecorationModel decoration, java.lang.String defaultWindowTitle, java.util.Locale locale) throws java.io.IOException, org.apache.maven.doxia.siterenderer.RendererException {
+ org.apache.maven.doxia.siterenderer.SiteRenderingContext context = createSiteRenderingContext(attributes, decoration, defaultWindowTitle, locale);
+ context.setSkin(skin);
+ java.util.zip.ZipFile zipFile = org.apache.maven.doxia.siterenderer.DefaultSiteRenderer.getZipFile(skin.getFile());
+ java.io.InputStream in = null;
+ try {
+ if (zipFile.getEntry(org.apache.maven.doxia.siterenderer.DefaultSiteRenderer.SKIN_TEMPLATE_LOCATION) != null) {
+ context.setTemplateName(org.apache.maven.doxia.siterenderer.DefaultSiteRenderer.SKIN_TEMPLATE_LOCATION);
+ context.setTemplateClassLoader(new java.net.URLClassLoader(new java.net.URL[]{ skin.getFile().toURI().toURL() }));
+ } else {
+ context.setTemplateName(org.apache.maven.doxia.siterenderer.DefaultSiteRenderer.DEFAULT_TEMPLATE);
+ context.setTemplateClassLoader(getClass().getClassLoader());
+ context.setUsingDefaultTemplate(true);
+ }
+ java.util.zip.ZipEntry skinDescriptorEntry = zipFile.getEntry(org.apache.maven.doxia.site.skin.SkinModel.SKIN_DESCRIPTOR_LOCATION);
+ {
+ in = zipFile.getInputStream(/* NPEX_NULL_EXP */
+ skinDescriptorEntry);
+ org.apache.maven.doxia.site.skin.SkinModel skinModel = new org.apache.maven.doxia.site.skin.io.xpp3.SkinXpp3Reader().read(in);
+ context.setSkinModel(skinModel);
+ java.lang.String toolsPrerequisite = (skinModel.getPrerequisites() == null) ? null : skinModel.getPrerequisites().getDoxiaSitetools();
+ java.lang.Package p = org.apache.maven.doxia.siterenderer.DefaultSiteRenderer.class.getPackage();
+ java.lang.String current = (p == null) ? null : p.getImplementationVersion();
+ if ((org.codehaus.plexus.util.StringUtils.isNotBlank(toolsPrerequisite) && (current != null)) && (!matchVersion(current, toolsPrerequisite))) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException((("Cannot use skin: has " + toolsPrerequisite) + " Doxia Sitetools prerequisite, but current is ") + current);
+ }
+ }
+ } catch (org.codehaus.plexus.util.xml.pull.XmlPullParserException e) {
+ throw new org.apache.maven.doxia.siterenderer.RendererException(((("Failed to parse " + org.apache.maven.doxia.site.skin.SkinModel.SKIN_DESCRIPTOR_LOCATION) + " skin descriptor from ") + skin.getId()) + " skin", e);
+ } finally {
+ org.codehaus.plexus.util.IOUtil.close(in);
+ org.apache.maven.doxia.siterenderer.DefaultSiteRenderer.closeZipFile(zipFile);
+ }
+ return context;
+}
+
+ boolean matchVersion( String current, String prerequisite )
+ throws RendererException
+ {
+ try
+ {
+ ArtifactVersion v = new DefaultArtifactVersion( current );
+ VersionRange vr = VersionRange.createFromVersionSpec( prerequisite );
+
+ boolean matched = false;
+ ArtifactVersion recommendedVersion = vr.getRecommendedVersion();
+ if ( recommendedVersion == null )
+ {
+ List restrictions = vr.getRestrictions();
+ for ( Restriction restriction : restrictions )
+ {
+ if ( restriction.containsVersion( v ) )
+ {
+ matched = true;
+ break;
+ }
+ }
+ }
+ else
+ {
+ // only singular versions ever have a recommendedVersion
+ @SuppressWarnings( "unchecked" )
+ int compareTo = recommendedVersion.compareTo( v );
+ matched = ( compareTo <= 0 );
+ }
+
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Skin doxia-sitetools prerequisite: " + prerequisite + ", current: " + current
+ + ", matched = " + matched );
+ }
+
+ return matched;
+ }
+ catch ( InvalidVersionSpecificationException e )
+ {
+ throw new RendererException( "Invalid skin doxia-sitetools prerequisite: " + prerequisite, e );
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Deprecated
+ public SiteRenderingContext createContextForTemplate( File templateFile, Map attributes,
+ DecorationModel decoration, String defaultWindowTitle,
+ Locale locale )
+ throws MalformedURLException
+ {
+ SiteRenderingContext context = createSiteRenderingContext( attributes, decoration, defaultWindowTitle, locale );
+
+ context.setTemplateName( templateFile.getName() );
+ context.setTemplateClassLoader( new URLClassLoader( new URL[]{templateFile.getParentFile().toURI().toURL()} ) );
+
+ return context;
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File resourcesDirectory,
+ File outputDirectory )
+ throws IOException
+ {
+ throw new AssertionError( "copyResources( SiteRenderingContext, File, File ) is deprecated." );
+ }
+
+ /** {@inheritDoc} */
+ public void copyResources( SiteRenderingContext siteRenderingContext, File outputDirectory )
+ throws IOException
+ {
+ if ( siteRenderingContext.getSkin() != null )
+ {
+ ZipFile file = getZipFile( siteRenderingContext.getSkin().getFile() );
+
+ try
+ {
+ for ( Enumeration extends ZipEntry> e = file.entries(); e.hasMoreElements(); )
+ {
+ ZipEntry entry = e.nextElement();
+
+ if ( !entry.getName().startsWith( "META-INF/" ) )
+ {
+ File destFile = new File( outputDirectory, entry.getName() );
+ if ( !entry.isDirectory() )
+ {
+ if ( destFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ destFile.getParentFile().mkdirs();
+
+ copyFileFromZip( file, entry, destFile );
+ }
+ else
+ {
+ destFile.mkdirs();
+ }
+ }
+ }
+ }
+ finally
+ {
+ closeZipFile( file );
+ }
+ }
+
+ if ( siteRenderingContext.isUsingDefaultTemplate() )
+ {
+ InputStream resourceList = getClass().getClassLoader()
+ .getResourceAsStream( RESOURCE_DIR + "/resources.txt" );
+
+ if ( resourceList != null )
+ {
+ Reader r = null;
+ LineNumberReader reader = null;
+ try
+ {
+ r = ReaderFactory.newReader( resourceList, ReaderFactory.UTF_8 );
+ reader = new LineNumberReader( r );
+
+ String line;
+
+ while ( ( line = reader.readLine() ) != null )
+ {
+ if ( line.startsWith( "#" ) || line.trim().length() == 0 )
+ {
+ continue;
+ }
+
+ InputStream is = getClass().getClassLoader().getResourceAsStream( RESOURCE_DIR + "/" + line );
+
+ if ( is == null )
+ {
+ throw new IOException( "The resource " + line + " doesn't exist." );
+ }
+
+ File outputFile = new File( outputDirectory, line );
+
+ if ( outputFile.exists() )
+ {
+ // don't override existing content: avoids extra rewrite with same content or extra site
+ // resource
+ continue;
+ }
+
+ if ( !outputFile.getParentFile().exists() )
+ {
+ outputFile.getParentFile().mkdirs();
+ }
+
+ OutputStream os = null;
+ try
+ {
+ // for the images
+ os = new FileOutputStream( outputFile );
+ IOUtil.copy( is, os );
+ }
+ finally
+ {
+ IOUtil.close( os );
+ }
+
+ IOUtil.close( is );
+ }
+ }
+ finally
+ {
+ IOUtil.close( reader );
+ IOUtil.close( r );
+ }
+ }
+ }
+
+ // Copy extra site resources
+ for ( File siteDirectory : siteRenderingContext.getSiteDirectories() )
+ {
+ File resourcesDirectory = new File( siteDirectory, "resources" );
+
+ if ( resourcesDirectory != null && resourcesDirectory.exists() )
+ {
+ copyDirectory( resourcesDirectory, outputDirectory );
+ }
+ }
+
+ // Check for the existence of /css/site.css
+ File siteCssFile = new File( outputDirectory, "/css/site.css" );
+ if ( !siteCssFile.exists() )
+ {
+ // Create the subdirectory css if it doesn't exist, DOXIA-151
+ File cssDirectory = new File( outputDirectory, "/css/" );
+ boolean created = cssDirectory.mkdirs();
+ if ( created && getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The directory '" + cssDirectory.getAbsolutePath() + "' did not exist. It was created." );
+ }
+
+ // If the file is not there - create an empty file, DOXIA-86
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug(
+ "The file '" + siteCssFile.getAbsolutePath() + "' does not exist. Creating an empty file." );
+ }
+ Writer writer = null;
+ try
+ {
+ writer = WriterFactory.newWriter( siteCssFile, siteRenderingContext.getOutputEncoding() );
+ //DOXIA-290...the file should not be 0 bytes.
+ writer.write( "/* You can override this file with your own styles */" );
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+ }
+
+ private static void copyFileFromZip( ZipFile file, ZipEntry entry, File destFile )
+ throws IOException
+ {
+ FileOutputStream fos = new FileOutputStream( destFile );
+
+ try
+ {
+ IOUtil.copy( file.getInputStream( entry ), fos );
+ }
+ finally
+ {
+ IOUtil.close( fos );
+ }
+ }
+
+ /**
+ * Copy the directory
+ *
+ * @param source source file to be copied
+ * @param destination destination file
+ * @throws java.io.IOException if any
+ */
+ protected void copyDirectory( File source, File destination )
+ throws IOException
+ {
+ if ( source.exists() )
+ {
+ DirectoryScanner scanner = new DirectoryScanner();
+
+ String[] includedResources = {"**/**"};
+
+ scanner.setIncludes( includedResources );
+
+ scanner.addDefaultExcludes();
+
+ scanner.setBasedir( source );
+
+ scanner.scan();
+
+ List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
+
+ for ( String name : includedFiles )
+ {
+ File sourceFile = new File( source, name );
+
+ File destinationFile = new File( destination, name );
+
+ FileUtils.copyFile( sourceFile, destinationFile );
+ }
+ }
+ }
+
+ private Reader validate( Reader source, String resource )
+ throws ParseException, IOException
+ {
+ getLogger().debug( "Validating: " + resource );
+
+ try
+ {
+ String content = IOUtil.toString( new BufferedReader( source ) );
+
+ new XmlValidator( new PlexusLoggerWrapper( getLogger() ) ).validate( content );
+
+ return new StringReader( content );
+ }
+ finally
+ {
+ IOUtil.close( source );
+ }
+ }
+
+ // TODO replace with StringUtils.endsWithIgnoreCase() from maven-shared-utils 0.7
+ static boolean endsWithIgnoreCase( String str, String searchStr )
+ {
+ if ( str.length() < searchStr.length() )
+ {
+ return false;
+ }
+
+ return str.regionMatches( true, str.length() - searchStr.length(), searchStr, 0, searchStr.length() );
+ }
+
+ private static ZipFile getZipFile( File file )
+ throws IOException
+ {
+ if ( file == null )
+ {
+ throw new IOException( "Error opening ZipFile: null" );
+ }
+
+ try
+ {
+ // TODO: plexus-archiver, if it could do the excludes
+ return new ZipFile( file );
+ }
+ catch ( ZipException ex )
+ {
+ IOException ioe = new IOException( "Error opening ZipFile: " + file.getAbsolutePath() );
+ ioe.initCause( ex );
+ throw ioe;
+ }
+ }
+
+ private static void closeZipFile( ZipFile zipFile )
+ {
+ // TODO: move to plexus utils
+ try
+ {
+ zipFile.close();
+ }
+ catch ( IOException e )
+ {
+ // ignore
+ }
+ }
+}
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_821/npe.json b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_821/npe.json
new file mode 100644
index 000000000..76c2d1eb4
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_821/npe.json
@@ -0,0 +1,7 @@
+{
+ "filepath": "doxia-site-renderer/src/main/java/org/apache/maven/doxia/siterenderer/DefaultSiteRenderer.java",
+ "line": 814,
+ "npe_method": "createContextForSkin",
+ "deref_field": "skinDescriptorEntry",
+ "npe_class": "DefaultSiteRenderer"
+}
\ No newline at end of file
diff --git a/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_828/buggy.java b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_828/buggy.java
new file mode 100644
index 000000000..1add25b38
--- /dev/null
+++ b/Java-base/maven-doxia-sitetools/bugs/DefaultSiteRenderer_828/buggy.java
@@ -0,0 +1,1169 @@
+package org.apache.maven.doxia.siterenderer;
+
+/*
+ * 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.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+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 java.util.Properties;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
+import org.apache.maven.artifact.versioning.Restriction;
+import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.doxia.Doxia;
+import org.apache.maven.doxia.logging.PlexusLoggerWrapper;
+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.site.decoration.DecorationModel;
+import org.apache.maven.doxia.site.decoration.PublishDate;
+import org.apache.maven.doxia.site.skin.SkinModel;
+import org.apache.maven.doxia.site.skin.io.xpp3.SkinXpp3Reader;
+import org.apache.maven.doxia.parser.module.ParserModule;
+import org.apache.maven.doxia.parser.module.ParserModuleManager;
+import org.apache.maven.doxia.parser.module.ParserModuleNotFoundException;
+import org.apache.maven.doxia.siterenderer.sink.SiteRendererSink;
+import org.apache.maven.doxia.util.XmlValidator;
+import org.apache.velocity.Template;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.tools.Scope;
+import org.apache.velocity.tools.ToolManager;
+import org.apache.velocity.tools.config.ConfigurationUtils;
+import org.apache.velocity.tools.config.EasyFactoryConfiguration;
+import org.apache.velocity.tools.config.FactoryConfiguration;
+import org.apache.velocity.tools.generic.AlternatorTool;
+import org.apache.velocity.tools.generic.ClassTool;
+import org.apache.velocity.tools.generic.ComparisonDateTool;
+import org.apache.velocity.tools.generic.ContextTool;
+import org.apache.velocity.tools.generic.ConversionTool;
+import org.apache.velocity.tools.generic.DisplayTool;
+import org.apache.velocity.tools.generic.EscapeTool;
+import org.apache.velocity.tools.generic.FieldTool;
+import org.apache.velocity.tools.generic.LinkTool;
+import org.apache.velocity.tools.generic.LoopTool;
+import org.apache.velocity.tools.generic.MathTool;
+import org.apache.velocity.tools.generic.NumberTool;
+import org.apache.velocity.tools.generic.RenderTool;
+import org.apache.velocity.tools.generic.ResourceTool;
+import org.apache.velocity.tools.generic.SortTool;
+import org.apache.velocity.tools.generic.XmlTool;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.i18n.I18N;
+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.Os;
+import org.codehaus.plexus.util.PathTool;
+import org.codehaus.plexus.util.PropertyUtils;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.WriterFactory;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+import org.codehaus.plexus.velocity.VelocityComponent;
+
+/**
+ * DefaultSiteRenderer class.
+ *
+ * @author Emmanuel Venisse
+ * @author Vincent Siveton
+ * @since 1.0
+ */
+@Component( role = Renderer.class )
+public class DefaultSiteRenderer
+ extends AbstractLogEnabled
+ implements Renderer
+{
+ // ----------------------------------------------------------------------
+ // Requirements
+ // ----------------------------------------------------------------------
+
+ @Requirement
+ private VelocityComponent velocity;
+
+ @Requirement
+ private ParserModuleManager parserModuleManager;
+
+ @Requirement
+ private Doxia doxia;
+
+ @Requirement
+ private I18N i18n;
+
+ @Requirement
+ private PlexusContainer plexus;
+
+ private static final String RESOURCE_DIR = "org/apache/maven/doxia/siterenderer/resources";
+
+ private static final String DEFAULT_TEMPLATE = RESOURCE_DIR + "/default-site.vm";
+
+ private static final String SKIN_TEMPLATE_LOCATION = "META-INF/maven/site.vm";
+
+ private static final String TOOLS_LOCATION = "META-INF/maven/site-tools.xml";
+
+ // ----------------------------------------------------------------------
+ // Renderer implementation
+ // ----------------------------------------------------------------------
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext )
+ throws IOException, RendererException
+ {
+ return locateDocumentFiles( siteRenderingContext, false );
+ }
+
+ /** {@inheritDoc} */
+ public Map locateDocumentFiles( SiteRenderingContext siteRenderingContext,
+ boolean editable )
+ throws IOException, RendererException
+ {
+ Map